├── .coveragerc ├── .github └── workflows │ └── tests.yaml ├── .gitignore ├── .hgignore ├── .readthedocs.yaml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── DeveloperGuidelines.txt ├── StyleGuide.txt ├── _static │ ├── default.css │ └── paste.css ├── community │ ├── index.txt │ ├── mailing-list.txt │ └── repository.txt ├── conf.py ├── developer-features.txt ├── do-it-yourself-framework.txt ├── download │ └── index.txt ├── future.txt ├── include │ ├── contact.txt │ └── reference_header.txt ├── index.txt ├── license.txt ├── modules │ ├── auth.auth_tkt.txt │ ├── auth.basic.txt │ ├── auth.cas.txt │ ├── auth.cookie.txt │ ├── auth.digest.txt │ ├── auth.form.txt │ ├── auth.grantip.txt │ ├── auth.multi.txt │ ├── cascade.txt │ ├── cgiapp.txt │ ├── cgitb_catcher.txt │ ├── debug.debugapp.txt │ ├── debug.fsdiff.txt │ ├── debug.prints.txt │ ├── debug.profile.txt │ ├── debug.watchthreads.txt │ ├── debug.wdg_validate.txt │ ├── errordocument.txt │ ├── evalexception.txt │ ├── exceptions.txt │ ├── fileapp.txt │ ├── fixture.txt │ ├── gzipper.txt │ ├── httpexceptions.txt │ ├── httpheaders.txt │ ├── httpserver.txt │ ├── lint.txt │ ├── pony.txt │ ├── progress.txt │ ├── proxy.txt │ ├── recursive.txt │ ├── registry.txt │ ├── reloader.txt │ ├── request.txt │ ├── response.txt │ ├── session.txt │ ├── transaction.txt │ ├── translogger.txt │ ├── url.txt │ ├── urlmap.txt │ ├── urlparser.txt │ ├── util.import_string.txt │ ├── util.multidict.txt │ ├── wsgilib.txt │ └── wsgiwrappers.txt ├── news.txt ├── paste-httpserver-threadpool.txt ├── test_server.ini ├── testing-applications.txt ├── url-parsing-with-wsgi.txt └── web │ ├── default-site.css │ ├── site.js │ └── style.css ├── paste ├── __init__.py ├── auth │ ├── __init__.py │ ├── auth_tkt.py │ ├── basic.py │ ├── cas.py │ ├── cookie.py │ ├── digest.py │ ├── form.py │ ├── grantip.py │ ├── multi.py │ └── open_id.py ├── cascade.py ├── cgiapp.py ├── cgitb_catcher.py ├── config.py ├── cowbell │ ├── __init__.py │ ├── bell-ascending.png │ └── bell-descending.png ├── debug │ ├── __init__.py │ ├── debugapp.py │ ├── doctest_webapp.py │ ├── fsdiff.py │ ├── prints.py │ ├── profile.py │ ├── testserver.py │ ├── watchthreads.py │ └── wdg_validate.py ├── errordocument.py ├── evalexception │ ├── __init__.py │ ├── evalcontext.py │ ├── media │ │ ├── MochiKit.packed.js │ │ ├── debug.js │ │ ├── minus.jpg │ │ └── plus.jpg │ └── middleware.py ├── exceptions │ ├── __init__.py │ ├── collector.py │ ├── errormiddleware.py │ ├── formatter.py │ ├── reporter.py │ └── serial_number_generator.py ├── fileapp.py ├── fixture.py ├── flup_session.py ├── gzipper.py ├── httpexceptions.py ├── httpheaders.py ├── httpserver.py ├── lint.py ├── modpython.py ├── pony.py ├── progress.py ├── proxy.py ├── recursive.py ├── registry.py ├── reloader.py ├── request.py ├── response.py ├── session.py ├── transaction.py ├── translogger.py ├── url.py ├── urlmap.py ├── urlparser.py ├── util │ ├── PySourceColor.py │ ├── __init__.py │ ├── cgitb_hook.py │ ├── classinit.py │ ├── classinstance.py │ ├── converters.py │ ├── dateinterval.py │ ├── datetimeutil.py │ ├── field_storage.py │ ├── filemixin.py │ ├── finddata.py │ ├── findpackage.py │ ├── import_string.py │ ├── intset.py │ ├── ip4.py │ ├── killthread.py │ ├── looper.py │ ├── mimeparse.py │ ├── multidict.py │ ├── quoting.py │ ├── scgiserver.py │ ├── template.py │ ├── threadedprint.py │ └── threadinglocal.py ├── wsgilib.py └── wsgiwrappers.py ├── regen-docs ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── cgiapp_data │ ├── error.cgi │ ├── form.cgi │ ├── ok.cgi │ └── stderr.cgi ├── template.txt ├── test_auth │ ├── __init__.py │ ├── test_auth_cookie.py │ ├── test_auth_digest.py │ └── test_auth_tkt.py ├── test_cgiapp.py ├── test_cgitb_catcher.py ├── test_config.py ├── test_doctests.py ├── test_errordocument.py ├── test_exceptions │ ├── __init__.py │ ├── test_error_middleware.py │ ├── test_formatter.py │ ├── test_httpexceptions.py │ └── test_reporter.py ├── test_fileapp.py ├── test_fixture.py ├── test_grantip.py ├── test_gzipper.py ├── test_httpheaders.py ├── test_httpserver.py ├── test_import_string.py ├── test_multidict.py ├── test_profilemiddleware.py ├── test_proxy.py ├── test_recursive.py ├── test_registry.py ├── test_request.py ├── test_request_form.py ├── test_response.py ├── test_session.py ├── test_urlmap.py ├── test_urlparser.py ├── test_util │ ├── __init__.py │ ├── test_cgitb_hook.py │ ├── test_datetimeutil.py │ ├── test_field_storage.py │ ├── test_mimeparse.py │ └── test_quoting.py ├── test_wsgilib.py ├── test_wsgiwrappers.py └── urlparser_data │ ├── __init__.py │ ├── deep │ ├── index.html │ └── sub │ │ └── Main.txt │ ├── find_file │ ├── dir with spaces │ │ └── test 4.html │ ├── index.txt │ ├── test 3.html │ └── test2.html │ ├── hook │ ├── __init__.py │ ├── app.py │ └── index.py │ ├── not_found │ ├── __init__.py │ ├── recur │ │ ├── __init__.py │ │ └── isfound.txt │ ├── simple │ │ ├── __init__.py │ │ └── found.txt │ └── user │ │ ├── __init__.py │ │ └── list.py │ ├── python │ ├── __init__.py │ ├── simpleapp.py │ ├── stream.py │ └── sub │ │ ├── __init__.py │ │ └── simpleapp.py │ └── secured.txt └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = 1 3 | # NOTE: cannot use package easily, without chdir (https://github.com/nedbat/coveragepy/issues/268). 4 | source = paste/,tests/ 5 | parallel = 1 6 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: 3 | - push 4 | - workflow_dispatch 5 | - pull_request 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | include: 12 | - python: "3.8" 13 | env: py38 14 | - python: "3.9" 15 | env: py39 16 | - python: "3.10" 17 | env: py310 18 | - python: "3.11" 19 | env: py311 20 | - python: "3.11" 21 | env: py311-namespace 22 | - python: "3.12" 23 | env: py312 24 | - python: pypy-3.10 25 | env: pypy3 26 | name: ${{ matrix.env }} on Python ${{ matrix.python }} 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: actions/setup-python@v5 30 | with: 31 | python-version: ${{ matrix.python }} 32 | - run: pip install tox 33 | - run: tox 34 | env: 35 | TOXENV: ${{ matrix.env }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pyc 3 | 4 | .coverage 5 | .idea 6 | .pytest_cache 7 | .tox 8 | .venv 9 | .vscode 10 | 11 | *.egg-info 12 | 13 | build 14 | dist 15 | docs/_build/ 16 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | .project 4 | .pydevproject 5 | .settings 6 | *.pyc 7 | *.pyo 8 | *.log 9 | *.tmp 10 | *.egg-info 11 | dist/ 12 | build/ 13 | docs/_build 14 | .tox 15 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-lts-latest 11 | tools: 12 | python: "3.11" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | 18 | # We recommend specifying your dependencies to enable reproducible builds: 19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | # python: 21 | # install: 22 | # - requirements: docs/requirements.txt 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | docs/license.txt -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include docs *.txt *.css *.js 2 | include docs/_templates/*.html 3 | include docs/conf.py 4 | include docs/test_server.ini 5 | include regen-docs 6 | include tox.ini 7 | include LICENSE 8 | recursive-exclude docs/_build/_sources * 9 | recursive-include docs/_build *.html 10 | recursive-include tests *.txt *.py *.cgi *.html 11 | recursive-include paste *.js *.jpg *.png 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # simple Makefile for some common tasks 2 | .PHONY: clean test dist release pypi tagv 3 | 4 | paste-version := $(shell python setup.py --version) 5 | 6 | clean: 7 | find . -name "*.pyc" |xargs rm || true 8 | rm -r dist || true 9 | rm -r build || true 10 | rm -rf .tox || true 11 | rm -r cover .coverage || true 12 | rm -r .eggs || true 13 | rm -r paste.egg-info || true 14 | 15 | tagv: 16 | git tag -s -m ${paste-version} ${paste-version} 17 | git push origin master --tags 18 | 19 | cleanagain: 20 | find . -name "*.pyc" |xargs rm || true 21 | rm -r dist || true 22 | rm -r build || true 23 | rm -r .tox || true 24 | rm -r cover .coverage || true 25 | rm -r .eggs || true 26 | rm -r paste.egg-info || true 27 | 28 | test: 29 | tox --skip-missing-interpreters 30 | 31 | dist: test 32 | python3 setup.py sdist bdist_wheel 33 | 34 | release: clean test cleanagain tagv pypi gh 35 | 36 | pypi: 37 | python3 setup.py sdist bdist_wheel 38 | twine upload dist/* 39 | 40 | gh: 41 | gh release create ${paste-version} --generate-notes dist/* 42 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | *Paste is in maintenance mode and recently moved from bitbucket to github. 3 | Patches are accepted to keep it on life support, but for the most part, please 4 | consider using other options.* 5 | 6 | **As of release 3.7.0 Paste no longer supports Python 2. If you are 7 | required to continue using Python 2 please pin an earlier version of Paste.** 8 | 9 | **With version 3.10.0 Paste development moves to the pasteorg GitHub 10 | organization and will be going deeper into maintenance mode unless 11 | more active maintainers step forward to take over. "Deeper" in this 12 | case means that releases will be much less frequent and patches 13 | will only be accepted for security issues or major problems. Current 14 | consumers of Paste should prepare to migrate away to more modern 15 | solutions.** 16 | 17 | Paste provides several pieces of "middleware" (or filters) that can be nested 18 | to build web applications. Each piece of middleware uses the WSGI (`PEP 333`_) 19 | interface, and should be compatible with other middleware based on those 20 | interfaces. 21 | 22 | .. _PEP 333: http://www.python.org/dev/peps/pep-0333/ 23 | 24 | * `Paste project at GitHub (source code, bug tracker) 25 | `_ 26 | * `Paste on the Python Cheeseshop (PyPI) 27 | `_ 28 | * `Paste on Read the Docs 29 | `_ 30 | 31 | See also: 32 | 33 | * `WebOb `_ 34 | 35 | Includes these features... 36 | 37 | Testing 38 | ------- 39 | 40 | * A fixture for testing WSGI applications conveniently and in-process, 41 | in ``paste.fixture`` 42 | 43 | * A fixture for testing command-line applications, also in 44 | ``paste.fixture`` 45 | 46 | * Check components for WSGI-compliance in ``paste.lint`` 47 | 48 | Dispatching 49 | ----------- 50 | 51 | * Chain and cascade WSGI applications (returning the first non-error 52 | response) in ``paste.cascade`` 53 | 54 | * Dispatch to several WSGI applications based on URL prefixes, in 55 | ``paste.urlmap`` 56 | 57 | * Allow applications to make subrequests and forward requests 58 | internally, in ``paste.recursive`` 59 | 60 | Web Application 61 | --------------- 62 | 63 | * Run CGI programs as WSGI applications in ``paste.cgiapp`` 64 | 65 | * Traverse files and load WSGI applications from ``.py`` files (or 66 | static files), in ``paste.urlparser`` 67 | 68 | * Serve static directories of files, also in ``paste.urlparser``; also 69 | in that module serving from Egg resources using ``pkg_resources``. 70 | 71 | Tools 72 | ----- 73 | 74 | * Catch HTTP-related exceptions (e.g., ``HTTPNotFound``) and turn them 75 | into proper responses in ``paste.httpexceptions`` 76 | 77 | * Several authentication techniques, including HTTP (Basic and 78 | Digest), signed cookies, and CAS single-signon, in the 79 | ``paste.auth`` package. 80 | 81 | * Create sessions in ``paste.session`` and ``paste.flup_session`` 82 | 83 | * Gzip responses in ``paste.gzip`` 84 | 85 | * A wide variety of routines for manipulating WSGI requests and 86 | producing responses, in ``paste.request``, ``paste.response`` and 87 | ``paste.wsgilib`` 88 | 89 | Debugging Filters 90 | ----------------- 91 | 92 | * Catch (optionally email) errors with extended tracebacks (using 93 | Zope/ZPT conventions) in ``paste.exceptions`` 94 | 95 | * Catch errors presenting traceback in ``paste.cgitb_catcher``. 96 | 97 | * Profile each request and append profiling information to the HTML, 98 | in ``paste.debug.profile`` 99 | 100 | * Capture ``print`` output and present it in the browser for 101 | debugging, in ``paste.debug.prints`` 102 | 103 | * Validate all HTML output from applications using the `WDG Validator 104 | `_, appending any errors 105 | or warnings to the page, in ``paste.debug.wdg_validator`` 106 | 107 | Other Tools 108 | ----------- 109 | 110 | * A file monitor to allow restarting the server when files have been 111 | updated (for automatic restarting when editing code) in 112 | ``paste.reloader`` 113 | 114 | * A class for generating and traversing URLs, and creating associated 115 | HTML code, in ``paste.url`` 116 | 117 | The official development repo is at https://github.com/pasteorg/paste. 118 | -------------------------------------------------------------------------------- /docs/StyleGuide.txt: -------------------------------------------------------------------------------- 1 | +++++++++++++++++++ 2 | Paste Style Guide 3 | +++++++++++++++++++ 4 | 5 | Generally you should follow the recommendations in `PEP 8`_, the 6 | Python Style Guide. Some things to take particular note of: 7 | 8 | .. _PEP 8: http://www.python.org/peps/pep-0008.html 9 | 10 | * **No tabs**. Not anywhere. Always indent with 4 spaces. 11 | 12 | * I don't stress too much on line length. But try to break lines up 13 | by grouping with parenthesis instead of with backslashes (if you 14 | can). Do asserts like:: 15 | 16 | assert some_condition(a, b), ( 17 | "Some condition failed, %r isn't right!" % a) 18 | 19 | * But if you are having problems with line length, maybe you should 20 | just break the expression up into multiple statements. 21 | 22 | * Blank lines between methods, unless they are very small and closely 23 | bound to each other. 24 | 25 | * Don't use the form ``condition and trueValue or falseValue``. Break 26 | it out and use a variable. 27 | 28 | * I (Ian Bicking) am very picky about whitespace. There's one and 29 | only one right way to do it. Good examples:: 30 | 31 | short = 3 32 | longerVar = 4 33 | 34 | if x == 4: 35 | do stuff 36 | 37 | func(arg1='a', arg2='b') 38 | func((a + b)*10) 39 | 40 | **Bad** examples:: 41 | 42 | short =3 43 | longerVar=4 44 | 45 | if x==4: do stuff 46 | 47 | func(arg1 = 'a', arg2 = 'b') 48 | func(a,b) 49 | func( a, b ) 50 | [ 1, 2, 3 ] 51 | 52 | If the whitespace isn't right, it'll annoy me and I'll feel a need 53 | to fix it. Really, this whitespace stuff shouldn't be that 54 | controversial should it? Some particular points that I feel 55 | strongly about: 56 | 57 | * No space after a function name (bad: ``func (arg)``). 58 | * No space after or before a parenthesis (bad: ``func( arg )``). 59 | * Always one space after a comma (bad: ``func(a,b)``). 60 | 61 | * Use ``@@`` to mark something that is suboptimal, or where you have a 62 | concern that it's not right. Try to also date it and put your 63 | username there. 64 | 65 | * Docstrings are good. They should look like:: 66 | 67 | class AClass: 68 | """ 69 | doc string... 70 | """ 71 | 72 | Don't use single quotes (''') -- they tend to cause problems in 73 | Emacs. Don't bother trying make the string less vertically compact. 74 | 75 | * Comments go right before the thing they are commenting on. 76 | 77 | * Methods never, ever, ever start with capital letters. Generally 78 | only classes are capitalized. But definitely never methods. 79 | 80 | * Use ``cls`` to refer to a class. Use ``meta`` to refer to a 81 | metaclass (which also happens to be a class, but calling a metaclass 82 | ``cls`` will be confusing). 83 | 84 | * Use ``isinstance`` instead of comparing types. E.g.:: 85 | 86 | if isinstance(var, str): ... 87 | # Bad: 88 | if type(var) is StringType: ... 89 | 90 | * Never, ever use two leading underscores. This is annoyingly 91 | private. If name clashes are a concern, use explicit name mangling 92 | instead (e.g., ``_MyThing_blahblah``). This is essentially the same 93 | thing as double-underscore, only it's transparent where double 94 | underscore obscures. 95 | 96 | * Module names should be unique in the package. Subpackages shouldn't 97 | share module names with sibling or parent packages. Sadly this 98 | isn't possible for ``__init__.py``, but it's otherwise easy enough. 99 | 100 | * Module names should be all lower case, and probably have no 101 | underscores (smushedwords). 102 | 103 | -------------------------------------------------------------------------------- /docs/_static/paste.css: -------------------------------------------------------------------------------- 1 | a.invisible-link { 2 | color: #fff; 3 | text-decoration: none; 4 | } 5 | 6 | a.invisible-link:visited { 7 | color: #fff; 8 | text-decoration: none; 9 | } 10 | 11 | a.invisible:link { 12 | color: #fff; 13 | text-decoration: none; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /docs/community/index.txt: -------------------------------------------------------------------------------- 1 | Community 2 | ========= 3 | 4 | **Much of the Paste community has moved on to other things. These 5 | links are left for reference, but do not expect to find much activity 6 | there.** 7 | 8 | Much of the communication goes on in the `mailing lists 9 | `_; see that page for information on the lists. 10 | 11 | For live IRC discussion, try the ``#pythonpaste`` channel on `Freenode 12 | `_. 13 | 14 | If you find bugs in the code or documentation, please `submit a ticket 15 | `_. You can also `view tickets 16 | `_. 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/community/mailing-list.txt: -------------------------------------------------------------------------------- 1 | Mailing Lists 2 | ============= 3 | 4 | **Much of the Paste community has moved on to other things. These 5 | links are left for reference, but do not expect to find much activity 6 | there.** 7 | 8 | General discussion and questions should go to: 9 | 10 | `paste-users@googlegroups.org `_: 11 | New posts are `on Google Groups `_ `old posts are in their own archive `_ 12 | 13 | More abstract discussion of Python web programming should go to: 14 | 15 | `web-sig@python.org `_: 16 | `Subscribe `__, 17 | `Archives `__ 18 | 19 | -------------------------------------------------------------------------------- /docs/community/repository.txt: -------------------------------------------------------------------------------- 1 | Repository 2 | ========== 3 | 4 | Paste is kept in a Git repository at 5 | http://github.com/pasteorg/paste 6 | 7 | Go there to make pull requests and report issues. 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Paste documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Apr 22 22:08:49 2008. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # The contents of this file are pickled, so don't put values in the namespace 9 | # that aren't pickleable (module imports are okay, they're removed automatically). 10 | # 11 | # All configuration values have a default value; values that are commented out 12 | # serve to show the default value. 13 | 14 | 15 | # If your extensions are in another directory, add it here. 16 | #import sys 17 | #sys.path.append('some/directory') 18 | 19 | # General configuration 20 | # --------------------- 21 | 22 | # Add any Sphinx extension module names here, as strings. They can be extensions 23 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 24 | extensions = ['sphinx.ext.autodoc'] 25 | 26 | # Add any paths that contain templates here, relative to this directory. 27 | # templates_path = ['_templates'] 28 | 29 | # The suffix of source filenames. 30 | source_suffix = '.txt' 31 | 32 | # The master toctree document. 33 | master_doc = 'index' 34 | 35 | # General substitutions. 36 | project = 'Paste' 37 | copyright = '2008, Ian Bicking' 38 | 39 | # The default replacements for |version| and |release|, also used in various 40 | # other places throughout the built documents. 41 | # 42 | # The short X.Y version. 43 | version = '' 44 | # The full version, including alpha/beta/rc tags. 45 | release = '' 46 | 47 | # There are two options for replacing |today|: either, you set today to some 48 | # non-false value, then it is used: 49 | #today = '' 50 | # Else, today_fmt is used as the format for a strftime call. 51 | today_fmt = '%B %d, %Y' 52 | 53 | # List of documents that shouldn't be included in the build. 54 | unused_docs = ['include/contact.txt', 'include/reference_header.txt'] 55 | 56 | # If true, '()' will be appended to :func: etc. cross-reference text. 57 | #add_function_parentheses = True 58 | 59 | # If true, the current module name will be prepended to all description 60 | # unit titles (such as .. function::). 61 | #add_module_names = True 62 | 63 | # If true, sectionauthor and moduleauthor directives will be shown in the 64 | # output. They are ignored by default. 65 | #show_authors = False 66 | 67 | # The name of the Pygments (syntax highlighting) style to use. 68 | pygments_style = 'sphinx' 69 | 70 | 71 | # Options for HTML output 72 | # ----------------------- 73 | 74 | html_theme = 'nature' 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 = ['_static'] 80 | 81 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 82 | # using the given strftime format. 83 | html_last_updated_fmt = '%b %d, %Y' 84 | 85 | # If true, SmartyPants will be used to convert quotes and dashes to 86 | # typographically correct entities. 87 | #html_use_smartypants = True 88 | 89 | # Content template for the index page. 90 | #html_index = '' 91 | 92 | # Custom sidebar templates, maps document names to template names. 93 | #html_sidebars = {} 94 | 95 | # Additional templates that should be rendered to pages, maps page names to 96 | # template names. 97 | #html_additional_pages = {} 98 | 99 | # If false, no module index is generated. 100 | #html_use_modindex = True 101 | 102 | # If true, the reST sources are included in the HTML build as _sources/. 103 | #html_copy_source = True 104 | 105 | # Output file base name for HTML help builder. 106 | htmlhelp_basename = 'Pastedoc' 107 | 108 | 109 | # Options for LaTeX output 110 | # ------------------------ 111 | 112 | # The paper size ('letter' or 'a4'). 113 | #latex_paper_size = 'letter' 114 | 115 | # The font size ('10pt', '11pt' or '12pt'). 116 | #latex_font_size = '10pt' 117 | 118 | # Grouping the document tree into LaTeX files. List of tuples 119 | # (source start file, target name, title, author, document class [howto/manual]). 120 | #latex_documents = [] 121 | 122 | # Additional stuff for the LaTeX preamble. 123 | #latex_preamble = '' 124 | 125 | # Documents to append as an appendix to all manuals. 126 | #latex_appendices = [] 127 | 128 | # If false, no module index is generated. 129 | #latex_use_modindex = True 130 | -------------------------------------------------------------------------------- /docs/download/index.txt: -------------------------------------------------------------------------------- 1 | Downloads 2 | ========= 3 | 4 | Each of these packages is available in several formats. The source 5 | distribution is a complete set of documentation, tests, and the source 6 | files themselves. There are also two "Egg" files: these are files 7 | `easy_install `_ 8 | can install directly into your ``site-packages/`` directory, and are 9 | Python-version specific. The download files for the latest version 10 | are always located on the Cheese Shop pages (listed below). 11 | 12 | * `Paste `_ 13 | * `Paste Script `_ 14 | * `Paste Deploy `_ 15 | * `Paste WebKit `_ 16 | * `Wareweb `_ (deprecated, use `Pylons 17 | `_ instead) 18 | 19 | All the packages are available in some repositories: 20 | 21 | * https://github.com/pasteorg/paste 22 | * https://github.com/pasteorg/pastescript 23 | * https://github.com/Pylons/pastedeploy/ 24 | * https://github.com/Pylons/webob 25 | * ... and others 26 | 27 | ..note:: Several of these repositories and projects are fairly stale and 28 | insufficiently maintained. Work is in progress to get them on 29 | life support, but it is unlikely they will ever be active projects 30 | again. WebOb is still actively maintained. 31 | -------------------------------------------------------------------------------- /docs/future.txt: -------------------------------------------------------------------------------- 1 | The Future Of Paste 2 | =================== 3 | 4 | Introduction 5 | ------------ 6 | 7 | Paste has been under development for a while, and has lots of code in it. Too much code! The code is largely decoupled except for some core functions shared by many parts of the code. Those core functions are largely replaced in `WebOb `_, and replaced with better implementations. 8 | 9 | The future of these pieces is to split them into independent packages, and refactor the internal Paste dependencies to rely instead on WebOb. 10 | 11 | Already Extracted 12 | ----------------- 13 | 14 | paste.fixture: 15 | WebTest 16 | ScriptTest 17 | 18 | paste.lint: 19 | wsgiref.validate 20 | 21 | paste.exceptions and paste.evalexception: 22 | WebError 23 | 24 | paste.util.template: 25 | Tempita 26 | 27 | 28 | To Be Separated 29 | --------------- 30 | 31 | paste.httpserver and paste.debug.watchthreads: 32 | Not sure what to call this. 33 | 34 | paste.cascade and paste.errordocuments: 35 | Not sure; Ben has an implementation of errordocuments in ``pylons.middleware.StatusCodeRedirect`` 36 | 37 | paste.urlmap, paste.deploy.config.PrefixMiddleware: 38 | In... some routing thing? Together with the previous package? 39 | 40 | paste.proxy: 41 | WSGIProxy (needs lots of cleanup though) 42 | 43 | paste.fileapp, paste.urlparser.StaticURLParser, paste.urlparser.PkgResourcesParser: 44 | In some new file-serving package. 45 | 46 | paste.cgiapp, wphp.fcgi_app: 47 | Some proxyish app... maybe WSGIProxy? 48 | 49 | paste.translogger, paste.debug.prints, paste.util.threadedprint, wsgifilter.proxyapp.DebugHeaders: 50 | Some... other place. Something loggy. 51 | 52 | paste.registry, paste.config: 53 | Not sure. Alberto Valverde expressed interest in splitting out paste.registry. 54 | 55 | paste.cgitb_catcher: 56 | Move to WebError? Not sure if it matters. For some reason people use this, though. 57 | 58 | 59 | To Deprecate 60 | ------------ 61 | 62 | (In that, I won't extract these anywhere; I'm not going to do any big deletes anytime soon, though) 63 | 64 | paste.recursive 65 | Better to do it manually (with webob.Request.get_response) 66 | 67 | paste.wsgiwrappers, paste.request, paste.response, paste.wsgilib, paste.httpheaders, paste.httpexceptions: 68 | All the functionality is already in WebOb. 69 | 70 | paste.urlparser.URLParser: 71 | Really this is tied to paste.webkit more than anything. 72 | 73 | paste.auth.*: 74 | Well, these all need to be refactored, and replacements exist in AuthKit and repoze.who. Some pieces might still have utility. 75 | 76 | paste.debug.profile: 77 | I think repoze.profile supersedes this. 78 | 79 | paste.debug.wdg_validator: 80 | It could get reimplemented with more options for validators, but I'm not really that interested at the moment. The code is nothing fancy. 81 | 82 | paste.transaction: 83 | More general in repoze.tm 84 | 85 | paste.url: 86 | No one uses this 87 | 88 | 89 | Undecided 90 | --------- 91 | 92 | paste.debug.fsdiff: 93 | Maybe ScriptTest? 94 | 95 | paste.session: 96 | It's an okay naive session system. But maybe Beaker makes it irrelevant (Beaker does seem slightly more complex to setup). But then, this can just live here indefinitely. 97 | 98 | paste.gzipper: 99 | I'm a little uncomfortable with this in concept. It's largely in WebOb right now, but not as middleware. 100 | 101 | paste.reloader: 102 | Maybe this should be moved to paste.script (i.e., paster serve) 103 | 104 | paste.debug.debugapp, paste.script.testapp: 105 | Alongside other debugging tools, I guess 106 | 107 | paste.progress: 108 | Not sure this works. 109 | -------------------------------------------------------------------------------- /docs/include/contact.txt: -------------------------------------------------------------------------------- 1 | If you have questions about this document, please contact the `paste 2 | mailing list `_ 3 | or try IRC (``#pythonpaste`` on freenode.net). If there's something that 4 | confused you and you want to give feedback, please `submit an issue 5 | `_. 6 | -------------------------------------------------------------------------------- /docs/include/reference_header.txt: -------------------------------------------------------------------------------- 1 | Paste Reference Document 2 | @@@@@@@@@@@@@@@@@@@@@@@@ 3 | 4 | .. contents:: 5 | 6 | -------------------------------------------------------------------------------- /docs/index.txt: -------------------------------------------------------------------------------- 1 | Python Paste 2 | ============ 3 | 4 | This is a new copy of the documentation for Paste. The project is now being 5 | maintained on limited life-support from https://github.com/pasteorg/paste 6 | **Please consider other options.** 7 | 8 | **With version 3.10.0 Paste development moves to the pasteorg GitHub 9 | organization and will be going deeper into maintenance mode unless 10 | more active maintainers step forward to take over. "Deeper" in this 11 | case means that releases will be much less frequent and patches 12 | will only be accepted for security issues or major problems. Current 13 | consumers of Paste should prepare to migrate away to more modern 14 | solutions.** 15 | 16 | Many of the links you will find in these docs will be out of date. If you 17 | know of a better destination for a link please submit an issue or pull 18 | request at the GitHub repository above. 19 | 20 | Contents: 21 | 22 | .. toctree:: 23 | :maxdepth: 1 24 | 25 | news 26 | future 27 | testing-applications 28 | url-parsing-with-wsgi 29 | do-it-yourself-framework 30 | paste-httpserver-threadpool 31 | developer-features 32 | DeveloperGuidelines 33 | StyleGuide 34 | paste-httpserver-threadpool 35 | testing-applications 36 | url-parsing-with-wsgi 37 | community/index.txt 38 | community/mailing-list.txt 39 | community/repository.txt 40 | download/index.txt 41 | license 42 | 43 | Indices and tables 44 | ================== 45 | 46 | * :ref:`genindex` 47 | * :ref:`modindex` 48 | * :ref:`search` 49 | 50 | .. comment: 51 | 52 | I want to put these somewhere sometime, but no place for them now... 53 | Python Paste -- 50% tastier than Elmer's! 54 | Paste: making the web sticky. 55 | Fix broken websites by applying Paste liberally. 56 | Paste: paper over your inconsistencies. 57 | Paste: a soft mixture of malleable consistency. 58 | Paste: a tasty mixture to be spread on bread or crackers. 59 | Paste: glue that won't hurt you if you eat it. 60 | Python Paste: the web extruded into the form of a snake. 61 | Paste: the vinegar eel. 62 | Paste: you bring the cut. 63 | Paste: a doughy substance from which to make metaphorical web cupcakes. 64 | LAMP? LAMPP! 65 | Putting the P in Wep 2.0! 66 | Frankenweb crush tiny humans! 67 | DSL? DSF! 68 | Paste: Comfort for the framework-scarred 69 | 70 | Other Components 71 | ================ 72 | 73 | * `Paste Deploy <./deploy/>`_ 74 | 75 | * `Paste Script <./script/>`_ 76 | 77 | * `WebOb `_ 78 | 79 | * `WSGI specification (PEP 333) `_ 80 | 81 | License 82 | ======= 83 | 84 | Paste is distributed under the `MIT license 85 | `_. 86 | -------------------------------------------------------------------------------- /docs/license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2007 Ian Bicking and Contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /docs/modules/auth.auth_tkt.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.auth.auth_tkt` -- auth_tkt cookie parsing 2 | ===================================================== 3 | 4 | .. automodule:: paste.auth.auth_tkt 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: AuthTKTMiddleware 10 | .. autofunction:: make_auth_tkt_middleware 11 | .. autoclass:: AuthTicket 12 | .. autoexception:: BadTicket 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/modules/auth.basic.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.auth.basic` -- Basic HTTP authentication 2 | ==================================================== 3 | 4 | .. automodule:: paste.auth.basic 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: AuthBasicAuthenticator 10 | .. autoclass:: AuthBasicHandler 11 | .. autofunction:: make_basic 12 | -------------------------------------------------------------------------------- /docs/modules/auth.cas.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.auth.cas` -- CAS authentication 2 | =========================================== 3 | 4 | .. automodule:: paste.auth.cas 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: AuthCASHandler 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/modules/auth.cookie.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.auth.cookie` -- Cookie-based authentication 2 | ======================================================= 3 | 4 | .. automodule:: paste.auth.cookie 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: AuthCookieSigner 10 | .. autoclass:: AuthCookieHandler 11 | .. autoclass:: AuthCookieEnviron 12 | .. autofunction:: make_auth_cookie 13 | -------------------------------------------------------------------------------- /docs/modules/auth.digest.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.auth.digest` -- HTTP Digest login 2 | ============================================= 3 | 4 | .. automodule:: paste.auth.digest 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: AuthDigestAuthenticator 10 | .. autoclass:: AuthDigestHandler 11 | .. autofunction:: digest_password 12 | .. autofunction:: make_digest 13 | -------------------------------------------------------------------------------- /docs/modules/auth.form.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.auth.form` -- HTML form/cookie authentication 2 | ========================================================= 3 | 4 | .. automodule:: paste.auth.form 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: AuthFormHandler 10 | .. autofunction:: make_form 11 | -------------------------------------------------------------------------------- /docs/modules/auth.grantip.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.auth.grantip` -- Set user and groups based on IP address 2 | ==================================================================== 3 | 4 | .. automodule:: paste.auth.grantip 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: GrantIPMiddleware 10 | .. autofunction:: make_grantip 11 | -------------------------------------------------------------------------------- /docs/modules/auth.multi.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.auth.multi` -- Authentication via one of multiple methods 2 | ===================================================================== 3 | 4 | .. automodule:: paste.auth.multi 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: MultiHandler 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/modules/cascade.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.cascade` -- send requests to multiple applications until success 2 | ============================================================================ 3 | 4 | .. automodule:: paste.cascade 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: Cascade 10 | .. autofunction:: make_cascade 11 | -------------------------------------------------------------------------------- /docs/modules/cgiapp.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.cgiapp` -- run CGI scripts as WSGI applications 2 | =========================================================== 3 | 4 | .. automodule:: paste.cgiapp 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: CGIApplication 10 | .. autoexception:: CGIError 11 | .. autofunction:: make_cgi_application 12 | -------------------------------------------------------------------------------- /docs/modules/cgitb_catcher.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.cgitb_catcher` -- catch exceptions using cgitb 2 | ========================================================== 3 | 4 | .. automodule:: paste.cgitb_catcher 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: CgitbMiddleware 10 | .. autofunction:: make_cgitb_middleware 11 | -------------------------------------------------------------------------------- /docs/modules/debug.debugapp.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.debug.debugapp` -- debug app 2 | ======================================== 3 | 4 | .. automodule:: paste.debug.debugapp 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: SimpleApplication 10 | .. autoclass:: SlowConsumer 11 | .. autofunction:: make_test_app 12 | .. autofunction:: make_slow_app 13 | 14 | -------------------------------------------------------------------------------- /docs/modules/debug.fsdiff.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.debug.fsdiff` -- Show differences between directories 2 | ================================================================= 3 | 4 | .. automodule:: paste.debug.fsdiff 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: Diff 10 | .. autoclass:: Snapshot 11 | .. autoclass:: File 12 | .. autoclass:: Dir 13 | .. autofunction:: report_expected_diffs 14 | .. autofunction:: show_diff 15 | 16 | -------------------------------------------------------------------------------- /docs/modules/debug.prints.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.debug.prints` -- capture print output 2 | ================================================= 3 | 4 | .. automodule:: paste.debug.prints 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: PrintDebugMiddleware 10 | 11 | -------------------------------------------------------------------------------- /docs/modules/debug.profile.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.debug.profile` -- profile applications and requests 2 | =============================================================== 3 | 4 | .. automodule:: paste.debug.profile 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: ProfileMiddleware 10 | .. autofunction:: make_profile_middleware 11 | .. autofunction:: profile_decorator 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/modules/debug.watchthreads.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.debug.watchthreads` -- watch thread workers in paste.httpserver 2 | =========================================================================== 3 | 4 | .. automodule:: paste.debug.watchthreads 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: WatchThreads 10 | .. autofunction:: make_watch_threads 11 | .. autofunction:: make_bad_app 12 | 13 | -------------------------------------------------------------------------------- /docs/modules/debug.wdg_validate.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.debug.debugapp` -- debug app 2 | ======================================== 3 | 4 | .. automodule:: paste.debug.wdg_validate 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: WDGValidateMiddleware 10 | .. autofunction:: make_wdg_validate_middleware 11 | 12 | -------------------------------------------------------------------------------- /docs/modules/errordocument.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.errordocument` -- Do internal redirects for error responses 2 | ======================================================================= 3 | 4 | .. automodule:: paste.errordocument 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: StatusBasedForward 10 | .. autofunction:: make_errordocument 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/modules/evalexception.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.evalexception` -- Interactive debugging for errors 2 | ============================================================== 3 | 4 | .. automodule:: paste.evalexception 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: EvalException 10 | -------------------------------------------------------------------------------- /docs/modules/exceptions.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.exceptions` -- Catch, display, and notify for exceptions 2 | ==================================================================== 3 | 4 | .. automodule:: paste.exceptions.errormiddleware 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: ErrorMiddleware 10 | .. autofunction:: handle_exception 11 | .. autofunction:: make_error_middleware 12 | 13 | :mod:`paste.exceptions.collector` -- Collection information from exceptions 14 | =========================================================================== 15 | 16 | .. automodule:: paste.exceptions.collector 17 | 18 | Module Contents 19 | --------------- 20 | 21 | .. autoclass:: ExceptionCollector 22 | .. autofunction:: collect_exception 23 | 24 | :mod:`paste.exceptions.formatter` -- Format exception information 25 | ================================================================= 26 | 27 | .. automodule:: paste.exceptions.formatter 28 | 29 | Module Contents 30 | --------------- 31 | 32 | .. autoclass:: TextFormatter 33 | .. autoclass:: HTMLFormatter 34 | .. autofunction:: format_html 35 | .. autofunction:: format_text 36 | 37 | :mod:`paste.exceptions.reporter` -- Report exceptions 38 | ===================================================== 39 | 40 | .. automodule:: paste.exceptions.reporter 41 | 42 | Module Contents 43 | --------------- 44 | 45 | .. autoclass:: EmailReporter 46 | .. autoclass:: LogReporter 47 | .. autoclass:: FileReporter 48 | .. autoclass:: WSGIAppReporter 49 | -------------------------------------------------------------------------------- /docs/modules/fileapp.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.fileapp` -- Serve files 2 | =================================== 3 | 4 | .. automodule:: paste.fileapp 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: FileApp 10 | .. autoclass:: DirectoryApp 11 | .. autofunction:: DataApp 12 | .. autofunction:: ArchiveStore 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/modules/fixture.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.fixture` -- Test applications 2 | ========================================= 3 | 4 | .. contents:: 5 | 6 | .. automodule:: paste.fixture 7 | 8 | Module Contents 9 | --------------- 10 | 11 | .. autoclass:: TestApp 12 | :members: 13 | .. autoclass:: TestRequest 14 | 15 | Forms 16 | ----- 17 | 18 | .. autoclass:: Form 19 | :members: 20 | .. autoclass:: Field 21 | :members: 22 | .. autoclass:: Select 23 | .. autoclass:: Radio 24 | .. autoclass:: Checkbox 25 | .. autoclass:: Text 26 | .. autoclass:: Textarea 27 | .. autoclass:: Hidden 28 | .. autoclass:: Submit 29 | 30 | Script Testing 31 | -------------- 32 | 33 | .. autoclass:: TestFileEnvironment 34 | :members: 35 | .. autoclass:: ProcResult 36 | :members: 37 | .. autoclass:: FoundFile 38 | .. autoclass:: FoundDir 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/modules/gzipper.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.gzipper` -- Gzip-compress responses 2 | =============================================== 3 | 4 | .. automodule:: paste.gzipper 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: middleware 10 | .. autofunction:: make_gzip_middleware 11 | -------------------------------------------------------------------------------- /docs/modules/httpexceptions.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.httpexceptions` -- Easily product HTTP errors 2 | ========================================================= 3 | 4 | .. automodule:: paste.httpexceptions 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: HTTPExceptionHandler 10 | .. autofunction:: make_middleware 11 | 12 | Exceptions 13 | ---------- 14 | 15 | .. autoexception:: HTTPException 16 | .. autoexception:: HTTPError 17 | .. autoexception:: HTTPRedirection 18 | .. autoexception:: HTTPMultipleChoices 19 | .. autoexception:: HTTPMovedPermanently 20 | .. autoexception:: HTTPFound 21 | .. autoexception:: HTTPNotModified 22 | .. autoexception:: HTTPUseProxy 23 | .. autoexception:: HTTPTemporaryRedirect 24 | .. autoexception:: HTTPClientError 25 | .. autoexception:: HTTPBadRequest 26 | .. autoexception:: HTTPUnauthorized 27 | .. autoexception:: HTTPPaymentRequired 28 | .. autoexception:: HTTPForbidden 29 | .. autoexception:: HTTPNotFound 30 | .. autoexception:: HTTPMethodNotAllowed 31 | .. autoexception:: HTTPNotAcceptable 32 | .. autoexception:: HTTPProxyAuthenticationRequired 33 | .. autoexception:: HTTPRequestTimeout 34 | .. autoexception:: HTTPConflict 35 | .. autoexception:: HTTPGone 36 | .. autoexception:: HTTPLengthRequired 37 | .. autoexception:: HTTPPreconditionFailed 38 | .. autoexception:: HTTPRequestEntityTooLarge 39 | .. autoexception:: HTTPRequestURITooLong 40 | .. autoexception:: HTTPUnsupportedMediaType 41 | .. autoexception:: HTTPRequestRangeNotSatisfiable 42 | .. autoexception:: HTTPExpectationFailed 43 | .. autoexception:: HTTPServerError 44 | .. autoexception:: HTTPInternalServerError 45 | .. autoexception:: HTTPNotImplemented 46 | .. autoexception:: HTTPBadGateway 47 | .. autoexception:: HTTPServiceUnavailable 48 | .. autoexception:: HTTPGatewayTimeout 49 | .. autoexception:: HTTPVersionNotSupported 50 | -------------------------------------------------------------------------------- /docs/modules/httpheaders.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.httpheaders` -- Manipulate HTTP Headers 2 | =================================================== 3 | 4 | .. comment: 5 | I just don't feel like documenting the items... 6 | 7 | .. automodule:: paste.httpheaders 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/modules/httpserver.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.httpserver` -- HTTP server 2 | ====================================== 3 | 4 | .. automodule:: paste.httpserver 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autofunction:: serve 10 | .. autofunction:: server_runner 11 | -------------------------------------------------------------------------------- /docs/modules/lint.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.lint` -- Check for the validity of WSGI requests and responses 2 | ========================================================================== 3 | 4 | .. automodule:: paste.lint 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autofunction:: middleware 10 | .. autoexception:: WSGIWarning 11 | -------------------------------------------------------------------------------- /docs/modules/pony.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.pony` -- Add pony power to your application 2 | ======================================================= 3 | 4 | .. automodule:: paste.pony 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: PonyMiddleware 10 | .. autofunction:: make_pony 11 | -------------------------------------------------------------------------------- /docs/modules/progress.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.progress` -- Track progress of uploads 2 | ================================================== 3 | 4 | .. automodule:: paste.progress 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: UploadProgressMonitor 10 | .. autoclass:: UploadProgressReporter 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/modules/proxy.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.proxy` -- Proxy WSGI requests to HTTP requests 2 | ========================================================== 3 | 4 | .. automodule:: paste.proxy 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: Proxy 10 | .. autofunction:: make_proxy 11 | .. autoclass:: TransparentProxy 12 | .. autofunction:: make_transparent_proxy 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/modules/recursive.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.recursive` -- internal requests 2 | =========================================== 3 | 4 | .. automodule:: paste.recursive 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: RecursiveMiddleware 10 | .. autofunction:: ForwardRequestException 11 | -------------------------------------------------------------------------------- /docs/modules/registry.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.registry` -- Manage thread-local request-specific objects 2 | ===================================================================== 3 | 4 | .. automodule:: paste.registry 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: StackedObjectProxy 10 | .. autoclass:: Registry 11 | .. autoclass:: RegistryManager 12 | .. autoclass:: StackedObjectRestorer 13 | .. autofunction:: make_registry_manager 14 | -------------------------------------------------------------------------------- /docs/modules/reloader.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.reloader` -- Monitor for file changes to restart the process 2 | ======================================================================== 3 | 4 | .. automodule:: paste.reloader 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autofunction:: install 10 | .. autoclass:: Monitor 11 | .. autofunction:: watch_file 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/modules/request.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.request` -- Utility functions for the WSGI environment 2 | ================================================================== 3 | 4 | .. automodule:: paste.request 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autofunction:: get_cookies 10 | .. autofunction:: get_cookie_dict 11 | .. autofunction:: parse_querystring 12 | .. autofunction:: parse_formvars 13 | .. autofunction:: construct_url 14 | .. autofunction:: path_info_split 15 | .. autofunction:: path_info_pop 16 | .. autofunction:: resolve_relative_url 17 | .. autoclass:: EnvironHeaders 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/modules/response.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.response` -- Utility functions for producing responses 2 | ================================================================== 3 | 4 | .. automodule:: paste.response 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: HeaderDict 10 | .. autofunction:: has_header 11 | .. autofunction:: header_value 12 | .. autofunction:: remove_header 13 | .. autofunction:: replace_header 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/modules/session.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.session` -- Simple file-based sessions 2 | ================================================== 3 | 4 | .. automodule:: paste.session 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: SessionMiddleware 10 | .. autofunction:: make_session_middleware 11 | 12 | -------------------------------------------------------------------------------- /docs/modules/transaction.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.transaction` -- DB-API transactions 2 | =============================================== 3 | 4 | .. automodule:: paste.transaction 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: TransactionManagerMiddleware 10 | .. autoclass:: ConnectionFactory 11 | .. autofunction:: BasicTransactionHandler 12 | -------------------------------------------------------------------------------- /docs/modules/translogger.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.translogger` -- Log requests 2 | ======================================== 3 | 4 | .. automodule:: paste.translogger 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: TransLogger 10 | .. autofunction:: make_filter 11 | -------------------------------------------------------------------------------- /docs/modules/url.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.url` -- URL convenience class 2 | ========================================= 3 | 4 | .. automodule:: paste.url 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: URL 10 | .. autoclass:: Image 11 | -------------------------------------------------------------------------------- /docs/modules/urlmap.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.urlmap` -- Map URL paths 2 | ==================================== 3 | 4 | .. automodule:: paste.urlmap 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: URLMap 10 | .. autofunction:: urlmap_factory 11 | .. autoclass:: PathProxyURLMap 12 | -------------------------------------------------------------------------------- /docs/modules/urlparser.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.urlparser` -- Handle URL paths and server static files 2 | ================================================================== 3 | 4 | .. automodule:: paste.urlparser 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: StaticURLParser 10 | .. autofunction:: make_static 11 | .. autoclass:: PkgResourcesParser 12 | .. autofunction:: make_pkg_resources 13 | .. autoclass:: URLParser 14 | .. autofunction:: make_url_parser 15 | -------------------------------------------------------------------------------- /docs/modules/util.import_string.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.util.import_string` -- Import objects from strings 2 | ============================================================== 3 | 4 | .. automodule:: paste.util.import_string 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autofunction:: eval_import 10 | .. autofunction:: simple_import 11 | .. autofunction:: import_module 12 | .. autofunction:: try_import_module 13 | -------------------------------------------------------------------------------- /docs/modules/util.multidict.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.util.multidict` -- Dictionaries with multiple values 2 | ================================================================ 3 | 4 | .. automodule:: paste.util.multidict 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: MultiDict 10 | .. autoclass:: UnicodeMultiDict 11 | 12 | -------------------------------------------------------------------------------- /docs/modules/wsgilib.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.wsgilib` -- Miscellaneous WSGI utility functions 2 | ============================================================ 3 | 4 | .. automodule:: paste.wsgilib 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autofunction:: add_close 10 | .. autofunction:: add_start_close 11 | .. autofunction:: chained_app_iters 12 | .. autoclass:: encode_unicode_app_iter 13 | .. autofunction:: catch_errors 14 | .. autofunction:: catch_errors_app 15 | .. autofunction:: raw_interactive 16 | .. autofunction:: interactive 17 | .. autofunction:: dump_environ 18 | .. autofunction:: intercept_output 19 | -------------------------------------------------------------------------------- /docs/modules/wsgiwrappers.txt: -------------------------------------------------------------------------------- 1 | :mod:`paste.wsgiwrappers` -- Wrapper functions for WSGI request and response 2 | ============================================================================ 3 | 4 | .. automodule:: paste.wsgiwrappers 5 | 6 | Module Contents 7 | --------------- 8 | 9 | .. autoclass:: WSGIRequest 10 | .. autoclass:: WSGIResponse 11 | -------------------------------------------------------------------------------- /docs/test_server.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | error_email = ianb@colorstudy.com 3 | 4 | [app:main] 5 | use = egg:PasteScript#test 6 | 7 | [server:main] 8 | use = egg:Paste#http 9 | host = 127.0.0.1:8081 10 | # These options make it easier to trigger the thread pool catches 11 | # (i.e., threads are hung fast, killed fast, spawn fast, and the 12 | # whole process dies quickly due to zombies) 13 | threadpool_workers = 3 14 | threadpool_hung_thread_limit = 10 15 | threadpool_kill_thread_limit = 20 16 | threadpool_spawn_if_under = 2 17 | threadpool_max_zombie_threads_before_die = 2 18 | threadpool_hung_check_period = 1 19 | threadpool_dying_limit = 10 20 | 21 | [server:cherrypy] 22 | use = egg:PasteScript#cherrypy 23 | host = 127.0.0.1:8080 24 | 25 | [filter-app:watch_threads] 26 | use = egg:Paste#error_catcher 27 | debug = true 28 | next = watch_threads_inner 29 | 30 | [app:watch_threads_inner] 31 | use = egg:Paste#urlmap 32 | /bad = bad_app 33 | / = watch_app 34 | 35 | [app:watch_app] 36 | use = egg:Paste#watch_threads 37 | allow_kill = true 38 | 39 | [app:bad_app] 40 | paste.app_factory = paste.debug.watchthreads:make_bad_app 41 | 42 | 43 | -------------------------------------------------------------------------------- /docs/web/site.js: -------------------------------------------------------------------------------- 1 | function setup_dropdowns() { 2 | var els = document.getElementsByTagName('UL'); 3 | for (var i = 0; i < els.length; i++) { 4 | var el = els[i]; 5 | if (el.className.search(/\bcontents\b/) > -1) { 6 | enable_dropdown(el); 7 | } 8 | } 9 | } 10 | 11 | function enable_dropdown(el) { 12 | var title = el.getElementsByTagName('LI')[0]; 13 | var plus_minus = document.createTextNode(' [-]'); 14 | if (title.childNodes[0].tagName != 'A') { 15 | anchor = document.createElement('A'); 16 | while (title.childNodes.length) { 17 | anchor.appendChild(title.childNodes[0]); 18 | } 19 | anchor.setAttribute('href', '#'); 20 | anchor.style.padding = '1px'; 21 | title.appendChild(anchor); 22 | } else { 23 | anchor = title.childNodes[0]; 24 | } 25 | anchor.appendChild(plus_minus); 26 | function show_hide() { 27 | if (el.sub_hidden) { 28 | set_sub_li(el, ''); 29 | anchor.removeChild(plus_minus); 30 | plus_minus = document.createTextNode(' [-]'); 31 | anchor.appendChild(plus_minus); 32 | } else { 33 | set_sub_li(el, 'none'); 34 | anchor.removeChild(plus_minus); 35 | plus_minus = document.createTextNode(' [+]'); 36 | anchor.appendChild(plus_minus); 37 | } 38 | el.sub_hidden = ! el.sub_hidden; 39 | return false; 40 | } 41 | anchor.onclick = show_hide; 42 | show_hide(); 43 | } 44 | 45 | function set_sub_li(list, display) { 46 | var sub = list.getElementsByTagName('LI'); 47 | for (var i = 1; i < sub.length; i++) { 48 | sub[i].style.display = display; 49 | } 50 | } 51 | 52 | function add_onload(func) { 53 | if (window.onload) { 54 | var old_onload = window.onload; 55 | function new_onload() { 56 | old_onload(); 57 | func(); 58 | } 59 | window.onload = new_onload; 60 | } else { 61 | window.onload = func; 62 | } 63 | } 64 | 65 | add_onload(setup_dropdowns); 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /docs/web/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Helvetica,Arial,sans-serif; 3 | margin: 0; 4 | background-color: #fff; 5 | color: #000; 6 | } 7 | 8 | i, em { 9 | font-family: Times New Roman,Times,serif; 10 | } 11 | 12 | a:link { 13 | color: #730; 14 | } 15 | 16 | a:visited { 17 | color: #402; 18 | } 19 | 20 | a:hover { 21 | background-color: #fd8; 22 | } 23 | 24 | div#header { 25 | display: block; 26 | background-color: #930; 27 | color: #fd6; 28 | border-bottom: 3px solid #f70; 29 | padding: 3px; 30 | font-size: 30px; 31 | } 32 | 33 | div#header h1 { 34 | padding: 0; 35 | margin: 0; 36 | font-size: 1.5em; 37 | } 38 | 39 | div#nav { 40 | float: left; 41 | background-color: #fd9; 42 | border: 1px solid #f70; 43 | margin-right: 1em; 44 | border-bottom: 1px solid #f70; 45 | width: 200px; 46 | } 47 | 48 | div#nav ul { 49 | padding: 0; 50 | margin: 0; 51 | } 52 | 53 | div#nav li { 54 | list-style: none; 55 | margin: 0; 56 | } 57 | 58 | div#nav ul li ul li a { 59 | padding: 2px 2em 2px 2em; 60 | font-weight: normal; 61 | } 62 | 63 | div#nav a { 64 | display: block; 65 | padding: 2px 1em 2px 1em; 66 | text-decoration: none; 67 | color: #400; 68 | font-weight: bold; 69 | } 70 | 71 | div#nav a:hover { 72 | background-color: #f80; 73 | color: #fff; 74 | } 75 | 76 | /* If I ever do menus that show the "current" page, that would be 77 | by marking some "links" as selected */ 78 | div#nav span.selected { 79 | display: block; 80 | font-weight: bold; 81 | padding: 2px 1em 2px 1em; 82 | } 83 | 84 | div#body { 85 | padding: 1em; 86 | } 87 | 88 | h1.page-title { 89 | margin-top: 0; 90 | } -------------------------------------------------------------------------------- /paste/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | import warnings 5 | 6 | try: 7 | with warnings.catch_warnings(): 8 | warnings.simplefilter("ignore", category=DeprecationWarning) 9 | import pkg_resources 10 | pkg_resources.declare_namespace(__name__) 11 | except (AttributeError, ImportError): 12 | # don't prevent use of paste if pkg_resources isn't installed 13 | from pkgutil import extend_path 14 | __path__ = extend_path(__path__, __name__) 15 | 16 | try: 17 | import modulefinder 18 | except ImportError: 19 | pass 20 | else: 21 | for p in __path__: 22 | modulefinder.AddPackagePath(__name__, p) 23 | -------------------------------------------------------------------------------- /paste/auth/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | Package for authentication/identification of requests. 5 | 6 | The objective of this package is to provide single-focused middleware 7 | components that implement a particular specification. Integration of 8 | the components into a usable system is up to a higher-level framework. 9 | """ 10 | -------------------------------------------------------------------------------- /paste/auth/basic.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Clark C. Evans 2 | # This module is part of the Python Paste Project and is released under 3 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 4 | # This code was written with funding by http://prometheusresearch.com 5 | """ 6 | Basic HTTP/1.0 Authentication 7 | 8 | This module implements ``Basic`` authentication as described in 9 | HTTP/1.0 specification [1]_ . Do not use this module unless you 10 | are using SSL or need to work with very out-dated clients, instead 11 | use ``digest`` authentication. 12 | 13 | >>> from paste.wsgilib import dump_environ 14 | >>> from paste.httpserver import serve 15 | >>> # from paste.auth.basic import AuthBasicHandler 16 | >>> realm = 'Test Realm' 17 | >>> def authfunc(environ, username, password): 18 | ... return username == password 19 | >>> serve(AuthBasicHandler(dump_environ, realm, authfunc)) 20 | serving on... 21 | 22 | .. [1] http://www.w3.org/Protocols/HTTP/1.0/draft-ietf-http-spec.html#BasicAA 23 | """ 24 | from base64 import b64decode 25 | 26 | from paste.httpexceptions import HTTPUnauthorized 27 | from paste.httpheaders import ( 28 | AUTHORIZATION, 29 | AUTH_TYPE, 30 | REMOTE_USER, 31 | WWW_AUTHENTICATE, 32 | ) 33 | 34 | class AuthBasicAuthenticator: 35 | """ 36 | implements ``Basic`` authentication details 37 | """ 38 | type = 'basic' 39 | def __init__(self, realm, authfunc): 40 | self.realm = realm 41 | self.authfunc = authfunc 42 | 43 | def build_authentication(self): 44 | head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm) 45 | return HTTPUnauthorized(headers=head) 46 | 47 | def authenticate(self, environ): 48 | authorization = AUTHORIZATION(environ) 49 | if not authorization: 50 | return self.build_authentication() 51 | (authmeth, auth) = authorization.split(' ', 1) 52 | if 'basic' != authmeth.lower(): 53 | return self.build_authentication() 54 | auth = b64decode(auth.strip().encode('ascii')).decode('ascii') 55 | username, password = auth.split(':', 1) 56 | if self.authfunc(environ, username, password): 57 | return username 58 | return self.build_authentication() 59 | 60 | __call__ = authenticate 61 | 62 | class AuthBasicHandler: 63 | """ 64 | HTTP/1.0 ``Basic`` authentication middleware 65 | 66 | Parameters: 67 | 68 | ``application`` 69 | 70 | The application object is called only upon successful 71 | authentication, and can assume ``environ['REMOTE_USER']`` 72 | is set. If the ``REMOTE_USER`` is already set, this 73 | middleware is simply pass-through. 74 | 75 | ``realm`` 76 | 77 | This is a identifier for the authority that is requesting 78 | authorization. It is shown to the user and should be unique 79 | within the domain it is being used. 80 | 81 | ``authfunc`` 82 | 83 | This is a mandatory user-defined function which takes a 84 | ``environ``, ``username`` and ``password`` for its first 85 | three arguments. It should return ``True`` if the user is 86 | authenticated. 87 | 88 | """ 89 | def __init__(self, application, realm, authfunc): 90 | self.application = application 91 | self.authenticate = AuthBasicAuthenticator(realm, authfunc) 92 | 93 | def __call__(self, environ, start_response): 94 | username = REMOTE_USER(environ) 95 | if not username: 96 | result = self.authenticate(environ) 97 | if isinstance(result, str): 98 | AUTH_TYPE.update(environ, 'basic') 99 | REMOTE_USER.update(environ, result) 100 | else: 101 | return result.wsgi_application(environ, start_response) 102 | return self.application(environ, start_response) 103 | 104 | middleware = AuthBasicHandler 105 | 106 | __all__ = ['AuthBasicHandler'] 107 | 108 | def make_basic(app, global_conf, realm, authfunc, **kw): 109 | """ 110 | Grant access via basic authentication 111 | 112 | Config looks like this:: 113 | 114 | [filter:grant] 115 | use = egg:Paste#auth_basic 116 | realm=myrealm 117 | authfunc=somepackage.somemodule:somefunction 118 | 119 | """ 120 | from paste.util.import_string import eval_import 121 | import types 122 | authfunc = eval_import(authfunc) 123 | assert isinstance(authfunc, types.FunctionType), "authfunc must resolve to a function" 124 | return AuthBasicHandler(app, realm, authfunc) 125 | 126 | 127 | if "__main__" == __name__: 128 | import doctest 129 | doctest.testmod(optionflags=doctest.ELLIPSIS) 130 | -------------------------------------------------------------------------------- /paste/auth/cas.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Clark C. Evans 2 | # This module is part of the Python Paste Project and is released under 3 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 4 | # This code was written with funding by http://prometheusresearch.com 5 | """ 6 | CAS 1.0 Authentication 7 | 8 | The Central Authentication System is a straight-forward single sign-on 9 | mechanism developed by Yale University's ITS department. It has since 10 | enjoyed widespread success and is deployed at many major universities 11 | and some corporations. 12 | 13 | https://clearinghouse.ja-sig.org/wiki/display/CAS/Home 14 | http://www.yale.edu/tp/auth/usingcasatyale.html 15 | 16 | This implementation has the goal of maintaining current path arguments 17 | passed to the system so that it can be used as middleware at any stage 18 | of processing. It has the secondary goal of allowing for other 19 | authentication methods to be used concurrently. 20 | """ 21 | from urllib.parse import urlencode 22 | from urllib.request import urlopen 23 | from paste.request import construct_url 24 | from paste.httpexceptions import HTTPSeeOther, HTTPForbidden 25 | 26 | class CASLoginFailure(HTTPForbidden): 27 | """ The exception raised if the authority returns 'no' """ 28 | 29 | class CASAuthenticate(HTTPSeeOther): 30 | """ The exception raised to authenticate the user """ 31 | 32 | def AuthCASHandler(application, authority): 33 | """ 34 | middleware to implement CAS 1.0 authentication 35 | 36 | There are several possible outcomes: 37 | 38 | 0. If the REMOTE_USER environment variable is already populated; 39 | then this middleware is a no-op, and the request is passed along 40 | to the application. 41 | 42 | 1. If a query argument 'ticket' is found, then an attempt to 43 | validate said ticket /w the authentication service done. If the 44 | ticket is not validated; an 403 'Forbidden' exception is raised. 45 | Otherwise, the REMOTE_USER variable is set with the NetID that 46 | was validated and AUTH_TYPE is set to "cas". 47 | 48 | 2. Otherwise, a 303 'See Other' is returned to the client directing 49 | them to login using the CAS service. After logon, the service 50 | will send them back to this same URL, only with a 'ticket' query 51 | argument. 52 | 53 | Parameters: 54 | 55 | ``authority`` 56 | 57 | This is a fully-qualified URL to a CAS 1.0 service. The URL 58 | should end with a '/' and have the 'login' and 'validate' 59 | sub-paths as described in the CAS 1.0 documentation. 60 | 61 | """ 62 | assert authority.endswith("/") and authority.startswith("http") 63 | def cas_application(environ, start_response): 64 | username = environ.get('REMOTE_USER','') 65 | if username: 66 | return application(environ, start_response) 67 | qs = environ.get('QUERY_STRING','').split("&") 68 | if qs and qs[-1].startswith("ticket="): 69 | # assume a response from the authority 70 | ticket = qs.pop().split("=", 1)[1] 71 | environ['QUERY_STRING'] = "&".join(qs) 72 | service = construct_url(environ) 73 | args = urlencode( 74 | {'service': service,'ticket': ticket}) 75 | requrl = authority + "validate?" + args 76 | result = urlopen(requrl).read().split("\n") 77 | if 'yes' == result[0]: 78 | environ['REMOTE_USER'] = result[1] 79 | environ['AUTH_TYPE'] = 'cas' 80 | return application(environ, start_response) 81 | exce = CASLoginFailure() 82 | else: 83 | service = construct_url(environ) 84 | args = urlencode({'service': service}) 85 | location = authority + "login?" + args 86 | exce = CASAuthenticate(location) 87 | return exce.wsgi_application(environ, start_response) 88 | return cas_application 89 | 90 | middleware = AuthCASHandler 91 | 92 | __all__ = ['CASLoginFailure', 'CASAuthenticate', 'AuthCASHandler' ] 93 | 94 | if '__main__' == __name__: 95 | authority = "https://secure.its.yale.edu/cas/servlet/" 96 | from paste.wsgilib import dump_environ 97 | from paste.httpserver import serve 98 | from paste.httpexceptions import HTTPExceptionHandler 99 | serve(HTTPExceptionHandler( 100 | AuthCASHandler(dump_environ, authority))) 101 | -------------------------------------------------------------------------------- /paste/auth/grantip.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | Grant roles and logins based on IP address. 5 | """ 6 | from paste.util import ip4 7 | 8 | class GrantIPMiddleware: 9 | 10 | """ 11 | On each request, ``ip_map`` is checked against ``REMOTE_ADDR`` 12 | and logins and roles are assigned based on that. 13 | 14 | ``ip_map`` is a map of {ip_mask: (username, roles)}. Either 15 | ``username`` or ``roles`` may be None. Roles may also be prefixed 16 | with ``-``, like ``'-system'`` meaning that role should be 17 | revoked. ``'__remove__'`` for a username will remove the username. 18 | 19 | If ``clobber_username`` is true (default) then any user 20 | specification will override the current value of ``REMOTE_USER``. 21 | ``'__remove__'`` will always clobber the username. 22 | 23 | ``ip_mask`` is something that `paste.util.ip4:IP4Range 24 | `_ can parse. Simple IP 25 | addresses, IP/mask, ip<->ip ranges, and hostnames are allowed. 26 | """ 27 | 28 | def __init__(self, app, ip_map, clobber_username=True): 29 | self.app = app 30 | self.ip_map = [] 31 | for key, value in ip_map.items(): 32 | self.ip_map.append((ip4.IP4Range(key), 33 | self._convert_user_role(value[0], value[1]))) 34 | self.clobber_username = clobber_username 35 | 36 | def _convert_user_role(self, username, roles): 37 | if roles and isinstance(roles, str): 38 | roles = roles.split(',') 39 | return (username, roles) 40 | 41 | def __call__(self, environ, start_response): 42 | addr = ip4.ip2int(environ['REMOTE_ADDR'], False) 43 | remove_user = False 44 | add_roles = [] 45 | for range, (username, roles) in self.ip_map: 46 | if addr in range: 47 | if roles: 48 | add_roles.extend(roles) 49 | if username == '__remove__': 50 | remove_user = True 51 | elif username: 52 | if (not environ.get('REMOTE_USER') 53 | or self.clobber_username): 54 | environ['REMOTE_USER'] = username 55 | if (remove_user and 'REMOTE_USER' in environ): 56 | del environ['REMOTE_USER'] 57 | if roles: 58 | self._set_roles(environ, add_roles) 59 | return self.app(environ, start_response) 60 | 61 | def _set_roles(self, environ, roles): 62 | cur_roles = environ.get('REMOTE_USER_TOKENS', '').split(',') 63 | # Get rid of empty roles: 64 | cur_roles = list(filter(None, cur_roles)) 65 | remove_roles = [] 66 | for role in roles: 67 | if role.startswith('-'): 68 | remove_roles.append(role[1:]) 69 | else: 70 | if role not in cur_roles: 71 | cur_roles.append(role) 72 | for role in remove_roles: 73 | if role in cur_roles: 74 | cur_roles.remove(role) 75 | environ['REMOTE_USER_TOKENS'] = ','.join(cur_roles) 76 | 77 | 78 | def make_grantip(app, global_conf, clobber_username=False, **kw): 79 | """ 80 | Grant roles or usernames based on IP addresses. 81 | 82 | Config looks like this:: 83 | 84 | [filter:grant] 85 | use = egg:Paste#grantip 86 | clobber_username = true 87 | # Give localhost system role (no username): 88 | 127.0.0.1 = -:system 89 | # Give everyone in 192.168.0.* editor role: 90 | 192.168.0.0/24 = -:editor 91 | # Give one IP the username joe: 92 | 192.168.0.7 = joe 93 | # And one IP is should not be logged in: 94 | 192.168.0.10 = __remove__:-editor 95 | 96 | """ 97 | from paste.deploy.converters import asbool 98 | clobber_username = asbool(clobber_username) 99 | ip_map = {} 100 | for key, value in kw.items(): 101 | if ':' in value: 102 | username, role = value.split(':', 1) 103 | else: 104 | username = value 105 | role = '' 106 | if username == '-': 107 | username = '' 108 | if role == '-': 109 | role = '' 110 | ip_map[key] = value 111 | return GrantIPMiddleware(app, ip_map, clobber_username) 112 | 113 | 114 | -------------------------------------------------------------------------------- /paste/auth/multi.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Clark C. Evans 2 | # This module is part of the Python Paste Project and is released under 3 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 4 | # This code was written with funding by http://prometheusresearch.com 5 | """ 6 | Authentication via Multiple Methods 7 | 8 | In some environments, the choice of authentication method to be used 9 | depends upon the environment and is not "fixed". This middleware allows 10 | N authentication methods to be registered along with a goodness function 11 | which determines which method should be used. The following example 12 | demonstrates how to use both form and digest authentication in a server 13 | stack; by default it uses form-based authentication unless 14 | ``*authmeth=digest`` is specified as a query argument. 15 | 16 | >>> from paste.auth import form, cookie, digest, multi 17 | >>> from paste.wsgilib import dump_environ 18 | >>> from paste.httpserver import serve 19 | >>> 20 | >>> multi = multi.MultiHandler(dump_environ) 21 | >>> def authfunc(environ, realm, user): 22 | ... return digest.digest_password(realm, user, user) 23 | >>> multi.add_method('digest', digest.middleware, "Test Realm", authfunc) 24 | >>> multi.set_query_argument('digest') 25 | >>> 26 | >>> def authfunc(environ, username, password): 27 | ... return username == password 28 | >>> multi.add_method('form', form.middleware, authfunc) 29 | >>> multi.set_default('form') 30 | >>> serve(cookie.middleware(multi)) 31 | serving on... 32 | 33 | """ 34 | 35 | class MultiHandler: 36 | """ 37 | Multiple Authentication Handler 38 | 39 | This middleware provides two othogonal facilities: 40 | 41 | - a manner to register any number of authentication middlewares 42 | 43 | - a mechanism to register predicates which cause one of the 44 | registered middlewares to be used depending upon the request 45 | 46 | If none of the predicates returns True, then the application is 47 | invoked directly without middleware 48 | """ 49 | def __init__(self, application): 50 | self.application = application 51 | self.default = application 52 | self.binding = {} 53 | self.predicate = [] 54 | def add_method(self, name, factory, *args, **kwargs): 55 | self.binding[name] = factory(self.application, *args, **kwargs) 56 | def add_predicate(self, name, checker): 57 | self.predicate.append((checker, self.binding[name])) 58 | def set_default(self, name): 59 | """ set default authentication method """ 60 | self.default = self.binding[name] 61 | def set_query_argument(self, name, key = '*authmeth', value = None): 62 | """ choose authentication method based on a query argument """ 63 | lookfor = "%s=%s" % (key, value or name) 64 | self.add_predicate(name, 65 | lambda environ: lookfor in environ.get('QUERY_STRING','')) 66 | def __call__(self, environ, start_response): 67 | for (checker, binding) in self.predicate: 68 | if checker(environ): 69 | return binding(environ, start_response) 70 | return self.default(environ, start_response) 71 | 72 | middleware = MultiHandler 73 | 74 | __all__ = ['MultiHandler'] 75 | 76 | if "__main__" == __name__: 77 | import doctest 78 | doctest.testmod(optionflags=doctest.ELLIPSIS) 79 | 80 | -------------------------------------------------------------------------------- /paste/cgitb_catcher.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | """ 5 | WSGI middleware 6 | 7 | Captures any exceptions and prints a pretty report. 8 | """ 9 | 10 | from io import StringIO 11 | import sys 12 | 13 | from paste.util import converters, NO_DEFAULT 14 | from paste.util.cgitb_hook import Hook 15 | 16 | 17 | class CgitbMiddleware: 18 | 19 | def __init__(self, app, 20 | global_conf=None, 21 | display=NO_DEFAULT, 22 | logdir=None, 23 | context=5, 24 | format="html"): 25 | self.app = app 26 | if global_conf is None: 27 | global_conf = {} 28 | if display is NO_DEFAULT: 29 | display = global_conf.get('debug') 30 | if isinstance(display, str): 31 | display = converters.asbool(display) 32 | self.display = display 33 | self.logdir = logdir 34 | self.context = int(context) 35 | self.format = format 36 | 37 | def __call__(self, environ, start_response): 38 | try: 39 | app_iter = self.app(environ, start_response) 40 | return self.catching_iter(app_iter, environ) 41 | except Exception: 42 | exc_info = sys.exc_info() 43 | start_response('500 Internal Server Error', 44 | [('content-type', 'text/html')], 45 | exc_info) 46 | response = self.exception_handler(exc_info, environ) 47 | response = response.encode('utf8') 48 | return [response] 49 | 50 | def catching_iter(self, app_iter, environ): 51 | if not app_iter: 52 | return 53 | error_on_close = False 54 | try: 55 | for v in app_iter: 56 | yield v 57 | if hasattr(app_iter, 'close'): 58 | error_on_close = True 59 | app_iter.close() 60 | except Exception: 61 | response = self.exception_handler(sys.exc_info(), environ) 62 | if not error_on_close and hasattr(app_iter, 'close'): 63 | try: 64 | app_iter.close() 65 | except Exception: 66 | close_response = self.exception_handler( 67 | sys.exc_info(), environ) 68 | response += ( 69 | '
Error in .close():
%s' 70 | % close_response) 71 | response = response.encode('utf8') 72 | yield response 73 | 74 | def exception_handler(self, exc_info, environ): 75 | dummy_file = StringIO() 76 | hook = Hook( 77 | file=dummy_file, 78 | display=self.display, 79 | logdir=self.logdir, 80 | context=self.context, 81 | format=self.format) 82 | hook(*exc_info) 83 | return dummy_file.getvalue() 84 | 85 | 86 | def make_cgitb_middleware(app, global_conf, 87 | display=NO_DEFAULT, 88 | logdir=None, 89 | context=5, 90 | format='html'): 91 | """ 92 | Wraps the application in an error catcher based on the 93 | former ``cgitb`` module in the standard library. 94 | 95 | display: 96 | If true (or debug is set in the global configuration) 97 | then the traceback will be displayed in the browser 98 | 99 | logdir: 100 | Writes logs of all errors in that directory 101 | 102 | context: 103 | Number of lines of context to show around each line of 104 | source code 105 | """ 106 | from paste.deploy.converters import asbool 107 | if display is not NO_DEFAULT: 108 | display = asbool(display) 109 | if 'debug' in global_conf: 110 | global_conf['debug'] = asbool(global_conf['debug']) 111 | return CgitbMiddleware( 112 | app, global_conf=global_conf, 113 | display=display, 114 | logdir=logdir, 115 | context=context, 116 | format=format) 117 | -------------------------------------------------------------------------------- /paste/cowbell/__init__.py: -------------------------------------------------------------------------------- 1 | # Cowbell images: http://commons.wikimedia.org/wiki/Image:Cowbell-1.jpg 2 | import os 3 | import re 4 | from paste.fileapp import FileApp 5 | from paste.response import header_value, remove_header 6 | 7 | SOUND = "http://www.c-eye.net/eyeon/WalkenWAVS/explorestudiospace.wav" 8 | 9 | class MoreCowbell: 10 | def __init__(self, app): 11 | self.app = app 12 | def __call__(self, environ, start_response): 13 | path_info = environ.get('PATH_INFO', '') 14 | script_name = environ.get('SCRIPT_NAME', '') 15 | for filename in ['bell-ascending.png', 'bell-descending.png']: 16 | if path_info == '/.cowbell/'+ filename: 17 | app = FileApp(os.path.join(os.path.dirname(__file__), filename)) 18 | return app(environ, start_response) 19 | type = [] 20 | body = [] 21 | def repl_start_response(status, headers, exc_info=None): 22 | ct = header_value(headers, 'content-type') 23 | if ct and ct.startswith('text/html'): 24 | type.append(ct) 25 | remove_header(headers, 'content-length') 26 | start_response(status, headers, exc_info) 27 | return body.append 28 | return start_response(status, headers, exc_info) 29 | app_iter = self.app(environ, repl_start_response) 30 | if type: 31 | # Got text/html 32 | body.extend(app_iter) 33 | body = ''.join(body) 34 | body = insert_head(body, self.javascript.replace('__SCRIPT_NAME__', script_name)) 35 | body = insert_body(body, self.resources.replace('__SCRIPT_NAME__', script_name)) 36 | return [body] 37 | else: 38 | return app_iter 39 | 40 | javascript = '''\ 41 | 72 | ''' 73 | 74 | resources = '''\ 75 | 78 | 81 | ''' 82 | 83 | def insert_head(body, text): 84 | end_head = re.search(r'', body, re.I) 85 | if end_head: 86 | return body[:end_head.start()] + text + body[end_head.end():] 87 | else: 88 | return text + body 89 | 90 | def insert_body(body, text): 91 | end_body = re.search(r'', body, re.I) 92 | if end_body: 93 | return body[:end_body.start()] + text + body[end_body.end():] 94 | else: 95 | return body + text 96 | 97 | def make_cowbell(global_conf, app): 98 | return MoreCowbell(app) 99 | 100 | if __name__ == '__main__': 101 | from paste.debug.debugapp import SimpleApplication 102 | app = MoreCowbell(SimpleApplication()) 103 | from paste.httpserver import serve 104 | serve(app) 105 | -------------------------------------------------------------------------------- /paste/cowbell/bell-ascending.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pasteorg/paste/28e461548498138b8814b243be432a04a7895dba/paste/cowbell/bell-ascending.png -------------------------------------------------------------------------------- /paste/cowbell/bell-descending.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pasteorg/paste/28e461548498138b8814b243be432a04a7895dba/paste/cowbell/bell-descending.png -------------------------------------------------------------------------------- /paste/debug/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | Package for debugging and development tools 5 | """ 6 | -------------------------------------------------------------------------------- /paste/debug/debugapp.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | # (c) 2005 Clark C. Evans 4 | # This module is part of the Python Paste Project and is released under 5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 6 | # This code was written with funding by http://prometheusresearch.com 7 | """ 8 | Various Applications for Debugging/Testing Purposes 9 | """ 10 | 11 | import time 12 | __all__ = ['SimpleApplication', 'SlowConsumer'] 13 | 14 | 15 | class SimpleApplication: 16 | """ 17 | Produces a simple web page 18 | """ 19 | def __call__(self, environ, start_response): 20 | body = b"simple" 21 | start_response("200 OK", [('Content-Type', 'text/html'), 22 | ('Content-Length', str(len(body)))]) 23 | return [body] 24 | 25 | class SlowConsumer: 26 | """ 27 | Consumes an upload slowly... 28 | 29 | NOTE: This should use the iterator form of ``wsgi.input``, 30 | but it isn't implemented in paste.httpserver. 31 | """ 32 | def __init__(self, chunk_size = 4096, delay = 1, progress = True): 33 | self.chunk_size = chunk_size 34 | self.delay = delay 35 | self.progress = True 36 | 37 | def __call__(self, environ, start_response): 38 | size = 0 39 | total = environ.get('CONTENT_LENGTH') 40 | if total: 41 | remaining = int(total) 42 | while remaining > 0: 43 | if self.progress: 44 | print("%s of %s remaining" % (remaining, total)) 45 | if remaining > 4096: 46 | chunk = environ['wsgi.input'].read(4096) 47 | else: 48 | chunk = environ['wsgi.input'].read(remaining) 49 | if not chunk: 50 | break 51 | size += len(chunk) 52 | remaining -= len(chunk) 53 | if self.delay: 54 | time.sleep(self.delay) 55 | body = "%d bytes" % size 56 | else: 57 | body = (b'\n' 58 | b'
\n' 59 | b'\n' 60 | b'\n' 61 | b'
\n') 62 | print("bingles") 63 | start_response("200 OK", [('Content-Type', 'text/html'), 64 | ('Content-Length', str(len(body)))]) 65 | return [body] 66 | 67 | def make_test_app(global_conf): 68 | return SimpleApplication() 69 | 70 | make_test_app.__doc__ = SimpleApplication.__doc__ 71 | 72 | def make_slow_app(global_conf, chunk_size=4096, delay=1, progress=True): 73 | from paste.deploy.converters import asbool 74 | return SlowConsumer( 75 | chunk_size=int(chunk_size), 76 | delay=int(delay), 77 | progress=asbool(progress)) 78 | 79 | make_slow_app.__doc__ = SlowConsumer.__doc__ 80 | -------------------------------------------------------------------------------- /paste/debug/testserver.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Clark C. Evans 2 | # This module is part of the Python Paste Project and is released under 3 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 4 | # This code was written with funding by http://prometheusresearch.com 5 | """ 6 | WSGI Test Server 7 | 8 | This builds upon paste.util.baseserver to customize it for regressions 9 | where using raw_interactive won't do. 10 | 11 | 12 | """ 13 | import time 14 | from paste.httpserver import WSGIServer 15 | 16 | class WSGIRegressionServer(WSGIServer): 17 | """ 18 | A threaded WSGIServer for use in regression testing. To use this 19 | module, call serve(application, regression=True), and then call 20 | server.accept() to let it handle one request. When finished, use 21 | server.stop() to shutdown the server. Note that all pending requests 22 | are processed before the server shuts down. 23 | """ 24 | defaulttimeout = 10 25 | def __init__ (self, *args, **kwargs): 26 | WSGIServer.__init__(self, *args, **kwargs) 27 | self.stopping = [] 28 | self.pending = [] 29 | self.timeout = self.defaulttimeout 30 | # this is a local connection, be quick 31 | self.socket.settimeout(2) 32 | def serve_forever(self): 33 | from threading import Thread 34 | thread = Thread(target=self.serve_pending) 35 | thread.start() 36 | def reset_expires(self): 37 | if self.timeout: 38 | self.expires = time.time() + self.timeout 39 | def close_request(self, *args, **kwargs): 40 | WSGIServer.close_request(self, *args, **kwargs) 41 | self.pending.pop() 42 | self.reset_expires() 43 | def serve_pending(self): 44 | self.reset_expires() 45 | while not self.stopping or self.pending: 46 | now = time.time() 47 | if now > self.expires and self.timeout: 48 | # note regression test doesn't handle exceptions in 49 | # threads very well; so we just print and exit 50 | print("\nWARNING: WSGIRegressionServer timeout exceeded\n") 51 | break 52 | if self.pending: 53 | self.handle_request() 54 | time.sleep(.1) 55 | def stop(self): 56 | """ stop the server (called from tester's thread) """ 57 | self.stopping.append(True) 58 | def accept(self, count = 1): 59 | """ accept another request (called from tester's thread) """ 60 | assert not self.stopping 61 | [self.pending.append(True) for x in range(count)] 62 | 63 | def serve(application, host=None, port=None, handler=None): 64 | server = WSGIRegressionServer(application, host, port, handler) 65 | print("serving on %s:%s" % server.server_address) 66 | server.serve_forever() 67 | return server 68 | 69 | if __name__ == '__main__': 70 | from urllib.request import urlopen 71 | from paste.wsgilib import dump_environ 72 | server = serve(dump_environ) 73 | baseuri = ("http://%s:%s" % server.server_address) 74 | 75 | def fetch(path): 76 | # tell the server to humor exactly one more request 77 | server.accept(1) 78 | # not needed; but this is what you do if the server 79 | # may not respond in a resonable time period 80 | import socket 81 | socket.setdefaulttimeout(5) 82 | # build a uri, fetch and return 83 | return urlopen(baseuri + path).read() 84 | 85 | assert "PATH_INFO: /foo" in fetch("/foo") 86 | assert "PATH_INFO: /womble" in fetch("/womble") 87 | 88 | # ok, let's make one more final request... 89 | server.accept(1) 90 | # and then schedule a stop() 91 | server.stop() 92 | # and then... fetch it... 93 | urlopen(baseuri) 94 | -------------------------------------------------------------------------------- /paste/debug/wdg_validate.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | Middleware that tests the validity of all generated HTML using the 5 | `WDG HTML Validator `_ 6 | """ 7 | 8 | from io import StringIO 9 | import subprocess 10 | from paste.response import header_value 11 | import re 12 | import html 13 | 14 | __all__ = ['WDGValidateMiddleware'] 15 | 16 | class WDGValidateMiddleware: 17 | 18 | """ 19 | Middleware that checks HTML and appends messages about the validity of 20 | the HTML. Uses: http://www.htmlhelp.com/tools/validator/ -- interacts 21 | with the command line client. Use the configuration ``wdg_path`` to 22 | override the path (default: looks for ``validate`` in $PATH). 23 | 24 | To install, in your web context's __init__.py:: 25 | 26 | def urlparser_wrap(environ, start_response, app): 27 | return wdg_validate.WDGValidateMiddleware(app)( 28 | environ, start_response) 29 | 30 | Or in your configuration:: 31 | 32 | middleware.append('paste.wdg_validate.WDGValidateMiddleware') 33 | """ 34 | 35 | _end_body_regex = re.compile(r'', re.I) 36 | 37 | def __init__(self, app, global_conf=None, wdg_path='validate'): 38 | self.app = app 39 | self.wdg_path = wdg_path 40 | 41 | def __call__(self, environ, start_response): 42 | output = StringIO() 43 | response = [] 44 | 45 | def writer_start_response(status, headers, exc_info=None): 46 | response.extend((status, headers)) 47 | start_response(status, headers, exc_info) 48 | return output.write 49 | 50 | app_iter = self.app(environ, writer_start_response) 51 | try: 52 | for s in app_iter: 53 | output.write(s) 54 | finally: 55 | if hasattr(app_iter, 'close'): 56 | app_iter.close() 57 | page = output.getvalue() 58 | status, headers = response 59 | v = header_value(headers, 'content-type') or '' 60 | if (not v.startswith('text/html') 61 | and not v.startswith('text/xhtml') 62 | and not v.startswith('application/xhtml')): 63 | # Can't validate 64 | # @@: Should validate CSS too... but using what? 65 | return [page] 66 | ops = [] 67 | if v.startswith('text/xhtml+xml'): 68 | ops.append('--xml') 69 | # @@: Should capture encoding too 70 | html_errors = self.call_wdg_validate( 71 | self.wdg_path, ops, page) 72 | if html_errors: 73 | page = self.add_error(page, html_errors)[0] 74 | headers.remove( 75 | ('Content-Length', 76 | str(header_value(headers, 'content-length')))) 77 | headers.append(('Content-Length', str(len(page)))) 78 | return [page] 79 | 80 | def call_wdg_validate(self, wdg_path, ops, page): 81 | if subprocess is None: 82 | raise ValueError( 83 | "This middleware requires the subprocess module from " 84 | "Python 2.4") 85 | proc = subprocess.Popen([wdg_path] + ops, 86 | shell=False, 87 | close_fds=True, 88 | stdout=subprocess.PIPE, 89 | stdin=subprocess.PIPE, 90 | stderr=subprocess.STDOUT) 91 | stdout = proc.communicate(page)[0] 92 | proc.wait() 93 | return stdout 94 | 95 | def add_error(self, html_page, html_errors): 96 | add_text = ('
%s
' 98 | % html.escape(html_errors)) 99 | match = self._end_body_regex.search(html_page) 100 | if match: 101 | return [html_page[:match.start()] 102 | + add_text 103 | + html_page[match.start():]] 104 | else: 105 | return [html_page + add_text] 106 | 107 | def make_wdg_validate_middleware( 108 | app, global_conf, wdg_path='validate'): 109 | """ 110 | Wraps the application in the WDG validator from 111 | http://www.htmlhelp.com/tools/validator/ 112 | 113 | Validation errors are appended to the text of each page. 114 | You can configure this by giving the path to the validate 115 | executable (by default picked up from $PATH) 116 | """ 117 | return WDGValidateMiddleware( 118 | app, global_conf, wdg_path=wdg_path) 119 | -------------------------------------------------------------------------------- /paste/evalexception/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | An exception handler for interactive debugging 5 | """ 6 | from paste.evalexception.middleware import EvalException 7 | 8 | -------------------------------------------------------------------------------- /paste/evalexception/evalcontext.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | from io import StringIO 4 | import traceback 5 | import threading 6 | import pdb 7 | import sys 8 | 9 | exec_lock = threading.Lock() 10 | 11 | class EvalContext: 12 | 13 | """ 14 | Class that represents a interactive interface. It has its own 15 | namespace. Use eval_context.exec_expr(expr) to run commands; the 16 | output of those commands is returned, as are print statements. 17 | 18 | This is essentially what doctest does, and is taken directly from 19 | doctest. 20 | """ 21 | 22 | def __init__(self, namespace, globs): 23 | self.namespace = namespace 24 | self.globs = globs 25 | 26 | def exec_expr(self, s): 27 | out = StringIO() 28 | exec_lock.acquire() 29 | save_stdout = sys.stdout 30 | try: 31 | debugger = _OutputRedirectingPdb(save_stdout) 32 | debugger.reset() 33 | pdb.set_trace = debugger.set_trace 34 | sys.stdout = out 35 | try: 36 | code = compile(s, '', "single", 0, 1) 37 | exec(code, self.globs, self.namespace) 38 | debugger.set_continue() 39 | except KeyboardInterrupt: 40 | raise 41 | except Exception: 42 | traceback.print_exc(file=out) 43 | debugger.set_continue() 44 | finally: 45 | sys.stdout = save_stdout 46 | exec_lock.release() 47 | return out.getvalue() 48 | 49 | # From doctest 50 | class _OutputRedirectingPdb(pdb.Pdb): 51 | """ 52 | A specialized version of the python debugger that redirects stdout 53 | to a given stream when interacting with the user. Stdout is *not* 54 | redirected when traced code is executed. 55 | """ 56 | def __init__(self, out): 57 | self.__out = out 58 | pdb.Pdb.__init__(self) 59 | 60 | def trace_dispatch(self, *args): 61 | # Redirect stdout to the given stream. 62 | save_stdout = sys.stdout 63 | sys.stdout = self.__out 64 | # Call Pdb's trace dispatch method. 65 | try: 66 | return pdb.Pdb.trace_dispatch(self, *args) 67 | finally: 68 | sys.stdout = save_stdout 69 | -------------------------------------------------------------------------------- /paste/evalexception/media/minus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pasteorg/paste/28e461548498138b8814b243be432a04a7895dba/paste/evalexception/media/minus.jpg -------------------------------------------------------------------------------- /paste/evalexception/media/plus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pasteorg/paste/28e461548498138b8814b243be432a04a7895dba/paste/evalexception/media/plus.jpg -------------------------------------------------------------------------------- /paste/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | Package for catching exceptions and displaying annotated exception 5 | reports 6 | """ 7 | -------------------------------------------------------------------------------- /paste/exceptions/serial_number_generator.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | """ 5 | Creates a human-readable identifier, using numbers and digits, 6 | avoiding ambiguous numbers and letters. hash_identifier can be used 7 | to create compact representations that are unique for a certain string 8 | (or concatenation of strings) 9 | """ 10 | 11 | try: 12 | from hashlib import md5 13 | except ImportError: 14 | from md5 import md5 15 | 16 | import operator 17 | byte2int = operator.itemgetter(0) 18 | 19 | good_characters = "23456789abcdefghjkmnpqrtuvwxyz" 20 | 21 | base = len(good_characters) 22 | 23 | def make_identifier(number): 24 | """ 25 | Encodes a number as an identifier. 26 | """ 27 | if not isinstance(number, int): 28 | raise ValueError( 29 | "You can only make identifiers out of integers (not %r)" 30 | % number) 31 | if number < 0: 32 | raise ValueError( 33 | "You cannot make identifiers out of negative numbers: %r" 34 | % number) 35 | result = [] 36 | while number: 37 | next = number % base 38 | result.append(good_characters[next]) 39 | # Note, this depends on integer rounding of results: 40 | number = number // base 41 | return ''.join(result) 42 | 43 | def hash_identifier(s, length, pad=True, hasher=md5, prefix='', 44 | group=None, upper=False): 45 | """ 46 | Hashes the string (with the given hashing module), then turns that 47 | hash into an identifier of the given length (using modulo to 48 | reduce the length of the identifier). If ``pad`` is False, then 49 | the minimum-length identifier will be used; otherwise the 50 | identifier will be padded with 0's as necessary. 51 | 52 | ``prefix`` will be added last, and does not count towards the 53 | target length. ``group`` will group the characters with ``-`` in 54 | the given lengths, and also does not count towards the target 55 | length. E.g., ``group=4`` will cause a identifier like 56 | ``a5f3-hgk3-asdf``. Grouping occurs before the prefix. 57 | """ 58 | if not callable(hasher): 59 | # Accept sha/md5 modules as well as callables 60 | hasher = hasher.new 61 | if length > 26 and hasher is md5: 62 | raise ValueError( 63 | "md5 cannot create hashes longer than 26 characters in " 64 | "length (you gave %s)" % length) 65 | if isinstance(s, str): 66 | s = s.encode('utf-8') 67 | elif not isinstance(s, bytes): 68 | s = str(s) 69 | s = s.encode('utf-8') 70 | h = hasher(s) 71 | bin_hash = h.digest() 72 | modulo = base ** length 73 | number = 0 74 | for c in list(bin_hash): 75 | number = (number * 256 + byte2int([c])) % modulo 76 | ident = make_identifier(number) 77 | if pad: 78 | ident = good_characters[0]*(length-len(ident)) + ident 79 | if group: 80 | parts = [] 81 | while ident: 82 | parts.insert(0, ident[-group:]) 83 | ident = ident[:-group] 84 | ident = '-'.join(parts) 85 | if upper: 86 | ident = ident.upper() 87 | return prefix + ident 88 | 89 | # doctest tests: 90 | __test__ = { 91 | 'make_identifier': """ 92 | >>> make_identifier(0) 93 | '' 94 | >>> make_identifier(1000) 95 | 'c53' 96 | >>> make_identifier(-100) 97 | Traceback (most recent call last): 98 | ... 99 | ValueError: You cannot make identifiers out of negative numbers: -100 100 | >>> make_identifier('test') 101 | Traceback (most recent call last): 102 | ... 103 | ValueError: You can only make identifiers out of integers (not 'test') 104 | >>> make_identifier(1000000000000) 105 | 'c53x9rqh3' 106 | """, 107 | 'hash_identifier': """ 108 | >>> hash_identifier(0, 5) 109 | 'cy2dr' 110 | >>> hash_identifier(0, 10) 111 | 'cy2dr6rg46' 112 | >>> hash_identifier('this is a test of a long string', 5) 113 | 'awatu' 114 | >>> hash_identifier(0, 26) 115 | 'cy2dr6rg46cx8t4w2f3nfexzk4' 116 | >>> hash_identifier(0, 30) 117 | Traceback (most recent call last): 118 | ... 119 | ValueError: md5 cannot create hashes longer than 26 characters in length (you gave 30) 120 | >>> hash_identifier(0, 10, group=4) 121 | 'cy-2dr6-rg46' 122 | >>> hash_identifier(0, 10, group=4, upper=True, prefix='M-') 123 | 'M-CY-2DR6-RG46' 124 | """} 125 | 126 | if __name__ == '__main__': 127 | import doctest 128 | doctest.testmod() 129 | 130 | -------------------------------------------------------------------------------- /paste/flup_session.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | """ 5 | Creates a session object. 6 | 7 | In your application, use:: 8 | 9 | environ['paste.flup_session_service'].session 10 | 11 | This will return a dictionary. The contents of this dictionary will 12 | be saved to disk when the request is completed. The session will be 13 | created when you first fetch the session dictionary, and a cookie will 14 | be sent in that case. There's current no way to use sessions without 15 | cookies, and there's no way to delete a session except to clear its 16 | data. 17 | """ 18 | 19 | from paste import httpexceptions, wsgilib 20 | from paste.util import NO_DEFAULT 21 | 22 | import flup.middleware.session 23 | 24 | flup_session = flup.middleware.session 25 | 26 | # This is a dictionary of existing stores, keyed by a tuple of 27 | # store type and parameters 28 | store_cache = {} 29 | 30 | class SessionMiddleware: 31 | 32 | session_classes = { 33 | 'memory': (flup_session.MemorySessionStore, 34 | [('session_timeout', 'timeout', int, 60)]), 35 | 'disk': (flup_session.DiskSessionStore, 36 | [('session_timeout', 'timeout', int, 60), 37 | ('session_dir', 'storeDir', str, '/tmp/sessions')]), 38 | 'shelve': (flup_session.ShelveSessionStore, 39 | [('session_timeout', 'timeout', int, 60), 40 | ('session_file', 'storeFile', str, 41 | '/tmp/session.shelve')]), 42 | } 43 | 44 | 45 | def __init__(self, app, 46 | global_conf=None, 47 | session_type=NO_DEFAULT, 48 | cookie_name=NO_DEFAULT, 49 | **store_config 50 | ): 51 | self.application = app 52 | if session_type is NO_DEFAULT: 53 | session_type = global_conf.get('session_type', 'disk') 54 | self.session_type = session_type 55 | try: 56 | self.store_class, self.store_args = self.session_classes[self.session_type] 57 | except KeyError: 58 | raise KeyError( 59 | "The session_type %s is unknown (I know about %s)" 60 | % (self.session_type, 61 | ', '.join(self.session_classes.keys()))) 62 | kw = {} 63 | for config_name, kw_name, coercer, default in self.store_args: 64 | value = coercer(store_config.get(config_name, default)) 65 | kw[kw_name] = value 66 | self.store = self.store_class(**kw) 67 | if cookie_name is NO_DEFAULT: 68 | cookie_name = global_conf.get('session_cookie', '_SID_') 69 | self.cookie_name = cookie_name 70 | 71 | def __call__(self, environ, start_response): 72 | service = flup_session.SessionService( 73 | self.store, environ, cookieName=self.cookie_name, 74 | fieldName=self.cookie_name) 75 | environ['paste.flup_session_service'] = service 76 | 77 | def cookie_start_response(status, headers, exc_info=None): 78 | service.addCookie(headers) 79 | return start_response(status, headers, exc_info) 80 | 81 | try: 82 | app_iter = self.application(environ, cookie_start_response) 83 | except httpexceptions.HTTPException as e: 84 | headers = (e.headers or {}).items() 85 | service.addCookie(headers) 86 | e.headers = dict(headers) 87 | service.close() 88 | raise 89 | except Exception: 90 | service.close() 91 | raise 92 | 93 | return wsgilib.add_close(app_iter, service.close) 94 | 95 | def make_session_middleware(app, global_conf, 96 | session_type=NO_DEFAULT, 97 | cookie_name=NO_DEFAULT, 98 | **store_config): 99 | """ 100 | Wraps the application in a session-managing middleware. 101 | The session service can then be found in 102 | ``environ['paste.flup_session_service']`` 103 | """ 104 | return SessionMiddleware( 105 | app, global_conf=global_conf, 106 | session_type=session_type, cookie_name=cookie_name, 107 | **store_config) 108 | -------------------------------------------------------------------------------- /paste/gzipper.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 5 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 6 | 7 | """ 8 | WSGI middleware 9 | 10 | Gzip-encodes the response. 11 | """ 12 | 13 | import gzip 14 | import io 15 | 16 | from paste.response import header_value, remove_header 17 | from paste.httpheaders import CONTENT_LENGTH 18 | 19 | class GzipOutput: 20 | pass 21 | 22 | class middleware: 23 | 24 | def __init__(self, application, compress_level=6): 25 | self.application = application 26 | self.compress_level = int(compress_level) 27 | 28 | def __call__(self, environ, start_response): 29 | if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', '') \ 30 | or environ['REQUEST_METHOD'] == 'HEAD': 31 | # nothing for us to do, so this middleware will 32 | # be a no-op (there's no body expected in the HEAD case, 33 | # and if we open a GzipFile we would produce an erroneous 34 | # 20-byte header and trailer): 35 | return self.application(environ, start_response) 36 | response = GzipResponse(start_response, self.compress_level) 37 | app_iter = self.application(environ, 38 | response.gzip_start_response) 39 | if app_iter is not None: 40 | response.finish_response(app_iter) 41 | 42 | return response.write() 43 | 44 | class GzipResponse: 45 | 46 | def __init__(self, start_response, compress_level): 47 | self.start_response = start_response 48 | self.compress_level = compress_level 49 | self.buffer = io.BytesIO() 50 | self.compressible = False 51 | self.content_length = None 52 | 53 | def gzip_start_response(self, status, headers, exc_info=None): 54 | self.headers = headers 55 | ct = header_value(headers,'content-type') 56 | ce = header_value(headers,'content-encoding') 57 | self.compressible = False 58 | if ct and (ct.startswith('text/') or ct.startswith('application/')) \ 59 | and 'zip' not in ct: 60 | self.compressible = True 61 | if ce: 62 | self.compressible = False 63 | if self.compressible: 64 | headers.append(('content-encoding', 'gzip')) 65 | remove_header(headers, 'content-length') 66 | self.headers = headers 67 | self.status = status 68 | return self.buffer.write 69 | 70 | def write(self): 71 | out = self.buffer 72 | out.seek(0) 73 | s = out.getvalue() 74 | out.close() 75 | return [s] 76 | 77 | def finish_response(self, app_iter): 78 | if self.compressible: 79 | output = gzip.GzipFile(mode='wb', compresslevel=self.compress_level, 80 | fileobj=self.buffer) 81 | else: 82 | output = self.buffer 83 | try: 84 | for s in app_iter: 85 | output.write(s) 86 | if self.compressible: 87 | output.close() 88 | finally: 89 | if hasattr(app_iter, 'close'): 90 | app_iter.close() 91 | content_length = self.buffer.tell() 92 | CONTENT_LENGTH.update(self.headers, content_length) 93 | self.start_response(self.status, self.headers) 94 | 95 | def filter_factory(application, **conf): 96 | import warnings 97 | warnings.warn( 98 | 'This function is deprecated; use make_gzip_middleware instead', 99 | DeprecationWarning, 2) 100 | def filter(application): 101 | return middleware(application) 102 | return filter 103 | 104 | def make_gzip_middleware(app, global_conf, compress_level=6): 105 | """ 106 | Wrap the middleware, so that it applies gzipping to a response 107 | when it is supported by the browser and the content is of 108 | type ``text/*`` or ``application/*`` 109 | """ 110 | compress_level = int(compress_level) 111 | return middleware(app, compress_level=compress_level) 112 | -------------------------------------------------------------------------------- /paste/pony.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | We have a pony and/or a unicorn. 5 | """ 6 | from paste.request import construct_url 7 | 8 | PONY = """ 9 | eJyFkkFuxCAMRfdzCisbJxK2D5D2JpbMrlI3XXQZDt9PCG0ySgcWIMT79rcN0XClUJlZRB9jVmci 10 | FmV19khjgRFl0RzrKmqzvY8lRUWFlXvCrD7UbAQR/17NUvGhypAF9og16vWtkC8DzUayS6pN3/dR 11 | ki0OnpzKjUBFpmlC7zVFRNL1rwoq6PWXXQSnIm9WoTzlM2//ke21o5g/l1ckRhiPbkDZXsKIR7l1 12 | 36hF9uMhnRiVjI8UgYjlsIKCrXXpcA9iX5y7zMmtG0fUpW61Ssttipf6cp3WARfkMVoYFryi2a+w 13 | o/2dhW0OXfcMTnmh53oR9egzPs+qkpY9IKxdUVRP5wHO7UDAuI6moA2N+/z4vtc2k8B+AIBimVU= 14 | """ 15 | 16 | UNICORN = """ 17 | eJyVVD1vhDAM3e9XeAtIxB5P6qlDx0OMXVBzSpZOHdsxP762E0JAnMgZ8Zn37OePAPC60eV1Dl5b 18 | SS7fB6DmQNGhtegpNlPIQS8HmkYGdSqNqDF9wcMYus4TuBYGsZwIPqXfEoNir5K+R3mbzhlR4JMW 19 | eGpikPpn9wHl2sDgEH1270guZwzKDRf3nTztMvfI5r3fJqEmNxdCyISBcWjNgjPG8Egg2hgT3mJi 20 | KBwNvmPB1hbWJ3TwBfMlqdTzxNyDE2H8zOD5HA4KkqJGPVY/TwnxmPA82kdSJNj7zs+R0d1pB+JO 21 | xn2DKgsdxAfFS2pfTSD0Fb6Uzv7dCQSvE5JmZQEQ90vNjBU1GPuGQpCPS8cGo+dQgjIKqxnJTXbw 22 | ucFzPFVIJXtzk6BXKGPnYsKzvFmGx7A0j6Zqvlvk5rETXbMWTGWj0RFc8QNPYVfhJfMMniCPazWJ 23 | lGtPZecIGJWW6oL2hpbWRZEkChe8eg5Wb7xx/MBZBFjxeZPEss+mRQ3Uhc8WQv684seSRO7i3nb4 24 | 7HlKUg8sraz47LmXyh8S0somADvoUpoHjGWl+rUkF0H+EIf/gbyyMg58BBk6L634/fkHUCodMw== 25 | """ 26 | 27 | 28 | class PonyMiddleware: 29 | 30 | def __init__(self, application): 31 | self.application = application 32 | 33 | def __call__(self, environ, start_response): 34 | path_info = environ.get('PATH_INFO', '') 35 | if path_info == '/pony': 36 | url = construct_url(environ, with_query_string=False) 37 | if 'horn' in environ.get('QUERY_STRING', ''): 38 | data = UNICORN 39 | link = 'remove horn!' 40 | else: 41 | data = PONY 42 | url += '?horn' 43 | link = 'add horn!' 44 | msg = data.decode('base64').decode('zlib') 45 | msg = '
%s\n%s
' % ( 46 | msg, url, link) 47 | start_response('200 OK', [('content-type', 'text/html')]) 48 | return [msg] 49 | else: 50 | return self.application(environ, start_response) 51 | 52 | def make_pony(app, global_conf): 53 | """ 54 | Adds pony power to any application, at /pony 55 | """ 56 | return PonyMiddleware(app) 57 | 58 | -------------------------------------------------------------------------------- /paste/util/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Package for miscellaneous routines that do not depend on other parts 3 | of Paste 4 | """ 5 | 6 | 7 | class NoDefault: 8 | """Sentinel for parameters without default value.""" 9 | 10 | def __repr__(self): 11 | return '' 12 | 13 | 14 | NO_DEFAULT = NoDefault() 15 | -------------------------------------------------------------------------------- /paste/util/classinit.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | class ClassInitMeta(type): 5 | 6 | def __new__(meta, class_name, bases, new_attrs): 7 | cls = type.__new__(meta, class_name, bases, new_attrs) 8 | if (new_attrs.has_key('__classinit__') 9 | and not isinstance(cls.__classinit__, staticmethod)): 10 | setattr(cls, '__classinit__', 11 | staticmethod(cls.__classinit__.im_func)) 12 | if hasattr(cls, '__classinit__'): 13 | cls.__classinit__(cls, new_attrs) 14 | return cls 15 | 16 | def build_properties(cls, new_attrs): 17 | """ 18 | Given a class and a new set of attributes (as passed in by 19 | __classinit__), create or modify properties based on functions 20 | with special names ending in __get, __set, and __del. 21 | """ 22 | for name, value in new_attrs.items(): 23 | if (name.endswith('__get') or name.endswith('__set') 24 | or name.endswith('__del')): 25 | base = name[:-5] 26 | if hasattr(cls, base): 27 | old_prop = getattr(cls, base) 28 | if not isinstance(old_prop, property): 29 | raise ValueError( 30 | "Attribute %s is a %s, not a property; function %s is named like a property" 31 | % (base, type(old_prop), name)) 32 | attrs = {'fget': old_prop.fget, 33 | 'fset': old_prop.fset, 34 | 'fdel': old_prop.fdel, 35 | 'doc': old_prop.__doc__} 36 | else: 37 | attrs = {} 38 | attrs['f' + name[-3:]] = value 39 | if name.endswith('__get') and value.__doc__: 40 | attrs['doc'] = value.__doc__ 41 | new_prop = property(**attrs) 42 | setattr(cls, base, new_prop) 43 | -------------------------------------------------------------------------------- /paste/util/classinstance.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | class classinstancemethod: 5 | """ 6 | Acts like a class method when called from a class, like an 7 | instance method when called by an instance. The method should 8 | take two arguments, 'self' and 'cls'; one of these will be None 9 | depending on how the method was called. 10 | """ 11 | 12 | def __init__(self, func): 13 | self.func = func 14 | self.__doc__ = func.__doc__ 15 | 16 | def __get__(self, obj, type=None): 17 | return _methodwrapper(self.func, obj=obj, type=type) 18 | 19 | class _methodwrapper: 20 | 21 | def __init__(self, func, obj, type): 22 | self.func = func 23 | self.obj = obj 24 | self.type = type 25 | 26 | def __call__(self, *args, **kw): 27 | assert 'self' not in kw and 'cls' not in kw, ( 28 | "You cannot use 'self' or 'cls' arguments to a " 29 | "classinstancemethod") 30 | return self.func(*((self.obj, self.type) + args), **kw) 31 | 32 | def __repr__(self): 33 | if self.obj is None: 34 | return ('' 35 | % (self.type.__name__, self.func.func_name)) 36 | else: 37 | return ('' 38 | % (self.type.__name__, self.func.func_name, self.obj)) 39 | -------------------------------------------------------------------------------- /paste/util/converters.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | 5 | def asbool(obj): 6 | if isinstance(obj, (bytes, str)): 7 | obj = obj.strip().lower() 8 | if obj in ['true', 'yes', 'on', 'y', 't', '1']: 9 | return True 10 | elif obj in ['false', 'no', 'off', 'n', 'f', '0']: 11 | return False 12 | else: 13 | raise ValueError( 14 | "String is not true/false: %r" % obj) 15 | return bool(obj) 16 | 17 | def aslist(obj, sep=None, strip=True): 18 | if isinstance(obj, (bytes, str)): 19 | lst = obj.split(sep) 20 | if strip: 21 | lst = [v.strip() for v in lst] 22 | return lst 23 | elif isinstance(obj, (list, tuple)): 24 | return obj 25 | elif obj is None: 26 | return [] 27 | else: 28 | return [obj] 29 | -------------------------------------------------------------------------------- /paste/util/dateinterval.py: -------------------------------------------------------------------------------- 1 | """ 2 | DateInterval.py 3 | 4 | Convert interval strings (in the form of 1w2d, etc) to 5 | seconds, and back again. Is not exactly about months or 6 | years (leap years in particular). 7 | 8 | Accepts (y)ear, (b)month, (w)eek, (d)ay, (h)our, (m)inute, (s)econd. 9 | 10 | Exports only timeEncode and timeDecode functions. 11 | """ 12 | 13 | import re 14 | 15 | __all__ = ['interval_decode', 'interval_encode'] 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 = list(timeValues.items()) 34 | timeOrdered.sort(key=lambda x: x[1], reverse=True) 35 | 36 | 37 | def interval_encode(seconds, include_sign=False): 38 | """Encodes a number of seconds (representing a time interval) 39 | into a form like 1h2d3s. 40 | 41 | >>> interval_encode(10) 42 | '10s' 43 | >>> interval_encode(493939) 44 | '5d17h12m19s' 45 | """ 46 | s = '' 47 | orig = seconds 48 | seconds = abs(seconds) 49 | for char, amount in timeOrdered: 50 | if seconds >= amount: 51 | i, seconds = divmod(seconds, amount) 52 | s += '%i%s' % (i, char) 53 | if orig < 0: 54 | s = '-' + s 55 | elif not orig: 56 | return '0' 57 | elif include_sign: 58 | s = '+' + s 59 | return s 60 | 61 | _timeRE = re.compile(r'[0-9]+[a-zA-Z]') 62 | def interval_decode(s): 63 | """Decodes a number in the format 1h4d3m (1 hour, 3 days, 3 minutes) 64 | into a number of seconds 65 | 66 | >>> interval_decode('40s') 67 | 40 68 | >>> interval_decode('10000s') 69 | 10000 70 | >>> interval_decode('3d1w45s') 71 | 864045 72 | """ 73 | time = 0 74 | sign = 1 75 | s = s.strip() 76 | if s.startswith('-'): 77 | s = s[1:] 78 | sign = -1 79 | elif s.startswith('+'): 80 | s = s[1:] 81 | for match in allMatches(s, _timeRE): 82 | char = match.group(0)[-1].lower() 83 | if char not in timeValues: 84 | # @@: should signal error 85 | continue 86 | time += int(match.group(0)[:-1]) * timeValues[char] 87 | return time 88 | 89 | # @@-sgd 2002-12-23 - this function does not belong in this module, find a better place. 90 | def allMatches(source, regex): 91 | """Return a list of matches for regex in source 92 | """ 93 | pos = 0 94 | end = len(source) 95 | rv = [] 96 | match = regex.search(source, pos) 97 | while match: 98 | rv.append(match) 99 | match = regex.search(source, match.end() ) 100 | return rv 101 | 102 | if __name__ == '__main__': 103 | import doctest 104 | doctest.testmod() 105 | -------------------------------------------------------------------------------- /paste/util/filemixin.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | class FileMixin: 5 | 6 | """ 7 | Used to provide auxiliary methods to objects simulating files. 8 | Objects must implement write, and read if they are input files. 9 | Also they should implement close. 10 | 11 | Other methods you may wish to override: 12 | * flush() 13 | * seek(offset[, whence]) 14 | * tell() 15 | * truncate([size]) 16 | 17 | Attributes you may wish to provide: 18 | * closed 19 | * encoding (you should also respect that in write()) 20 | * mode 21 | * newlines (hard to support) 22 | * softspace 23 | """ 24 | 25 | def flush(self): 26 | pass 27 | 28 | def next(self): 29 | return self.readline() 30 | 31 | def readline(self, size=None): 32 | # @@: This is a lame implementation; but a buffer would probably 33 | # be necessary for a better implementation 34 | output = [] 35 | while 1: 36 | next = self.read(1) 37 | if not next: 38 | return ''.join(output) 39 | output.append(next) 40 | if size and size > 0 and len(output) >= size: 41 | return ''.join(output) 42 | if next == '\n': 43 | # @@: also \r? 44 | return ''.join(output) 45 | 46 | def xreadlines(self): 47 | return self 48 | 49 | def writelines(self, lines): 50 | for line in lines: 51 | self.write(line) 52 | 53 | 54 | -------------------------------------------------------------------------------- /paste/util/finddata.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | # Note: you may want to copy this into your setup.py file verbatim, as 4 | # you can't import this from another package, when you don't know if 5 | # that package is installed yet. 6 | 7 | import sys 8 | from fnmatch import fnmatchcase 9 | from pathlib import Path 10 | 11 | # Provided as an attribute, so you can append to these instead 12 | # of replicating them: 13 | standard_exclude = ('*.py', '*.pyc', '*$py.class', '*~', '.*', '*.bak') 14 | standard_exclude_directories = ('.*', 'CVS', '_darcs', './build', 15 | './dist', 'EGG-INFO', '*.egg-info') 16 | 17 | def find_package_data( 18 | where='.', package='', 19 | exclude=standard_exclude, 20 | exclude_directories=standard_exclude_directories, 21 | only_in_packages=True, 22 | show_ignored=False): 23 | """ 24 | Return a dictionary suitable for use in ``package_data`` 25 | in a distutils ``setup.py`` file. 26 | 27 | The dictionary looks like:: 28 | 29 | {'package': [files]} 30 | 31 | Where ``files`` is a list of all the files in that package that 32 | don't match anything in ``exclude``. 33 | 34 | If ``only_in_packages`` is true, then top-level directories that 35 | are not packages won't be included (but directories under packages 36 | will). 37 | 38 | Directories matching any pattern in ``exclude_directories`` will 39 | be ignored; by default directories with leading ``.``, ``CVS``, 40 | and ``_darcs`` will be ignored. 41 | 42 | If ``show_ignored`` is true, then all the files that aren't 43 | included in package data are shown on stderr (for debugging 44 | purposes). 45 | 46 | Note patterns use wildcards, or can be exact paths (including 47 | leading ``./``), and all searching is case-insensitive. 48 | """ 49 | 50 | out = {} 51 | stack = [(Path(where), '', package, only_in_packages)] 52 | while stack: 53 | where, prefix, package, only_in_packages = stack.pop(0) 54 | for name in where.iterdir(): 55 | fn = where.joinpath(name) 56 | if fn.is_dir(): 57 | bad_name = False 58 | for pattern in exclude_directories: 59 | if (fnmatchcase(name.as_posix(), pattern) 60 | or fn.as_posix().lower() == pattern.lower()): 61 | bad_name = True 62 | if show_ignored: 63 | print("Directory %s ignored by pattern %s" 64 | % (fn.as_posix(), pattern), file=sys.stderr) 65 | break 66 | if bad_name: 67 | continue 68 | if fn.joinpath('__init__.py').is_file() and not prefix: 69 | if not package: 70 | new_package = name.as_posix() 71 | else: 72 | new_package = package + '.' + name.as_posix() 73 | stack.append((fn, '', new_package, False)) 74 | else: 75 | stack.append((fn, prefix + name.as_posix() + '/', package, only_in_packages)) 76 | elif package or not only_in_packages: 77 | # is a file 78 | bad_name = False 79 | for pattern in exclude: 80 | if (fnmatchcase(name.as_posix(), pattern) 81 | or fn.as_posix().lower() == pattern.lower()): 82 | bad_name = True 83 | if show_ignored: 84 | print("File %s ignored by pattern %s" 85 | % (fn.as_posix(), pattern), file=sys.stderr) 86 | break 87 | if bad_name: 88 | continue 89 | out.setdefault(package, []).append(prefix+name.as_posix()) 90 | return out 91 | 92 | if __name__ == '__main__': 93 | import pprint 94 | pprint.pprint( 95 | find_package_data(show_ignored=True)) 96 | -------------------------------------------------------------------------------- /paste/util/findpackage.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | import sys 5 | import os 6 | 7 | def find_package(dir): 8 | """ 9 | Given a directory, finds the equivalent package name. If it 10 | is directly in sys.path, returns ''. 11 | """ 12 | dir = os.path.abspath(dir) 13 | orig_dir = dir 14 | path = map(os.path.abspath, sys.path) 15 | packages = [] 16 | last_dir = None 17 | while 1: 18 | if dir in path: 19 | return '.'.join(packages) 20 | packages.insert(0, os.path.basename(dir)) 21 | dir = os.path.dirname(dir) 22 | if last_dir == dir: 23 | raise ValueError( 24 | "%s is not under any path found in sys.path" % orig_dir) 25 | last_dir = dir 26 | 27 | -------------------------------------------------------------------------------- /paste/util/import_string.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | """ 5 | 'imports' a string -- converts a string to a Python object, importing 6 | any necessary modules and evaluating the expression. Everything 7 | before the : in an import expression is the module path; everything 8 | after is an expression to be evaluated in the namespace of that 9 | module. 10 | 11 | Alternately, if no : is present, then import the modules and get the 12 | attributes as necessary. Arbitrary expressions are not allowed in 13 | that case. 14 | """ 15 | 16 | def eval_import(s): 17 | """ 18 | Import a module, or import an object from a module. 19 | 20 | A module name like ``foo.bar:baz()`` can be used, where 21 | ``foo.bar`` is the module, and ``baz()`` is an expression 22 | evaluated in the context of that module. Note this is not safe on 23 | arbitrary strings because of the eval. 24 | """ 25 | if ':' not in s: 26 | return simple_import(s) 27 | module_name, expr = s.split(':', 1) 28 | module = import_module(module_name) 29 | obj = eval(expr, module.__dict__) 30 | return obj 31 | 32 | def simple_import(s): 33 | """ 34 | Import a module, or import an object from a module. 35 | 36 | A name like ``foo.bar.baz`` can be a module ``foo.bar.baz`` or a 37 | module ``foo.bar`` with an object ``baz`` in it, or a module 38 | ``foo`` with an object ``bar`` with an attribute ``baz``. 39 | """ 40 | parts = s.split('.') 41 | module = import_module(parts[0]) 42 | name = parts[0] 43 | parts = parts[1:] 44 | last_import_error = None 45 | while parts: 46 | name += '.' + parts[0] 47 | try: 48 | module = import_module(name) 49 | parts = parts[1:] 50 | except ImportError as e: 51 | last_import_error = e 52 | break 53 | obj = module 54 | while parts: 55 | try: 56 | obj = getattr(module, parts[0]) 57 | except AttributeError: 58 | raise ImportError( 59 | "Cannot find %s in module %r (stopped importing modules with error %s)" % (parts[0], module, last_import_error)) 60 | parts = parts[1:] 61 | return obj 62 | 63 | def import_module(s): 64 | """ 65 | Import a module. 66 | """ 67 | mod = __import__(s) 68 | parts = s.split('.') 69 | for part in parts[1:]: 70 | mod = getattr(mod, part) 71 | return mod 72 | 73 | def try_import_module(module_name): 74 | """ 75 | Imports a module, but catches import errors. Only catches errors 76 | when that module doesn't exist; if that module itself has an 77 | import error it will still get raised. Returns None if the module 78 | doesn't exist. 79 | """ 80 | try: 81 | return import_module(module_name) 82 | except ImportError as e: 83 | if not getattr(e, 'args', None): 84 | raise 85 | desc = e.args[0] 86 | if not desc.startswith('No module named '): 87 | raise 88 | desc = desc[len('No module named '):] 89 | # If you import foo.bar.baz, the bad import could be any 90 | # of foo.bar.baz, bar.baz, or baz; we'll test them all: 91 | parts = module_name.split('.') 92 | for i in range(len(parts)): 93 | if desc == '.'.join(parts[i:]): 94 | return None 95 | raise 96 | -------------------------------------------------------------------------------- /paste/util/killthread.py: -------------------------------------------------------------------------------- 1 | """ 2 | Kill a thread, from http://sebulba.wikispaces.com/recipe+thread2 3 | """ 4 | try: 5 | import ctypes 6 | except ImportError: 7 | raise ImportError( 8 | "You cannot use paste.util.killthread without ctypes installed") 9 | if not hasattr(ctypes, 'pythonapi'): 10 | raise ImportError( 11 | "You cannot use paste.util.killthread without ctypes.pythonapi") 12 | 13 | def async_raise(tid, exctype): 14 | """raises the exception, performs cleanup if needed. 15 | 16 | tid is the value given by thread.get_ident() (an integer). 17 | Raise SystemExit to kill a thread.""" 18 | if not isinstance(exctype, type): 19 | raise TypeError("Only types can be raised (not instances)") 20 | if not isinstance(tid, int): 21 | raise TypeError("tid must be an integer") 22 | res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), ctypes.py_object(exctype)) 23 | if res == 0: 24 | raise ValueError("invalid thread id") 25 | elif res != 1: 26 | # """if it returns a number greater than one, you're in trouble, 27 | # and you should call it again with exc=NULL to revert the effect""" 28 | ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), 0) 29 | raise SystemError("PyThreadState_SetAsyncExc failed") 30 | -------------------------------------------------------------------------------- /paste/util/looper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper for looping over sequences, particular in templates. 3 | 4 | Often in a loop in a template it's handy to know what's next up, 5 | previously up, if this is the first or last item in the sequence, etc. 6 | These can be awkward to manage in a normal Python loop, but using the 7 | looper you can get a better sense of the context. Use like:: 8 | 9 | >>> for loop, item in looper(['a', 'b', 'c']): 10 | ... print("%s %s" % (loop.number, item)) 11 | ... if not loop.last: 12 | ... print('---') 13 | 1 a 14 | --- 15 | 2 b 16 | --- 17 | 3 c 18 | 19 | """ 20 | 21 | __all__ = ['looper'] 22 | 23 | 24 | class looper: 25 | """ 26 | Helper for looping (particularly in templates) 27 | 28 | Use this like:: 29 | 30 | for loop, item in looper(seq): 31 | if loop.first: 32 | ... 33 | """ 34 | 35 | def __init__(self, seq): 36 | self.seq = seq 37 | 38 | def __iter__(self): 39 | return looper_iter(self.seq) 40 | 41 | def __repr__(self): 42 | return '<%s for %r>' % ( 43 | self.__class__.__name__, self.seq) 44 | 45 | class looper_iter: 46 | 47 | def __init__(self, seq): 48 | self.seq = list(seq) 49 | self.pos = 0 50 | 51 | def __iter__(self): 52 | return self 53 | 54 | def next(self): 55 | if self.pos >= len(self.seq): 56 | raise StopIteration 57 | result = loop_pos(self.seq, self.pos), self.seq[self.pos] 58 | self.pos += 1 59 | return result 60 | __next__ = next 61 | 62 | class loop_pos: 63 | 64 | def __init__(self, seq, pos): 65 | self.seq = seq 66 | self.pos = pos 67 | 68 | def __repr__(self): 69 | return '' % ( 70 | self.seq[self.pos], self.pos) 71 | 72 | def index(self): 73 | return self.pos 74 | index = property(index) 75 | 76 | def number(self): 77 | return self.pos + 1 78 | number = property(number) 79 | 80 | def item(self): 81 | return self.seq[self.pos] 82 | item = property(item) 83 | 84 | def next(self): 85 | try: 86 | return self.seq[self.pos+1] 87 | except IndexError: 88 | return None 89 | next = property(next) 90 | 91 | def previous(self): 92 | if self.pos == 0: 93 | return None 94 | return self.seq[self.pos-1] 95 | previous = property(previous) 96 | 97 | def odd(self): 98 | return not self.pos % 2 99 | odd = property(odd) 100 | 101 | def even(self): 102 | return self.pos % 2 103 | even = property(even) 104 | 105 | def first(self): 106 | return self.pos == 0 107 | first = property(first) 108 | 109 | def last(self): 110 | return self.pos == len(self.seq)-1 111 | last = property(last) 112 | 113 | def length(self): 114 | return len(self.seq) 115 | length = property(length) 116 | 117 | def first_group(self, getter=None): 118 | """ 119 | Returns true if this item is the start of a new group, 120 | where groups mean that some attribute has changed. The getter 121 | can be None (the item itself changes), an attribute name like 122 | ``'.attr'``, a function, or a dict key or list index. 123 | """ 124 | if self.first: 125 | return True 126 | return self._compare_group(self.item, self.previous, getter) 127 | 128 | def last_group(self, getter=None): 129 | """ 130 | Returns true if this item is the end of a new group, 131 | where groups mean that some attribute has changed. The getter 132 | can be None (the item itself changes), an attribute name like 133 | ``'.attr'``, a function, or a dict key or list index. 134 | """ 135 | if self.last: 136 | return True 137 | return self._compare_group(self.item, self.next, getter) 138 | 139 | def _compare_group(self, item, other, getter): 140 | if getter is None: 141 | return item != other 142 | elif (isinstance(getter, (bytes, str)) 143 | and getter.startswith('.')): 144 | getter = getter[1:] 145 | if getter.endswith('()'): 146 | getter = getter[:-2] 147 | return getattr(item, getter)() != getattr(other, getter)() 148 | else: 149 | return getattr(item, getter) != getattr(other, getter) 150 | elif callable(getter): 151 | return getter(item) != getter(other) 152 | else: 153 | return item[getter] != other[getter] 154 | 155 | -------------------------------------------------------------------------------- /paste/util/quoting.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | import html 5 | import html.entities 6 | import re 7 | from urllib.parse import quote, unquote 8 | 9 | __all__ = ['html_quote', 'html_unquote', 'url_quote', 'url_unquote', 10 | 'strip_html'] 11 | 12 | default_encoding = 'UTF-8' 13 | 14 | def html_quote(v, encoding=None): 15 | r""" 16 | Quote the value (turned to a string) as HTML. This quotes <, >, 17 | and quotes: 18 | """ 19 | encoding = encoding or default_encoding 20 | if v is None: 21 | return '' 22 | elif isinstance(v, bytes): 23 | return html.escape(v.decode(encoding), 1).encode(encoding) 24 | elif isinstance(v, str): 25 | return html.escape(v, 1) 26 | else: 27 | return html.escape(str(v), 1) 28 | 29 | _unquote_re = re.compile(r'&([a-zA-Z]+);') 30 | def _entity_subber(match, name2c=html.entities.name2codepoint): 31 | code = name2c.get(match.group(1)) 32 | if code: 33 | return chr(code) 34 | else: 35 | return match.group(0) 36 | 37 | def html_unquote(s, encoding=None): 38 | r""" 39 | Decode the value. 40 | 41 | """ 42 | if isinstance(s, bytes): 43 | s = s.decode(encoding or default_encoding) 44 | return _unquote_re.sub(_entity_subber, s) 45 | 46 | def strip_html(s): 47 | # should this use html_unquote? 48 | s = re.sub('<.*?>', '', s) 49 | s = html_unquote(s) 50 | return s 51 | 52 | def no_quote(s): 53 | """ 54 | Quoting that doesn't do anything 55 | """ 56 | return s 57 | 58 | _comment_quote_re = re.compile(r'\-\s*\>') 59 | # Everything but \r, \n, \t: 60 | _bad_chars_re = re.compile('[\x00-\x08\x0b-\x0c\x0e-\x1f]') 61 | def comment_quote(s): 62 | """ 63 | Quote that makes sure text can't escape a comment 64 | """ 65 | comment = str(s) 66 | #comment = _bad_chars_re.sub('', comment) 67 | #print('in ', repr(str(s))) 68 | #print('out', repr(comment)) 69 | comment = _comment_quote_re.sub('->', comment) 70 | return comment 71 | 72 | url_quote = quote 73 | url_unquote = unquote 74 | 75 | if __name__ == '__main__': 76 | import doctest 77 | doctest.testmod() 78 | -------------------------------------------------------------------------------- /paste/util/threadinglocal.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | """ 4 | Implementation of thread-local storage, for Python versions that don't 5 | have thread local storage natively. 6 | """ 7 | 8 | try: 9 | import threading 10 | except ImportError: 11 | # No threads, so "thread local" means process-global 12 | class local: 13 | pass 14 | else: 15 | try: 16 | local = threading.local 17 | except AttributeError: 18 | # Added in 2.4, but now we'll have to define it ourselves 19 | import thread 20 | class local: 21 | 22 | def __init__(self): 23 | self.__dict__['__objs'] = {} 24 | 25 | def __getattr__(self, attr, g=thread.get_ident): 26 | try: 27 | return self.__dict__['__objs'][g()][attr] 28 | except KeyError: 29 | raise AttributeError( 30 | "No variable %s defined for the thread %s" 31 | % (attr, g())) 32 | 33 | def __setattr__(self, attr, value, g=thread.get_ident): 34 | self.__dict__['__objs'].setdefault(g(), {})[attr] = value 35 | 36 | def __delattr__(self, attr, g=thread.get_ident): 37 | try: 38 | del self.__dict__['__objs'][g()][attr] 39 | except KeyError: 40 | raise AttributeError( 41 | "No variable %s defined for thread %s" 42 | % (attr, g())) 43 | 44 | -------------------------------------------------------------------------------- /regen-docs: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p docs/_static docs/_build 4 | sphinx-build -E -b html docs/ docs/_build || exit 1 5 | if [ "$1" = "publish" ] ; then 6 | cd docs/ 7 | echo "Uploading files..." 8 | scp -r _build/* ianb@webwareforpython.org:/home/paste/htdocs/ 9 | fi 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = 3 | tag_date = 0 4 | tag_svn_revision = 0 5 | 6 | [aliases] 7 | distribute = register sdist bdist_egg upload pudge publish 8 | 9 | [tool:pytest] 10 | testpaths = tests 11 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests for Paste""" 2 | import sys 3 | import os 4 | 5 | sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) 6 | 7 | import pkg_resources 8 | -------------------------------------------------------------------------------- /tests/cgiapp_data/error.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | print('hey you!') 4 | -------------------------------------------------------------------------------- /tests/cgiapp_data/form.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | print('Content-type: text/plain') 4 | print('') 5 | 6 | import sys 7 | import warnings 8 | from os.path import dirname 9 | 10 | base_dir = dirname(dirname(dirname(__file__))) 11 | sys.path.insert(0, base_dir) 12 | 13 | with warnings.catch_warnings(): 14 | warnings.simplefilter("ignore", category=DeprecationWarning) 15 | try: 16 | import pkg_resources 17 | except ImportError: 18 | # Ignore 19 | pass 20 | 21 | from paste.util.field_storage import FieldStorage 22 | 23 | class FormFieldStorage(FieldStorage): 24 | 25 | def _key_candidates(self, key): 26 | yield key 27 | 28 | try: 29 | # assume bytes, coerce to str 30 | try: 31 | yield key.decode(self.encoding) 32 | except UnicodeDecodeError: 33 | pass 34 | except AttributeError: 35 | # assume str, coerce to bytes 36 | try: 37 | yield key.encode(self.encoding) 38 | except UnicodeEncodeError: 39 | pass 40 | 41 | def __getitem__(self, key): 42 | error = None 43 | 44 | for candidate in self._key_candidates(key): 45 | if isinstance(candidate, bytes): 46 | # ouch 47 | candidate = repr(candidate) 48 | try: 49 | return super().__getitem__(candidate) 50 | except KeyError as e: 51 | if error is None: 52 | error = e 53 | 54 | # fall through, re-raise the first KeyError 55 | raise error 56 | 57 | def __contains__(self, key): 58 | for candidate in self._key_candidates(key): 59 | if super().__contains__(candidate): 60 | return True 61 | return False 62 | 63 | 64 | form = FormFieldStorage() 65 | 66 | print('Filename:', form['up'].filename) 67 | print('Name:', form['name'].value) 68 | print('Content:', form['up'].file.read()) 69 | -------------------------------------------------------------------------------- /tests/cgiapp_data/ok.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | print('Content-type: text/html; charset=UTF-8') 3 | print('Status: 200 Okay') 4 | print('') 5 | print('This is the body') 6 | -------------------------------------------------------------------------------- /tests/cgiapp_data/stderr.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | print('Status: 500 Server Error') 4 | print('Content-type: text/html') 5 | print() 6 | print('There was an error') 7 | print('some data on the error', file=sys.stderr) 8 | -------------------------------------------------------------------------------- /tests/template.txt: -------------------------------------------------------------------------------- 1 | The templating language is fairly simple, just {{stuff}}. For 2 | example:: 3 | 4 | >>> from paste.util.template import Template, sub 5 | >>> sub('Hi {{name}}', name='Ian') 6 | 'Hi Ian' 7 | >>> Template('Hi {{repr(name)}}').substitute(name='Ian') 8 | "Hi 'Ian'" 9 | >>> Template('Hi {{name+1}}').substitute(name='Ian') # doctest: +IGNORE_EXCEPTION_DETAIL 10 | Traceback (most recent call last): 11 | ... 12 | TypeError: cannot concatenate 'str' and 'int' objects at line 1 column 6 13 | 14 | It also has Django-style piping:: 15 | 16 | >>> sub('Hi {{name|repr}}', name='Ian') 17 | "Hi 'Ian'" 18 | 19 | Note that None shows up as an empty string:: 20 | 21 | >>> sub('Hi {{name}}', name=None) 22 | 'Hi ' 23 | 24 | And if/elif/else:: 25 | 26 | >>> t = Template('{{if x}}{{y}}{{else}}{{z}}{{endif}}') 27 | >>> t.substitute(x=1, y=2, z=3) 28 | '2' 29 | >>> t.substitute(x=0, y=2, z=3) 30 | '3' 31 | >>> t = Template('{{if x > 0}}positive{{elif x < 0}}negative{{else}}zero{{endif}}') 32 | >>> t.substitute(x=1), t.substitute(x=-10), t.substitute(x=0) 33 | ('positive', 'negative', 'zero') 34 | 35 | Plus a for loop:: 36 | 37 | >>> t = Template('{{for i in x}}i={{i}}\n{{endfor}}') 38 | >>> t.substitute(x=range(3)) 39 | 'i=0\ni=1\ni=2\n' 40 | >>> t = Template('{{for a, b in sorted(z.items()):}}{{a}}={{b}},{{endfor}}') 41 | >>> t.substitute(z={1: 2, 3: 4}) 42 | '1=2,3=4,' 43 | >>> t = Template('{{for i in x}}{{if not i}}{{break}}' 44 | ... '{{endif}}{{i}} {{endfor}}') 45 | >>> t.substitute(x=[1, 2, 0, 3, 4]) 46 | '1 2 ' 47 | >>> t = Template('{{for i in x}}{{if not i}}{{continue}}' 48 | ... '{{endif}}{{i}} {{endfor}}') 49 | >>> t.substitute(x=[1, 2, 0, 3, 0, 4]) 50 | '1 2 3 4 ' 51 | 52 | Also Python blocks:: 53 | 54 | >>> sub('{{py:\nx=1\n}}{{x}}') 55 | '1' 56 | 57 | And some syntax errors:: 58 | 59 | >>> t = Template('{{if x}}', name='foo.html') 60 | Traceback (most recent call last): 61 | ... 62 | TemplateError: No {{endif}} at line 1 column 3 in foo.html 63 | >>> t = Template('{{for x}}', name='foo2.html') 64 | Traceback (most recent call last): 65 | ... 66 | TemplateError: Bad for (no "in") in 'x' at line 1 column 3 in foo2.html 67 | 68 | There's also an HTMLTemplate that uses HTMLisms:: 69 | 70 | >>> from paste.util.template import HTMLTemplate, sub_html, html 71 | >>> sub_html('hi {{name}}', name='') 72 | 'hi <foo>' 73 | 74 | But if you don't want quoting to happen you can do:: 75 | 76 | >>> sub_html('hi {{name}}', name=html('')) 77 | 'hi ' 78 | >>> sub_html('hi {{name|html}}', name='') 79 | 'hi ' 80 | 81 | Also a couple handy functions;: 82 | 83 | >>> t = HTMLTemplate('') 84 | >>> t.substitute(id=1, class_='foo') 85 | '' 86 | >>> t.substitute(id='with space', class_=None) 87 | '' 88 | 89 | There's a handyish looper thing you can also use in your templates (or 90 | in Python, but it's more useful in templates generally):: 91 | 92 | >>> from paste.util.looper import looper 93 | >>> seq = ['apple', 'asparagus', 'Banana', 'orange'] 94 | >>> for loop, item in looper(seq): 95 | ... if item == 'apple': 96 | ... assert loop.first 97 | ... elif item == 'orange': 98 | ... assert loop.last 99 | ... if loop.first_group(lambda i: i[0].upper()): 100 | ... print('%s:' % item[0].upper()) 101 | ... print("%s %s" % (loop.number, item)) 102 | A: 103 | 1 apple 104 | 2 asparagus 105 | B: 106 | 3 Banana 107 | O: 108 | 4 orange 109 | 110 | It will also strip out empty lines, when there is a line that only 111 | contains a directive/statement (if/for, etc):: 112 | 113 | >>> sub('{{if 1}}\n{{x}}\n{{endif}}\n', x=0) 114 | '0\n' 115 | >>> sub('{{if 1}}x={{x}}\n{{endif}}\n', x=1) 116 | 'x=1\n' 117 | >>> sub('{{if 1}}\nx={{x}}\n{{endif}}\n', x=1) 118 | 'x=1\n' 119 | 120 | Lastly, there is a special directive that will create a default value 121 | for a variable, if no value is given:: 122 | 123 | >>> sub('{{default x=1}}{{x}}', x=2) 124 | '2' 125 | >>> sub('{{default x=1}}{{x}}') 126 | '1' 127 | >>> # The normal case: 128 | >>> sub('{{x}}') # doctest: +IGNORE_EXCEPTION_DETAIL 129 | Traceback (most recent call last): 130 | ... 131 | NameError: name 'x' is not defined at line 1 column 3 132 | 133 | And comments work:: 134 | 135 | >>> sub('Test=x{{#whatever}}') 136 | 'Test=x' 137 | -------------------------------------------------------------------------------- /tests/test_auth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pasteorg/paste/28e461548498138b8814b243be432a04a7895dba/tests/test_auth/__init__.py -------------------------------------------------------------------------------- /tests/test_auth/test_auth_cookie.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Clark C. Evans 2 | # This module is part of the Python Paste Project and is released under 3 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 4 | 5 | from paste.auth import cookie 6 | from paste.wsgilib import raw_interactive, dump_environ 7 | from paste.response import header_value 8 | 9 | def build(application,setenv, *args, **kwargs): 10 | def setter(environ, start_response): 11 | save = environ['paste.auth.cookie'].append 12 | for (k,v) in setenv.items(): 13 | save(k) 14 | environ[k] = v 15 | return application(environ, start_response) 16 | return cookie.middleware(setter,*args,**kwargs) 17 | 18 | def test_noop(): 19 | app = build(dump_environ,{}) 20 | (status,headers,content,errors) = \ 21 | raw_interactive(app) 22 | assert not header_value(headers,'Set-Cookie') 23 | 24 | def test_basic(key='key', val='bingles'): 25 | app = build(dump_environ,{key:val}) 26 | (status,headers,content,errors) = \ 27 | raw_interactive(app) 28 | value = header_value(headers,'Set-Cookie') 29 | assert "Path=/;" in value 30 | assert "expires=" not in value 31 | cookie = value.split(";")[0] 32 | (status,headers,content,errors) = \ 33 | raw_interactive(app,{'HTTP_COOKIE': cookie}) 34 | expected = ("%s: %s" % (key,val.replace("\n","\n "))) 35 | expected = expected.encode('utf8') 36 | assert expected in content 37 | 38 | def test_roundtrip(): 39 | roundtrip = str('').join(map(chr, range(256))) 40 | test_basic(roundtrip,roundtrip) 41 | 42 | -------------------------------------------------------------------------------- /tests/test_auth/test_auth_digest.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Clark C. Evans 2 | # This module is part of the Python Paste Project and is released under 3 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 4 | 5 | from paste.auth.digest import digest_password, AuthDigestHandler 6 | from paste.wsgilib import raw_interactive 7 | from paste.httpexceptions import HTTPExceptionHandler 8 | from paste.httpheaders import AUTHORIZATION, WWW_AUTHENTICATE, REMOTE_USER 9 | import os 10 | 11 | def application(environ, start_response): 12 | content = REMOTE_USER(environ) 13 | start_response("200 OK",(('Content-Type', 'text/plain'), 14 | ('Content-Length', len(content)))) 15 | 16 | content = content.encode('utf8') 17 | return [content] 18 | 19 | realm = "tag:clarkevans.com,2005:testing" 20 | 21 | def backwords(environ, realm, username): 22 | """ dummy password hash, where user password is just reverse """ 23 | password = list(username) 24 | password.reverse() 25 | password = "".join(password) 26 | return digest_password(realm, username, password) 27 | 28 | application = AuthDigestHandler(application,realm,backwords) 29 | application = HTTPExceptionHandler(application) 30 | 31 | def check(username, password, path="/"): 32 | """ perform two-stage authentication to verify login """ 33 | (status,headers,content,errors) = \ 34 | raw_interactive(application,path, accept='text/html') 35 | assert status.startswith("401") 36 | challenge = WWW_AUTHENTICATE(headers) 37 | response = AUTHORIZATION(username=username, password=password, 38 | challenge=challenge, path=path) 39 | assert "Digest" in response and username in response 40 | (status,headers,content,errors) = \ 41 | raw_interactive(application,path, 42 | HTTP_AUTHORIZATION=response) 43 | if status.startswith("200"): 44 | return content 45 | if status.startswith("401"): 46 | return None 47 | assert False, "Unexpected Status: %s" % status 48 | 49 | def test_digest(): 50 | assert b'bing' == check("bing","gnib") 51 | assert check("bing","bad") is None 52 | 53 | # 54 | # The following code uses sockets to test the functionality, 55 | # to enable use: 56 | # 57 | # $ TEST_SOCKET=1 pytest 58 | 59 | 60 | if os.environ.get("TEST_SOCKET", ""): 61 | from urllib.error import HTTPError 62 | from urllib.request import build_opener, HTTPDigestAuthHandler 63 | from paste.debug.testserver import serve 64 | server = serve(application) 65 | 66 | def authfetch(username,password,path="/",realm=realm): 67 | server.accept(2) 68 | import socket 69 | socket.setdefaulttimeout(5) 70 | uri = ("http://%s:%s" % server.server_address) + path 71 | auth = HTTPDigestAuthHandler() 72 | auth.add_password(realm,uri,username,password) 73 | opener = build_opener(auth) 74 | result = opener.open(uri) 75 | return result.read() 76 | 77 | def test_success(): 78 | assert "bing" == authfetch('bing','gnib') 79 | 80 | def test_failure(): 81 | # urllib tries 5 more times before it gives up 82 | server.accept(5) 83 | try: 84 | authfetch('bing','wrong') 85 | assert False, "this should raise an exception" 86 | except HTTPError as e: 87 | assert e.code == 401 88 | 89 | def test_shutdown(): 90 | server.stop() 91 | 92 | -------------------------------------------------------------------------------- /tests/test_auth/test_auth_tkt.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import base64 3 | from http.cookies import SimpleCookie 4 | from paste.auth.auth_tkt import AuthTicket 5 | 6 | 7 | def test_auth_ticket_digest_and_cookie_value(): 8 | test_parameters = [ 9 | ( 10 | ( 11 | 'shared_secret', 12 | 'username', 13 | '0.0.0.0', # remote address 14 | ), 15 | { 16 | 'tokens': ['admin'], 17 | 'time': 1579782607 18 | }, 19 | b'731274bec45f6983c1f33bac8e8baf43', 20 | b'731274bec45f6983c1f33bac8e8baf435e2991cfusername!admin!', 21 | ), 22 | ( 23 | ( 24 | 'shared_secret', 25 | 'username', 26 | '0.0.0.0', 27 | ), 28 | { 29 | 'tokens': ['admin'], 30 | 'time': 1579782607, 31 | 'digest_algo': hashlib.sha512 32 | }, 33 | b'09e72a63c57ca4cfeca5fa578646deb2b27f7a461d91ad9aa32b85c93ef6fa7744ac006eb3d9a71a36375b5ab50cbae072bb3042e2a59198b7f314900cba4423', 34 | b'09e72a63c57ca4cfeca5fa578646deb2b27f7a461d91ad9aa32b85c93ef6fa7744ac006eb3d9a71a36375b5ab50cbae072bb3042e2a59198b7f314900cba44235e2991cfusername!admin!', 35 | ), 36 | ] 37 | 38 | for test_args, test_kwargs, expected_digest, expected_cookie_value in test_parameters: 39 | token = AuthTicket(*test_args, **test_kwargs) 40 | assert expected_digest == token.digest() 41 | assert expected_cookie_value == token.cookie_value() 42 | 43 | 44 | def test_auth_ticket_cookie(): 45 | test_parameters = [ 46 | ( 47 | ( 48 | 'shared_secret', 49 | 'username', 50 | '0.0.0.0', # remote address 51 | ), 52 | { 53 | 'tokens': ['admin'], 54 | 'time': 1579782607 55 | }, 56 | { 57 | 'name': 'auth_tkt', 58 | 'path': '/', 59 | 'secure': '', 60 | 'cookie_value': b'731274bec45f6983c1f33bac8e8baf435e2991cfusername!admin!' 61 | } 62 | ), 63 | ( 64 | ( 65 | 'shared_secret', 66 | 'username', 67 | '0.0.0.0', # remote address 68 | ), 69 | { 70 | 'tokens': ['admin'], 71 | 'time': 1579782607, 72 | 'secure': True 73 | }, 74 | { 75 | 'name': 'auth_tkt', 76 | 'path': '/', 77 | 'secure': 'true', 78 | 'cookie_value': b'731274bec45f6983c1f33bac8e8baf435e2991cfusername!admin!' 79 | } 80 | ), 81 | ( 82 | ( 83 | 'shared_secret', 84 | 'username', 85 | '0.0.0.0', # remote address 86 | ), 87 | { 88 | 'tokens': ['admin'], 89 | 'time': 1579782607, 90 | 'cookie_name': 'custom_cookie', 91 | 'secure': False 92 | }, 93 | { 94 | 'name': 'custom_cookie', 95 | 'path': '/', 96 | 'secure': '', 97 | 'cookie_value': b'731274bec45f6983c1f33bac8e8baf435e2991cfusername!admin!' 98 | } 99 | ), 100 | ] 101 | 102 | for test_args, test_kwargs, expected_values in test_parameters: 103 | token = AuthTicket(*test_args, **test_kwargs) 104 | expected_cookie = SimpleCookie() 105 | # import pdb; pdb.set_trace() 106 | expected_cookie_value = base64.b64encode(expected_values['cookie_value']) 107 | 108 | expected_cookie[expected_values['name']] = expected_cookie_value 109 | expected_cookie[expected_values['name']]['path'] = expected_values['path'] 110 | expected_cookie[expected_values['name']]['secure'] = expected_values['secure'] 111 | assert expected_cookie == token.cookie() 112 | -------------------------------------------------------------------------------- /tests/test_cgiapp.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import pytest 5 | 6 | from paste.cgiapp import CGIApplication, CGIError 7 | from paste.fixture import TestApp 8 | 9 | data_dir = os.path.join(os.path.dirname(__file__), 'cgiapp_data') 10 | 11 | pytestmark = pytest.mark.skipif( 12 | sys.platform == 'win32' or sys.platform.startswith('java'), 13 | reason="CGI scripts can't work on Windows or Jython") 14 | 15 | 16 | # Ensure the CGI scripts are called with the same python interpreter. 17 | # Put a symlink to the interpreter executable into the path... 18 | def setup_module(): 19 | global oldpath, pyexelink 20 | oldpath = os.environ.get('PATH', None) 21 | os.environ['PATH'] = data_dir + os.path.pathsep + oldpath 22 | pyexelink = os.path.join(data_dir, "python") 23 | try: 24 | os.unlink(pyexelink) 25 | except OSError: 26 | pass 27 | os.symlink(sys.executable, pyexelink) 28 | 29 | # ... and clean up again. 30 | def teardown_module(): 31 | global oldpath, pyexelink 32 | os.unlink(pyexelink) 33 | if oldpath is not None: 34 | os.environ['PATH'] = oldpath 35 | else: 36 | del os.environ['PATH'] 37 | 38 | def test_ok(): 39 | app = TestApp(CGIApplication({}, script='ok.cgi', path=[data_dir])) 40 | res = app.get('') 41 | assert res.header('content-type') == 'text/html; charset=UTF-8' 42 | assert res.full_status == '200 Okay' 43 | assert 'This is the body' in res 44 | 45 | def test_form(): 46 | app = TestApp(CGIApplication({}, script='form.cgi', path=[data_dir])) 47 | res = app.post('', params={'name': b'joe'}, 48 | upload_files=[('up', 'file.txt', b'x'*10000)]) 49 | assert 'file.txt' in res 50 | assert 'joe' in res 51 | assert 'x'*10000 in res 52 | 53 | def test_error(): 54 | app = TestApp(CGIApplication({}, script='error.cgi', path=[data_dir])) 55 | pytest.raises(CGIError, app.get, '', status=500) 56 | 57 | def test_stderr(): 58 | app = TestApp(CGIApplication({}, script='stderr.cgi', path=[data_dir])) 59 | res = app.get('', expect_errors=True) 60 | assert res.status == 500 61 | assert 'error' in res 62 | assert 'some data' in res.errors 63 | -------------------------------------------------------------------------------- /tests/test_cgitb_catcher.py: -------------------------------------------------------------------------------- 1 | from paste.fixture import TestApp 2 | from paste.cgitb_catcher import CgitbMiddleware 3 | from paste import lint 4 | from .test_exceptions.test_error_middleware import clear_middleware 5 | 6 | def do_request(app, expect_status=500): 7 | app = lint.middleware(app) 8 | app = CgitbMiddleware(app, {}, display=True) 9 | app = clear_middleware(app) 10 | testapp = TestApp(app) 11 | res = testapp.get('', status=expect_status, 12 | expect_errors=True) 13 | return res 14 | 15 | 16 | ############################################################ 17 | ## Applications that raise exceptions 18 | ############################################################ 19 | 20 | def bad_app(): 21 | "No argument list!" 22 | return None 23 | 24 | def start_response_app(environ, start_response): 25 | "raise error before start_response" 26 | raise ValueError("hi") 27 | 28 | def after_start_response_app(environ, start_response): 29 | start_response("200 OK", [('Content-type', 'text/plain')]) 30 | raise ValueError('error2') 31 | 32 | def iter_app(environ, start_response): 33 | start_response("200 OK", [('Content-type', 'text/plain')]) 34 | return yielder([b'this', b' is ', b' a', None]) 35 | 36 | def yielder(args): 37 | for arg in args: 38 | if arg is None: 39 | raise ValueError("None raises error") 40 | yield arg 41 | 42 | ############################################################ 43 | ## Tests 44 | ############################################################ 45 | 46 | def test_makes_exception(): 47 | res = do_request(bad_app) 48 | print(res) 49 | assert 'bad_app() takes 0 positional arguments but 2 were given' in res 50 | assert 'iterator = application(environ, start_response_wrapper)' in res 51 | assert 'lint.py' in res 52 | assert 'cgitb_catcher.py' in res 53 | 54 | def test_start_res(): 55 | res = do_request(start_response_app) 56 | print(res) 57 | assert 'ValueError: hi' in res 58 | assert 'test_cgitb_catcher.py' in res 59 | assert 'line 26, in start_response_app' in res 60 | 61 | def test_after_start(): 62 | res = do_request(after_start_response_app, 200) 63 | print(res) 64 | assert 'ValueError: error2' in res 65 | assert 'line 30' in res 66 | 67 | def test_iter_app(): 68 | res = do_request(iter_app, 200) 69 | print(res) 70 | assert 'None raises error' in res 71 | assert 'yielder' in res 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | # (c) 2007 Philip Jenvey; written for Paste (http://pythonpaste.org) 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 | 4 | import pytest 5 | 6 | from paste.config import CONFIG, ConfigMiddleware 7 | from paste.fixture import TestApp 8 | 9 | test_key = 'test key' 10 | 11 | def reset_config(): 12 | while True: 13 | try: 14 | CONFIG._pop_object() 15 | except IndexError: 16 | break 17 | 18 | def app_with_config(environ, start_response): 19 | start_response('200 OK', [('Content-type','text/plain')]) 20 | lines = ['Variable is: %s\n' % CONFIG[test_key], 21 | 'Variable is (in environ): %s' % environ['paste.config'][test_key]] 22 | lines = [line.encode('utf8') for line in lines] 23 | return lines 24 | 25 | class NestingAppWithConfig: 26 | def __init__(self, app): 27 | self.app = app 28 | 29 | def __call__(self, environ, start_response): 30 | response = self.app(environ, start_response) 31 | assert isinstance(response, list) 32 | supplement = ['Nesting variable is: %s' % CONFIG[test_key], 33 | 'Nesting variable is (in environ): %s' % \ 34 | environ['paste.config'][test_key]] 35 | supplement = [line.encode('utf8') for line in supplement] 36 | response.extend(supplement) 37 | return response 38 | 39 | def test_request_config(): 40 | try: 41 | config = {test_key: 'test value'} 42 | app = ConfigMiddleware(app_with_config, config) 43 | res = TestApp(app).get('/') 44 | assert 'Variable is: test value' in res 45 | assert 'Variable is (in environ): test value' in res 46 | finally: 47 | reset_config() 48 | 49 | def test_request_config_multi(): 50 | try: 51 | config = {test_key: 'test value'} 52 | app = ConfigMiddleware(app_with_config, config) 53 | config = {test_key: 'nesting value'} 54 | app = ConfigMiddleware(NestingAppWithConfig(app), config) 55 | res = TestApp(app).get('/') 56 | assert 'Variable is: test value' in res 57 | assert 'Variable is (in environ): test value' in res 58 | assert 'Nesting variable is: nesting value' in res 59 | print(res) 60 | assert 'Nesting variable is (in environ): nesting value' in res 61 | finally: 62 | reset_config() 63 | 64 | def test_process_config(request_app=test_request_config): 65 | try: 66 | process_config = {test_key: 'bar', 'process_var': 'foo'} 67 | CONFIG.push_process_config(process_config) 68 | 69 | assert CONFIG[test_key] == 'bar' 70 | assert CONFIG['process_var'] == 'foo' 71 | 72 | request_app() 73 | 74 | assert CONFIG[test_key] == 'bar' 75 | assert CONFIG['process_var'] == 'foo' 76 | CONFIG.pop_process_config() 77 | 78 | pytest.raises(AttributeError, lambda: 'process_var' not in CONFIG) 79 | pytest.raises(IndexError, CONFIG.pop_process_config) 80 | finally: 81 | reset_config() 82 | 83 | def test_process_config_multi(): 84 | test_process_config(test_request_config_multi) 85 | -------------------------------------------------------------------------------- /tests/test_doctests.py: -------------------------------------------------------------------------------- 1 | import doctest 2 | import os 3 | 4 | import pytest 5 | 6 | from paste.util.import_string import simple_import 7 | 8 | filenames = [ 9 | 'tests/template.txt', 10 | ] 11 | 12 | modules = [ 13 | 'paste.util.template', 14 | 'paste.util.looper', 15 | # This one opens up httpserver, which is bad: 16 | #'paste.auth.cookie', 17 | #'paste.auth.multi', 18 | #'paste.auth.digest', 19 | #'paste.auth.basic', 20 | #'paste.auth.form', 21 | #'paste.progress', 22 | 'paste.exceptions.serial_number_generator', 23 | 'paste.evalexception.evalcontext', 24 | 'paste.util.dateinterval', 25 | 'paste.util.quoting', 26 | 'paste.wsgilib', 27 | 'paste.url', 28 | 'paste.request', 29 | ] 30 | 31 | options = doctest.ELLIPSIS | doctest.REPORT_ONLY_FIRST_FAILURE 32 | options |= doctest.IGNORE_EXCEPTION_DETAIL 33 | 34 | 35 | @pytest.mark.parametrize('filename', filenames) 36 | def test_doctests(filename): 37 | filename = os.path.join( 38 | os.path.dirname(os.path.dirname(__file__)), 39 | filename) 40 | failure, total = doctest.testfile( 41 | filename, module_relative=False, 42 | optionflags=options) 43 | assert not failure, "Failure in %r" % filename 44 | 45 | 46 | @pytest.mark.parametrize('module', modules) 47 | def test_doctest_mods(module): 48 | module = simple_import(module) 49 | failure, total = doctest.testmod( 50 | module, optionflags=options) 51 | assert not failure, "Failure in %r" % module 52 | 53 | 54 | if __name__ == '__main__': 55 | import sys 56 | import doctest 57 | args = sys.argv[1:] 58 | if not args: 59 | args = filenames 60 | for filename in args: 61 | doctest.testfile(filename, module_relative=False) 62 | -------------------------------------------------------------------------------- /tests/test_errordocument.py: -------------------------------------------------------------------------------- 1 | from paste.errordocument import forward 2 | from paste.fixture import TestApp 3 | from paste.recursive import RecursiveMiddleware 4 | 5 | def simple_app(environ, start_response): 6 | start_response("200 OK", [('Content-type', 'text/plain')]) 7 | return [b'requested page returned'] 8 | 9 | def not_found_app(environ, start_response): 10 | start_response("404 Not found", [('Content-type', 'text/plain')]) 11 | return [b'requested page returned'] 12 | 13 | def test_ok(): 14 | app = TestApp(simple_app) 15 | res = app.get('') 16 | assert res.header('content-type') == 'text/plain' 17 | assert res.full_status == '200 OK' 18 | assert 'requested page returned' in res 19 | 20 | def error_docs_app(environ, start_response): 21 | if environ['PATH_INFO'] == '/not_found': 22 | start_response("404 Not found", [('Content-type', 'text/plain')]) 23 | return [b'Not found'] 24 | elif environ['PATH_INFO'] == '/error': 25 | start_response("200 OK", [('Content-type', 'text/plain')]) 26 | return [b'Page not found'] 27 | else: 28 | return simple_app(environ, start_response) 29 | 30 | def test_error_docs_app(): 31 | app = TestApp(error_docs_app) 32 | res = app.get('') 33 | assert res.header('content-type') == 'text/plain' 34 | assert res.full_status == '200 OK' 35 | assert 'requested page returned' in res 36 | res = app.get('/error') 37 | assert res.header('content-type') == 'text/plain' 38 | assert res.full_status == '200 OK' 39 | assert 'Page not found' in res 40 | res = app.get('/not_found', status=404) 41 | assert res.header('content-type') == 'text/plain' 42 | assert res.full_status == '404 Not found' 43 | assert 'Not found' in res 44 | 45 | def test_forward(): 46 | app = forward(error_docs_app, codes={404:'/error'}) 47 | app = TestApp(RecursiveMiddleware(app)) 48 | res = app.get('') 49 | assert res.header('content-type') == 'text/plain' 50 | assert res.full_status == '200 OK' 51 | assert 'requested page returned' in res 52 | res = app.get('/error') 53 | assert res.header('content-type') == 'text/plain' 54 | assert res.full_status == '200 OK' 55 | assert 'Page not found' in res 56 | res = app.get('/not_found', status=404) 57 | assert res.header('content-type') == 'text/plain' 58 | assert res.full_status == '404 Not found' 59 | # Note changed response 60 | assert 'Page not found' in res 61 | 62 | def auth_required_app(environ, start_response): 63 | start_response('401 Unauthorized', [('content-type', 'text/plain'), ('www-authenticate', 'Basic realm="Foo"')]) 64 | return ['Sign in!'] 65 | 66 | def auth_docs_app(environ, start_response): 67 | if environ['PATH_INFO'] == '/auth': 68 | return auth_required_app(environ, start_response) 69 | elif environ['PATH_INFO'] == '/auth_doc': 70 | start_response("200 OK", [('Content-type', 'text/html')]) 71 | return [b'Login!'] 72 | else: 73 | return simple_app(environ, start_response) 74 | 75 | def test_auth_docs_app(): 76 | wsgi_app = forward(auth_docs_app, codes={401: '/auth_doc'}) 77 | app = TestApp(wsgi_app) 78 | res = app.get('/auth_doc') 79 | assert res.header('content-type') == 'text/html' 80 | res = app.get('/auth', status=401) 81 | assert res.header('content-type') == 'text/html' 82 | assert res.header('www-authenticate') == 'Basic realm="Foo"' 83 | assert res.body == b'Login!' 84 | 85 | def test_bad_error(): 86 | def app(environ, start_response): 87 | start_response('404 Not Found', [('content-type', 'text/plain')]) 88 | return ['not found'] 89 | app = forward(app, {404: '/404.html'}) 90 | app = TestApp(app) 91 | resp = app.get('/test', expect_errors=True) 92 | print(resp) 93 | -------------------------------------------------------------------------------- /tests/test_exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /tests/test_exceptions/test_error_middleware.py: -------------------------------------------------------------------------------- 1 | from paste.fixture import TestApp 2 | from paste.exceptions.errormiddleware import ErrorMiddleware 3 | from paste import lint 4 | from paste.util.quoting import strip_html 5 | # 6 | # For some strange reason, these 4 lines cannot be removed or the regression 7 | # test breaks; is it counting the number of lines in the file somehow? 8 | # 9 | 10 | def do_request(app, expect_status=500): 11 | app = lint.middleware(app) 12 | app = ErrorMiddleware(app, {}, debug=True) 13 | app = clear_middleware(app) 14 | testapp = TestApp(app) 15 | res = testapp.get('', status=expect_status, 16 | expect_errors=True) 17 | return res 18 | 19 | def clear_middleware(app): 20 | """ 21 | The fixture sets paste.throw_errors, which suppresses exactly what 22 | we want to test in this case. This wrapper also strips exc_info 23 | on the *first* call to start_response (but not the second, or 24 | subsequent calls. 25 | """ 26 | def clear_throw_errors(environ, start_response): 27 | headers_sent = [] 28 | def replacement(status, headers, exc_info=None): 29 | if headers_sent: 30 | return start_response(status, headers, exc_info) 31 | headers_sent.append(True) 32 | return start_response(status, headers) 33 | if 'paste.throw_errors' in environ: 34 | del environ['paste.throw_errors'] 35 | return app(environ, replacement) 36 | return clear_throw_errors 37 | 38 | 39 | ############################################################ 40 | ## Applications that raise exceptions 41 | ############################################################ 42 | 43 | def bad_app(): 44 | "No argument list!" 45 | return None 46 | 47 | def unicode_bad_app(environ, start_response): 48 | raise ValueError(u"\u1000") 49 | 50 | def start_response_app(environ, start_response): 51 | "raise error before start_response" 52 | raise ValueError("hi") 53 | 54 | def after_start_response_app(environ, start_response): 55 | start_response("200 OK", [('Content-type', 'text/plain')]) 56 | raise ValueError('error2') 57 | 58 | def iter_app(environ, start_response): 59 | start_response("200 OK", [('Content-type', 'text/plain')]) 60 | return yielder([b'this', b' is ', b' a', None]) 61 | 62 | def yielder(args): 63 | for arg in args: 64 | if arg is None: 65 | raise ValueError("None raises error") 66 | yield arg 67 | 68 | ############################################################ 69 | ## Tests 70 | ############################################################ 71 | 72 | def test_makes_exception(): 73 | res = do_request(bad_app) 74 | assert 'A fun and happy message.

' in \ 76 | e.html({'ping': 'fun', 'pong': 'happy'}) 77 | 78 | def test_redapp(): 79 | """ check that redirect returns the correct, expected results """ 80 | saved = [] 81 | def saveit(status, headers, exc_info = None): 82 | saved.append((status,headers)) 83 | def redapp(environ, start_response): 84 | raise HTTPFound("/bing/foo") 85 | app = HTTPExceptionHandler(redapp) 86 | result = list(app({'HTTP_ACCEPT': 'text/html'},saveit)) 87 | assert b'
' in result[0] 88 | assert "302 Found" == saved[0][0] 89 | assert "text/html; charset=utf8" == header_value(saved[0][1], 'content-type') 90 | assert "/bing/foo" == header_value(saved[0][1],'location') 91 | result = list(app({'HTTP_ACCEPT': 'text/plain'},saveit)) 92 | assert "text/plain; charset=utf8" == header_value(saved[1][1],'content-type') 93 | assert "/bing/foo" == header_value(saved[1][1],'location') 94 | 95 | def test_misc(): 96 | assert get_exception(301) == HTTPMovedPermanently 97 | redirect = HTTPFound("/some/path") 98 | assert isinstance(redirect,HTTPException) 99 | assert isinstance(redirect,HTTPRedirection) 100 | assert not isinstance(redirect,HTTPError) 101 | notfound = HTTPNotFound() 102 | assert isinstance(notfound,HTTPException) 103 | assert isinstance(notfound,HTTPError) 104 | assert isinstance(notfound,HTTPClientError) 105 | assert not isinstance(notfound,HTTPServerError) 106 | notimpl = HTTPNotImplemented() 107 | assert isinstance(notimpl,HTTPException) 108 | assert isinstance(notimpl,HTTPError) 109 | assert isinstance(notimpl,HTTPServerError) 110 | assert not isinstance(notimpl,HTTPClientError) 111 | 112 | -------------------------------------------------------------------------------- /tests/test_exceptions/test_reporter.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from paste.exceptions.reporter import LogReporter 4 | from paste.exceptions import collector 5 | 6 | def setup_file(fn, content=None): 7 | dir = os.path.join(os.path.dirname(__file__), 'reporter_output') 8 | fn = os.path.join(dir, fn) 9 | if os.path.exists(dir): 10 | if os.path.exists(fn): 11 | os.unlink(fn) 12 | else: 13 | os.mkdir(dir) 14 | if content is not None: 15 | f = open(fn, 'wb') 16 | f.write(content) 17 | f.close() 18 | return fn 19 | 20 | def test_logger(): 21 | fn = setup_file('test_logger.log') 22 | rep = LogReporter( 23 | filename=fn, 24 | show_hidden_frames=False) 25 | try: 26 | int('a') 27 | except Exception: 28 | exc_data = collector.collect_exception(*sys.exc_info()) 29 | else: 30 | assert 0 31 | rep.report(exc_data) 32 | content = open(fn).read() 33 | assert len(content.splitlines()) == 4 34 | assert 'ValueError' in content 35 | assert 'int' in content 36 | assert 'test_reporter.py' in content 37 | assert 'test_logger' in content 38 | 39 | try: 40 | 1 / 0 41 | except Exception: 42 | exc_data = collector.collect_exception(*sys.exc_info()) 43 | else: 44 | assert 0 45 | rep.report(exc_data) 46 | content = open(fn).read() 47 | print(content) 48 | assert len(content.splitlines()) == 8 49 | assert 'ZeroDivisionError' in content 50 | 51 | -------------------------------------------------------------------------------- /tests/test_fixture.py: -------------------------------------------------------------------------------- 1 | from paste.debug.debugapp import SimpleApplication, SlowConsumer 2 | from paste.fixture import TestApp 3 | from paste.wsgiwrappers import WSGIRequest 4 | 5 | 6 | def test_fixture(): 7 | app = TestApp(SimpleApplication()) 8 | res = app.get('/', params={'a': ['1', '2']}) 9 | assert (res.request.environ['QUERY_STRING'] == 10 | 'a=1&a=2') 11 | res = app.put('/') 12 | assert (res.request.environ['REQUEST_METHOD'] == 13 | 'PUT') 14 | res = app.delete('/') 15 | assert (res.request.environ['REQUEST_METHOD'] == 16 | 'DELETE') 17 | class FakeDict: 18 | def items(self): 19 | return [('a', '10'), ('a', '20')] 20 | res = app.post('/params', params=FakeDict()) 21 | 22 | # test multiple cookies in one request 23 | app.cookies['one'] = 'first'; 24 | app.cookies['two'] = 'second'; 25 | app.cookies['three'] = ''; 26 | res = app.get('/') 27 | hc = res.request.environ['HTTP_COOKIE'].split('; '); 28 | assert ('one=first' in hc) 29 | assert ('two=second' in hc) 30 | assert ('three=' in hc) 31 | 32 | 33 | def test_fixture_form(): 34 | app = TestApp(SlowConsumer()) 35 | res = app.get('/') 36 | form = res.forms[0] 37 | assert 'file' in form.fields 38 | assert form.action == '' 39 | 40 | 41 | def test_fixture_form_end(): 42 | def response(environ, start_response): 43 | body = b"
sm\xc3\xb6rebr\xc3\xb6
" 44 | start_response("200 OK", [('Content-Type', 'text/html'), 45 | ('Content-Length', str(len(body)))]) 46 | return [body] 47 | TestApp(response).get('/') 48 | 49 | def test_params_and_upload_files(): 50 | class PostApp: 51 | def __call__(self, environ, start_response): 52 | start_response("204 No content", []) 53 | self.request = WSGIRequest(environ) 54 | return [b''] 55 | post_app = PostApp() 56 | app = TestApp(post_app) 57 | app.post( 58 | '/', 59 | params={'param1': 'a', 'param2': 'b'}, 60 | upload_files=[ 61 | ('file1', 'myfile.txt', b'data1'), 62 | ('file2', b'yourfile.txt', b'data2'), 63 | ], 64 | ) 65 | params = post_app.request.params 66 | assert len(params) == 4 67 | assert params['param1'] == 'a' 68 | assert params['param2'] == 'b' 69 | assert params['file1'].value == b'data1' 70 | assert params['file1'].filename == 'myfile.txt' 71 | assert params['file2'].value == b'data2' 72 | assert params['file2'].filename == 'yourfile.txt' 73 | 74 | def test_unicode_path(): 75 | app = TestApp(SimpleApplication()) 76 | app.get(u"/?") 77 | app.post(u"/?") 78 | app.put(u"/?") 79 | app.delete(u"/?") 80 | -------------------------------------------------------------------------------- /tests/test_grantip.py: -------------------------------------------------------------------------------- 1 | from paste.auth import grantip 2 | from paste.fixture import TestApp 3 | 4 | def _make_app(): 5 | def application(environ, start_response): 6 | start_response('200 OK', [('content-type', 'text/plain')]) 7 | lines = [ 8 | str(environ.get('REMOTE_USER')), 9 | ':', 10 | str(environ.get('REMOTE_USER_TOKENS')), 11 | ] 12 | lines = [line.encode('utf8') for line in lines] 13 | return lines 14 | ip_map = { 15 | '127.0.0.1': (None, 'system'), 16 | '192.168.0.0/16': (None, 'worker'), 17 | '192.168.0.5<->192.168.0.8': ('bob', 'editor'), 18 | '192.168.0.8': ('__remove__', '-worker'), 19 | } 20 | app = grantip.GrantIPMiddleware(application, ip_map) 21 | app = TestApp(app) 22 | return app 23 | 24 | def test_req(): 25 | app = _make_app() 26 | def doit(remote_addr): 27 | res = app.get('/', extra_environ={'REMOTE_ADDR': remote_addr}) 28 | return res.body 29 | assert doit('127.0.0.1') == b'None:system' 30 | assert doit('192.168.15.12') == b'None:worker' 31 | assert doit('192.168.0.4') == b'None:worker' 32 | result = doit('192.168.0.5') 33 | assert result.startswith(b'bob:') 34 | assert b'editor' in result and b'worker' in result 35 | assert result.count(b',') == 1 36 | assert doit('192.168.0.8') == b'None:editor' 37 | -------------------------------------------------------------------------------- /tests/test_gzipper.py: -------------------------------------------------------------------------------- 1 | from paste.fixture import TestApp 2 | from paste.gzipper import middleware 3 | import gzip 4 | import io 5 | 6 | def simple_app(environ, start_response): 7 | start_response('200 OK', 8 | [('content-type', 'text/plain'), 9 | ('content-length', '0')]) 10 | return [b'this is a test'] if environ['REQUEST_METHOD'] != 'HEAD' else [] 11 | 12 | wsgi_app = middleware(simple_app) 13 | app = TestApp(wsgi_app) 14 | 15 | def test_gzip(): 16 | res = app.get( 17 | '/', extra_environ=dict(HTTP_ACCEPT_ENCODING='gzip')) 18 | assert int(res.header('content-length')) == len(res.body) 19 | assert res.body != b'this is a test' 20 | actual = gzip.GzipFile(fileobj=io.BytesIO(res.body)).read() 21 | assert actual == b'this is a test' 22 | 23 | def test_gzip_head(): 24 | res = app.head( 25 | '/', extra_environ=dict(HTTP_ACCEPT_ENCODING='gzip')) 26 | assert int(res.header('content-length')) == 0 27 | assert res.body == b'' 28 | -------------------------------------------------------------------------------- /tests/test_httpserver.py: -------------------------------------------------------------------------------- 1 | import email 2 | import io 3 | import socket 4 | 5 | from paste.httpserver import LimitedLengthFile, WSGIHandler, serve 6 | 7 | 8 | class MockServer: 9 | server_address = ('127.0.0.1', 80) 10 | 11 | 12 | class MockSocket: 13 | def makefile(self, mode, bufsize): 14 | return io.StringIO() 15 | 16 | 17 | def test_environ(): 18 | mock_socket = MockSocket() 19 | mock_client_address = '1.2.3.4' 20 | mock_server = MockServer() 21 | 22 | wsgi_handler = WSGIHandler(mock_socket, mock_client_address, mock_server) 23 | wsgi_handler.command = 'GET' 24 | wsgi_handler.path = '/path' 25 | wsgi_handler.request_version = 'HTTP/1.0' 26 | wsgi_handler.headers = email.message_from_string('Host: mywebsite') 27 | 28 | wsgi_handler.wsgi_setup() 29 | 30 | assert wsgi_handler.wsgi_environ['HTTP_HOST'] == 'mywebsite' 31 | 32 | 33 | def test_environ_with_multiple_values(): 34 | mock_socket = MockSocket() 35 | mock_client_address = '1.2.3.4' 36 | mock_server = MockServer() 37 | 38 | wsgi_handler = WSGIHandler(mock_socket, mock_client_address, mock_server) 39 | wsgi_handler.command = 'GET' 40 | wsgi_handler.path = '/path' 41 | wsgi_handler.request_version = 'HTTP/1.0' 42 | wsgi_handler.headers = email.message_from_string('Host: host1\nHost: host2') 43 | 44 | wsgi_handler.wsgi_setup() 45 | 46 | assert wsgi_handler.wsgi_environ['HTTP_HOST'] == 'host1,host2' 47 | 48 | 49 | def test_limited_length_file(): 50 | backing = io.BytesIO(b'0123456789') 51 | f = LimitedLengthFile(backing, 9) 52 | assert f.tell() == 0 53 | assert f.read() == b'012345678' 54 | assert f.tell() == 9 55 | assert f.read() == b'' 56 | 57 | def test_limited_length_file_tell_on_socket(): 58 | backing_read, backing_write = socket.socketpair() 59 | f = LimitedLengthFile(backing_read.makefile('rb'), 10) 60 | backing_write.send(b'0123456789') 61 | backing_write.close() 62 | assert f.tell() == 0 63 | assert f.read(1) == b'0' 64 | assert f.tell() == 1 65 | assert f.read() == b'123456789' 66 | assert f.tell() == 10 67 | backing_read.close() 68 | 69 | 70 | def test_address_family_v4(): 71 | #ipv4 72 | app = None 73 | host = '127.0.0.1' 74 | port = '9090' 75 | 76 | svr = serve(app, host=host, port=port, start_loop=False, use_threadpool=False) 77 | 78 | af = svr.address_family 79 | addr = svr.server_address 80 | p = svr.server_port 81 | 82 | svr.server_close() 83 | 84 | assert (af == socket.AF_INET) 85 | assert (addr[0] == '127.0.0.1') 86 | assert (str(p) == port) 87 | 88 | 89 | def test_address_family_v4_host_and_port(): 90 | #ipv4 91 | app = None 92 | host = '127.0.0.1:9091' 93 | 94 | svr = serve(app, host=host, start_loop=False, use_threadpool=False) 95 | 96 | af = svr.address_family 97 | addr = svr.server_address 98 | p = svr.server_port 99 | 100 | svr.server_close() 101 | 102 | assert (af == socket.AF_INET) 103 | assert (addr[0] == '127.0.0.1') 104 | assert (str(p) == '9091') 105 | 106 | def test_address_family_v6(): 107 | #ipv6 108 | app = None 109 | host = '[::1]' 110 | port = '9090' 111 | 112 | try: 113 | svr = serve(app, host=host, port=port, start_loop=False, use_threadpool=False) 114 | 115 | af = svr.address_family 116 | addr = svr.server_address 117 | p = svr.server_port 118 | 119 | svr.server_close() 120 | 121 | assert (af == socket.AF_INET6) 122 | assert (addr[0] == '::1') 123 | assert (str(p) == port) 124 | except (socket.error, OSError) as err: 125 | # v6 support not available in this OS, pass the test 126 | assert True 127 | -------------------------------------------------------------------------------- /tests/test_import_string.py: -------------------------------------------------------------------------------- 1 | from paste.util.import_string import eval_import, simple_import 2 | import sys 3 | import os 4 | 5 | def test_simple(): 6 | for func in eval_import, simple_import: 7 | assert func('sys') is sys 8 | assert func('sys.version') is sys.version 9 | assert func('os.path.join') is os.path.join 10 | 11 | def test_complex(): 12 | assert eval_import('sys:version') is sys.version 13 | assert eval_import('os:getcwd()') == os.getcwd() 14 | assert (eval_import('sys:version.split()[0]') == 15 | sys.version.split()[0]) 16 | 17 | -------------------------------------------------------------------------------- /tests/test_profilemiddleware.py: -------------------------------------------------------------------------------- 1 | from paste.fixture import TestApp 2 | try: 3 | from paste.debug.profile import ProfileMiddleware, profile_decorator 4 | disable = False 5 | except ImportError: 6 | disable = True 7 | 8 | if not disable: 9 | def simple_app(environ, start_response): 10 | start_response('200 OK', [('content-type', 'text/html')]) 11 | return ['all ok'] 12 | 13 | def long_func(): 14 | for i in range(1000): 15 | pass 16 | return 'test' 17 | 18 | def test_profile(): 19 | app = TestApp(ProfileMiddleware(simple_app, {})) 20 | res = app.get('/') 21 | # The original app: 22 | res.mustcontain('all ok') 23 | # The profile information: 24 | res.mustcontain('httpbin.org' in res 18 | -------------------------------------------------------------------------------- /tests/test_recursive.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from paste.fixture import TestApp 3 | from paste.recursive import RecursiveMiddleware, ForwardRequestException 4 | from .test_errordocument import simple_app 5 | 6 | def error_docs_app(environ, start_response): 7 | if environ['PATH_INFO'] == '/not_found': 8 | start_response("404 Not found", [('Content-type', 'text/plain')]) 9 | return [b'Not found'] 10 | elif environ['PATH_INFO'] == '/error': 11 | start_response("200 OK", [('Content-type', 'text/plain')]) 12 | return [b'Page not found'] 13 | elif environ['PATH_INFO'] == '/recurse': 14 | raise ForwardRequestException('/recurse') 15 | else: 16 | return simple_app(environ, start_response) 17 | 18 | class Middleware: 19 | def __init__(self, app, url='/error'): 20 | self.app = app 21 | self.url = url 22 | def __call__(self, environ, start_response): 23 | raise ForwardRequestException(self.url) 24 | 25 | def forward(app): 26 | app = TestApp(RecursiveMiddleware(app)) 27 | res = app.get('') 28 | assert res.header('content-type') == 'text/plain' 29 | assert res.full_status == '200 OK' 30 | assert 'requested page returned' in res 31 | res = app.get('/error') 32 | assert res.header('content-type') == 'text/plain' 33 | assert res.full_status == '200 OK' 34 | assert 'Page not found' in res 35 | res = app.get('/not_found') 36 | assert res.header('content-type') == 'text/plain' 37 | assert res.full_status == '200 OK' 38 | assert 'Page not found' in res 39 | try: 40 | res = app.get('/recurse') 41 | except AssertionError as e: 42 | if str(e).startswith('Forwarding loop detected'): 43 | pass 44 | else: 45 | raise AssertionError('Failed to detect forwarding loop') 46 | 47 | def test_ForwardRequest_url(): 48 | class TestForwardRequestMiddleware(Middleware): 49 | def __call__(self, environ, start_response): 50 | if environ['PATH_INFO'] != '/not_found': 51 | return self.app(environ, start_response) 52 | raise ForwardRequestException(self.url) 53 | forward(TestForwardRequestMiddleware(error_docs_app)) 54 | 55 | def test_ForwardRequest_environ(): 56 | class TestForwardRequestMiddleware(Middleware): 57 | def __call__(self, environ, start_response): 58 | if environ['PATH_INFO'] != '/not_found': 59 | return self.app(environ, start_response) 60 | environ['PATH_INFO'] = self.url 61 | raise ForwardRequestException(environ=environ) 62 | forward(TestForwardRequestMiddleware(error_docs_app)) 63 | 64 | def test_ForwardRequest_factory(): 65 | 66 | from paste.errordocument import StatusKeeper 67 | 68 | class TestForwardRequestMiddleware(Middleware): 69 | def __call__(self, environ, start_response): 70 | if environ['PATH_INFO'] != '/not_found': 71 | return self.app(environ, start_response) 72 | environ['PATH_INFO'] = self.url 73 | def factory(app): 74 | return StatusKeeper(app, status='404 Not Found', url='/error', headers=[]) 75 | raise ForwardRequestException(factory=factory) 76 | 77 | app = TestForwardRequestMiddleware(error_docs_app) 78 | app = TestApp(RecursiveMiddleware(app)) 79 | res = app.get('') 80 | assert res.header('content-type') == 'text/plain' 81 | assert res.full_status == '200 OK' 82 | assert 'requested page returned' in res 83 | res = app.get('/error') 84 | assert res.header('content-type') == 'text/plain' 85 | assert res.full_status == '200 OK' 86 | assert 'Page not found' in res 87 | res = app.get('/not_found', status=404) 88 | assert res.header('content-type') == 'text/plain' 89 | assert res.full_status == '404 Not Found' # Different status 90 | assert 'Page not found' in res 91 | try: 92 | res = app.get('/recurse') 93 | except AssertionError as e: 94 | if str(e).startswith('Forwarding loop detected'): 95 | pass 96 | else: 97 | raise AssertionError('Failed to detect forwarding loop') 98 | 99 | # Test Deprecated Code 100 | def test_ForwardRequestException(): 101 | class TestForwardRequestExceptionMiddleware(Middleware): 102 | def __call__(self, environ, start_response): 103 | if environ['PATH_INFO'] != '/not_found': 104 | return self.app(environ, start_response) 105 | raise ForwardRequestException(path_info=self.url) 106 | 107 | with pytest.warns(DeprecationWarning): 108 | forward(TestForwardRequestExceptionMiddleware(error_docs_app)) 109 | -------------------------------------------------------------------------------- /tests/test_request.py: -------------------------------------------------------------------------------- 1 | # (c) 2005 Ben Bangert 2 | # This module is part of the Python Paste Project and is released under 3 | # the MIT License: http://www.opensource.org/licenses/mit-license.php 4 | from paste.fixture import TestApp 5 | from paste.request import get_cookie_dict 6 | from paste.wsgiwrappers import WSGIRequest 7 | 8 | def simpleapp(environ, start_response): 9 | status = '200 OK' 10 | response_headers = [('Content-type','text/plain')] 11 | start_response(status, response_headers) 12 | request = WSGIRequest(environ) 13 | body = [ 14 | 'Hello world!\n', 'The get is %s' % str(request.GET), 15 | ' and Val is %s\n' % request.GET.get('name'), 16 | 'The languages are: %s\n' % request.languages, 17 | 'The accepttypes is: %s\n' % request.match_accept(['text/html', 'application/xml'])] 18 | body = [line.encode('utf8') for line in body] 19 | return body 20 | 21 | def test_gets(): 22 | app = TestApp(simpleapp) 23 | res = app.get('/') 24 | assert 'Hello' in res 25 | assert "get is MultiDict([])" in res 26 | 27 | res = app.get('/?name=george') 28 | res.mustcontain("get is MultiDict([('name', 'george')])") 29 | res.mustcontain("Val is george") 30 | 31 | def test_language_parsing(): 32 | app = TestApp(simpleapp) 33 | res = app.get('/') 34 | assert "The languages are: ['en-us']" in res 35 | 36 | res = app.get('/', headers={'Accept-Language':'da, en-gb;q=0.8, en;q=0.7'}) 37 | assert "languages are: ['da', 'en-gb', 'en', 'en-us']" in res 38 | 39 | res = app.get('/', headers={'Accept-Language':'en-gb;q=0.8, da, en;q=0.7'}) 40 | assert "languages are: ['da', 'en-gb', 'en', 'en-us']" in res 41 | 42 | def test_mime_parsing(): 43 | app = TestApp(simpleapp) 44 | res = app.get('/', headers={'Accept':'text/html'}) 45 | assert "accepttypes is: ['text/html']" in res 46 | 47 | res = app.get('/', headers={'Accept':'application/xml'}) 48 | assert "accepttypes is: ['application/xml']" in res 49 | 50 | res = app.get('/', headers={'Accept':'application/xml,*/*'}) 51 | assert "accepttypes is: ['text/html', 'application/xml']" in res 52 | 53 | def test_bad_cookie(): 54 | env = {} 55 | env['HTTP_COOKIE'] = '070-it-:>%0D", status=404) 49 | assert b'-->