Paste-1.7.5.1/0000775000175000017500000000000011445725545011361 5ustar ianbianbPaste-1.7.5.1/docs/0000775000175000017500000000000011354703364012303 5ustar ianbianbPaste-1.7.5.1/docs/include/0000775000175000017500000000000011354703364013726 5ustar ianbianbPaste-1.7.5.1/docs/include/contact.txt0000664000175000017500000000052211354703364016121 0ustar ianbianbIf you have questions about this document, please contact the `paste mailing list `_ or try IRC (``#pythonpaste`` on freenode.net). If there's something that confused you and you want to give feedback, please `submit an issue `_. Paste-1.7.5.1/docs/include/reference_header.txt0000664000175000017500000000010211354703364017726 0ustar ianbianbPaste Reference Document @@@@@@@@@@@@@@@@@@@@@@@@ .. contents:: Paste-1.7.5.1/docs/conf.py0000664000175000017500000000775611445725476013632 0ustar ianbianb# -*- coding: utf-8 -*- # # Paste documentation build configuration file, created by # sphinx-quickstart on Tue Apr 22 22:08:49 2008. # # This file is execfile()d with the current directory set to its containing dir. # # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). # # All configuration values have a default value; values that are commented out # serve to show the default value. import sys # If your extensions are in another directory, add it here. #sys.path.append('some/directory') # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.txt' # The master toctree document. master_doc = 'index' # General substitutions. project = 'Paste' copyright = '2008, Ian Bicking' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # # The short X.Y version. version = '1.7' # The full version, including alpha/beta/rc tags. release = '1.7.5.1' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. unused_docs = ['include/contact.txt', 'include/reference_header.txt'] # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # Options for HTML output # ----------------------- # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. html_style = 'default.css' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Content template for the index page. #html_index = '' # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If true, the reST sources are included in the HTML build as _sources/. #html_copy_source = True # Output file base name for HTML help builder. htmlhelp_basename = 'Pastedoc' # Options for LaTeX output # ------------------------ # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). #latex_documents = [] # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True Paste-1.7.5.1/docs/paste-httpserver-threadpool.txt0000664000175000017500000001440511354703364020527 0ustar ianbianbThe Paste HTTP Server Thread Pool ================================= This document describes how the thread pool in ``paste.httpserver`` works, and how it can adapt to problems. Note all of the configuration parameters listed here are prefixed with ``threadpool_`` when running through a Paste Deploy configuration. Error Cases ----------- When a WSGI application is called, it's possible that it will block indefinitely. There's two basic ways you can manage threads: * Start a thread on every request, close it down when the thread stops * Start a pool of threads, and reuse those threads for subsequent requests In both cases things go wrong -- if you start a thread every request you will have an explosion of threads, and with it memory and a loss of performance. This can culminate in really high loads, swapping, and the whole site grinds to a halt. If you are using a pool of threads, all the threads can simply be used up. New requests go into a queue to be processed, but since that queue never moves forward everyone will just block. The site basically freezes, though memory usage doesn't generally get worse. Paste Thread Pool ----------------- The thread pool in Paste has some options to walk the razor's edge between the two techniques, and to try to respond usefully in most cases. The pool tracks all workers threads. Threads can be in a few states: * Idle, waiting for a request ("idle") * Working on a request * For a reasonable amount of time ("busy") * For an unreasonably long amount of time ("hung") * Thread that should die * An exception has been injected that should kill the thread, but it hasn't happened yet ("dying") * An exception has been injected, but the thread has persisted for an unreasonable amount of time ("zombie") When a request comes in, if there are no idle worker threads waiting then the server looks at the workers; all workers are busy or hung. If too many are hung, another thread is opened up. The limit is if there are less than ``spawn_if_under`` busy threads. So if you have 10 workers, ``spawn_if_under`` is 5, and there are 6 hung threads and 4 busy threads, another thread will be opened (bringing the number of busy threads back to 5). Later those threads may be collected again if some of the threads become un-hung. A thread is hung if it has been working for longer than ``hung_thread_limit`` (default 30 seconds). Every so often, the server will check all the threads for error conditions. This happens every ``hung_check_period`` requests (default 100). At this time if there are more than enough threads (because of ``spawn_if_under``) some threads may be collected. If any threads have been working for longer than ``kill_thread_limit`` (default 1800 seconds, i.e., 30 minutes) then the thread will be killed. To kill a thread the ``ctypes`` module must be installed. This will raise an exception (``SystemExit``) in the thread, which should cause the thread to stop. It can take quite a while for this to actually take effect, sometimes on the order of several minutes. This uses a non-public API (hence the ``ctypes`` requirement), and so it might not work in all cases. I've tried it in pure Python code and with a hung socket, and in both cases it worked. As soon as the thread is killed (before it is actually dead) another worker is added to the pool. If the killed thread lives longer than ``dying_thread_limit`` (default 300 seconds, 5 minutes) then it is considered a zombie. Zombie threads are not handled specially unless you set ``max_zombies_before_die``. If you set this and there are more than this many zombie threads, then the entire process will be killed. This is useful if you are running the server under some process monitor, such as ``start-stop-daemon``, ``daemontools``, ``runit``, or with ``paster serve --monitor``. To make the process die, it may run ``os._exit``, which is considered an impolite way to exit a process (akin to ``kill -9``). It *will* try to run the functions registered with ``atexit`` (except for the thread cleanup functions, which are the ones which will block so long as there are living threads). Notification ------------ If you set ``error_email`` (including setting it globally in a Paste Deploy ``[DEFAULT]`` section) then you will be notified of two error conditions: when hung threads are killed, and when the process is killed due to too many zombie threads. Missed Cases ------------ If you have a worker pool size of 10, and 11 slow or hung requests come in, the first 10 will get handed off but the server won't know yet that they will hang. The last request will stay stuck in a queue until another request comes in. When a later request comes later (after ``hung_thread_limit`` seconds) the server will notice the problem and add more threads, and the 11th request will come through. If a trickle of bad requests keeps coming in, the number of hung threads will keep increasing. At 100 the ``hung_check_period`` may not clean them up fast enough. Killing threads is not something Python really supports. Corruption of the process, memory leaks, or who knows what might occur. For the most part the threads seem to be killed in a fairly simple manner -- an exception is raised, and ``finally`` blocks do get executed. But this hasn't been tried much in production, so there's not much experience with it. watch_threads ------------- If you want to see what's going on in your process, you can install the application ``egg:Paste#watch_threads`` (in the ``paste.debug.watchthreads`` module). This lets you see requests and how long they have been running. In Python 2.5 you can see tracebacks of the running requests; before that you can only see request data (URLs, User-Agent, etc). If you set ``allow_kill = true`` then you can also kill threads from the application. The thread pool is intended to run reliably without intervention, but this can help debug problems or give you some feeling of what causes problems in the site. This does open up privacy problems, as it gives you access to all the request data in the site, including cookies, IP addresses, etc. It shouldn't be left on in a public setting. socket_timeout -------------- The HTTP server (not the thread pool) also accepts an argument ``socket_timeout``. It is turned off by default. You might find it helpful to turn it on. Paste-1.7.5.1/docs/index.txt0000664000175000017500000000310211354703364014147 0ustar ianbianbPython Paste ============ Contents: .. toctree:: :maxdepth: 1 news future testing-applications url-parsing-with-wsgi do-it-yourself-framework paste-httpserver-threadpool developer-features DeveloperGuidelines StyleGuide paste-httpserver-threadpool testing-applications url-parsing-with-wsgi community/index.txt community/mailing-list.txt community/repository.txt download/index.txt license Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. comment: I want to put these somewhere sometime, but no place for them now... Python Paste -- 50% tastier than Elmer's! Paste: making the web sticky. Fix broken websites by applying Paste liberally. Paste: paper over your inconsistencies. Paste: a soft mixture of malleable consistency. Paste: a tasty mixture to be spread on bread or crackers. Paste: glue that won't hurt you if you eat it. Python Paste: the web extruded into the form of a snake. Paste: the vinegar eel. Paste: you bring the cut. Paste: a doughy substance from which to make metaphorical web cupcakes. LAMP? LAMPP! Putting the P in Wep 2.0! Frankenweb crush tiny humans! DSL? DSF! Paste: Comfort for the framework-scarred Other Components ================ * `Paste Deploy <./deploy/>`_ * `Paste Script <./script/>`_ * `WebOb <./webob/>`_ * `WSGI specification `_ License ======= Paste is distributed under the `MIT license `_. Paste-1.7.5.1/docs/testing-applications.txt0000664000175000017500000001172111354703364017207 0ustar ianbianbTesting Applications with Paste +++++++++++++++++++++++++++++++ :author: Ian Bicking :revision: $Rev$ :date: $LastChangedDate$ .. contents:: Introduction ============ Paste includes functionality for testing your application in a convenient manner. These facilities are quite young, and feedback is invited. Feedback and discussion should take place on the `Paste-users list `_. These facilities let you test your Paste and WSGI-based applications easily and without a server. .. include:: include/contact.txt The Tests Themselves ==================== The ``app`` object is a wrapper around your application, with many methods to make testing convenient. Here's an example test script:: def test_myapp(): res = app.get('/view', params={'id': 10}) # We just got /view?id=10 res.mustcontain('Item 10') res = app.post('/view', params={'id': 10, 'name': 'New item name'}) # The app does POST-and-redirect... res = res.follow() assert res.request.url == '/view?id=10' res.mustcontain('New item name') res.mustcontain('Item updated') The methods of the ``app`` object (a ``paste.tests.fixture.TestApp`` object): ``get(url, params={}, headers={}, status=None)``: Gets the URL. URLs are based in the root of your application; no domains are allowed. Parameters can be given as a dictionary, or included directly in the ``url``. Headers can also be added. This tests that the status is a ``200 OK`` or a redirect header, unless you pass in a ``status``. A status of ``"*"`` will never fail; or you can assert a specific status (like ``500``). Also, if any errors are written to the error stream this will raise an error. ``post(url, params={}, headers={}, status=None, upload_files=())``: POSTS to the URL. Like GET, except also allows for uploading files. The uploaded files are a list of ``(field_name, filename, file_content)``. If you don't want to do a urlencoded post body, you can put a ``content-type`` header in your header, and pass the body in as a string with ``params``. The response object: ``header(header_name, [default])``: Returns the named header. It's an error if there is more than one matching header. If you don't provide a default, it is an error if there is no matching header. ``all_headers(header_name):`` Returns a list of all matching headers. ``follow(**kw)``: Follows the redirect, returning the new response. It is an error if this response wasn't a redirect. Any keyword arguments are passed to ``app.get`` (e.g., ``status``). ``x in res``: Returns True if the string is found in the response. Whitespace is normalized for this test. ``mustcontain(*strings)``: Raises an error if any of the strings are not found in the response. ``showbrowser()``: Opens the HTML response in a browser; useful for debugging. ``str(res)``: Gives a slightly-compacted version of the response. ``click(description=None, linkid=None, href=None, anchor=None, index=None, verbose=False)``: Clicks the described link (`see docstring for more <./class-paste.fixture.TestResponse.html#click>`_) ``forms``: Return a dictionary of forms; you can use both indexes (refer to the forms in order) or the string ids of forms (if you've given them ids) to identify the form. See `Form Submissions <#form-submissions>`_ for more on the form objects. Request objects: ``url``: The url requested. ``environ``: The environment used for the request. ``full_url``: The url with query string. Form Submissions ================ You can fill out and submit forms from your tests. First you get the form:: res = testapp.get('/entry_form') form = res.forms[0] Then you fill it in fields:: # when there's one unambiguous name field: form['name'] = 'Bob' # Enter something into the first field named 'age' form.set('age', '45', index=1) Finally you submit:: # Submit with no particular submit button pressed: form.submit() # Or submit a button: form.submit('submit_button_name') Framework Hooks =============== Frameworks can detect that they are in a testing environment by the presence (and truth) of the WSGI environmental variable ``"paste.testing"``. More generally, frameworks can detect that something (possibly a test fixture) is ready to catch unexpected errors by the presence and truth of ``"paste.throw_errors"`` (this is sometimes set outside of testing fixtures too, when an error-handling middleware is in place). Frameworks that want to expose the inner structure of the request may use ``"paste.testing_variables"``. This will be a dictionary -- any values put into that dictionary will become attributes of the response object. So if you do ``env["paste.testing_variables"]['template'] = template_name`` in your framework, then ``response.template`` will be ``template_name``. Paste-1.7.5.1/docs/DeveloperGuidelines.txt0000664000175000017500000001102011354703364016774 0ustar ianbianb++++++++++++++++++++++++++++ Python Paste Developer Guide ++++++++++++++++++++++++++++ Hi. Welcome to Paste. I hope you enjoy your stay here. I hope to bring together multiple efforts here, for Paste to support multiple frameworks and directions, while presenting a fairly integrated frontend to users. How to do that? That's an open question, and this code is in some ways an exploration. There's some basic principles: * Keep stuff decoupled. * Must be testable. Of course tested is even better than testable. * Use WSGI standards for communication between decoupled libraries. * When possible, use HTTP semantics for communicating between libraries (e.g., indicate cachability using the appropriate HTTP headers). * When possible, use WSGI as a wrapper around web-neutral libraries. For instance, the configuration is a simple library, but the WSGI middleware that puts the configuration in place is really really simple. If it could be used outside of a web context, then having both a library and middleware form is good. * Entry into frameworks should be easy, but exit should also be easy. Heterogeneous frameworks and applications are the ambition. But we have to get some messiness into Paste before we can try to resolve that messiness. * When all is said and done, users should be able to ignore much of what we've done and focus on writing their applications, and Stuff Just Works. Documentation is good; stuff that works without user intervention is better. Developer Info ============== Mostly, if there's a problem we can discuss it and work it out, no one is going to bite your head off for committing something. * Framework-like things should go in subpackages, or perhaps in separate distributions entirely (Paste WebKit and Wareweb were extracted for this reason). * Integrating external servers and frameworks is also interesting, but it's best to introduce that as a requirement instead of including the work here. Paste Script contains several wrappers for external projects (servers in particular). * Tests are good. We use py.test_, because it is simple. I want to use doctests too, but the infrastructure isn't really there now -- but please feel free to use those too. ``unittest`` is kind of annoying, and py.test is both more powerful and easier to write for. Tests should go in the ``tests/`` directory. ``paste.fixture`` contains some convenience functions for testing WSGI applications and middleware. Pay particular attention to ``TestApp``. .. _py.test: http://codespeak.net/py/current/doc/test.html * If you move something around that someone may be using, keep their imports working and introduce a warning, like:: def backward_compat_function(*args, **kw): import warnings # Deprecated on 2005 Mar 5 warnings.warn('Moved to foo.function', DeprecationWarning, 2) return foo.function(*args, **kw) * If something is really experimental, put it in your home directory, or make a branch in your home directory. You can make a home directory for yourself, in ``http://svn.w4py.org/home/username``. * Not everything in the repository or even in the trunk will necessarily go into the release. The release should contain stuff that is tested, documented, and useful. Each module or feature also needs a champion -- someone who will stand by the code, answer questions, etc. It doesn't have to be the original developer, but there has to be *someone*. So when a release is cut, if some modules don't fulfill that they may be left out. * Try to keep to the `Style Guidelines`_. But if you are bringing in outside work, don't stress out too much about it. Still, if you have a choice, follow that. Those guidelines are meant to represent conventional Python style guides, there's nothing out of the normal there. .. _Style Guidelines: StyleGuide.html * Write your docstrings in `restructured text `_. As time goes on, I want to rely on docstrings more for documentation, with shorter narrative documentation pointing into the documentation generated from docstrings. The generation is done with `Pudge `_. To try generating the documentation, this should work:: $ easy_install svn://lesscode.org/buildutils/trunk \ svn://lesscode.org/pudge/trunk $ cd Paste $ python setup.py pudge This will install Pudge and `buildutils `_, and then generate the documentation into ``Paste/docs/html/``. Paste-1.7.5.1/docs/StyleGuide.txt0000664000175000017500000000614211354703364015125 0ustar ianbianb+++++++++++++++++++ Paste Style Guide +++++++++++++++++++ Generally you should follow the recommendations in `PEP 8`_, the Python Style Guide. Some things to take particular note of: .. _PEP 8: http://www.python.org/peps/pep-0008.html * **No tabs**. Not anywhere. Always indent with 4 spaces. * I don't stress too much on line length. But try to break lines up by grouping with parenthesis instead of with backslashes (if you can). Do asserts like:: assert some_condition(a, b), ( "Some condition failed, %r isn't right!" % a) * But if you are having problems with line length, maybe you should just break the expression up into multiple statements. * Blank lines between methods, unless they are very small and closely bound to each other. * Don't use the form ``condition and trueValue or falseValue``. Break it out and use a variable. * I (Ian Bicking) am very picky about whitespace. There's one and only one right way to do it. Good examples:: short = 3 longerVar = 4 if x == 4: do stuff func(arg1='a', arg2='b') func((a + b)*10) **Bad** examples:: short =3 longerVar=4 if x==4: do stuff func(arg1 = 'a', arg2 = 'b') func(a,b) func( a, b ) [ 1, 2, 3 ] If the whitespace isn't right, it'll annoy me and I'll feel a need to fix it. Really, this whitespace stuff shouldn't be that controversial should it? Some particular points that I feel strongly about: * No space after a function name (bad: ``func (arg)``). * No space after or before a parenthesis (bad: ``func( arg )``). * Always one space after a comma (bad: ``func(a,b)``). * Use ``@@`` to mark something that is suboptimal, or where you have a concern that it's not right. Try to also date it and put your username there. * Docstrings are good. They should look like:: class AClass(object): """ doc string... """ Don't use single quotes (''') -- they tend to cause problems in Emacs. Don't bother trying make the string less vertically compact. * Comments go right before the thing they are commenting on. * Methods never, ever, ever start with capital letters. Generally only classes are capitalized. But definitely never methods. * Use ``cls`` to refer to a class. Use ``meta`` to refer to a metaclass (which also happens to be a class, but calling a metaclass ``cls`` will be confusing). * Use ``isinstance`` instead of comparing types. E.g.:: if isinstance(var, str): ... # Bad: if type(var) is StringType: ... * Never, ever use two leading underscores. This is annoyingly private. If name clashes are a concern, use explicit name mangling instead (e.g., ``_MyThing_blahblah``). This is essentially the same thing as double-underscore, only it's transparent where double underscore obscures. * Module names should be unique in the package. Subpackages shouldn't share module names with sibling or parent packages. Sadly this isn't possible for ``__init__.py``, but it's otherwise easy enough. * Module names should be all lower case, and probably have no underscores (smushedwords). Paste-1.7.5.1/docs/community/0000775000175000017500000000000011354713766014336 5ustar ianbianbPaste-1.7.5.1/docs/community/index.txt0000664000175000017500000000065111354703364016201 0ustar ianbianbCommunity ========= Much of the communication goes on in the `mailing lists `_; see that page for information on the lists. For live IRC discussion, try the ``#pythonpaste`` channel on `Freenode `_. If you find bugs in the code or documentation, please `submit a ticket `_. You can also `view tickets `_. Paste-1.7.5.1/docs/community/repository.txt0000664000175000017500000000034411354713766017317 0ustar ianbianbRepository ========== Paste is kept in a Mercurial (hg) repository at http://bitbucket.org/ianb/paste If you are using a command-line Mercurial client, you can check it out like:: hg clone http://bitbucket.org/ianb/paste Paste-1.7.5.1/docs/community/mailing-list.txt0000664000175000017500000000114511354703364017462 0ustar ianbianbMailing Lists ============= General discussion and questions should go to: `paste-users@googlegroups.org `_: New posts are `on Google Groups `_ `old posts are in their own archive `_ More abstract discussion of Python web programming should go to: `web-sig@python.org `_: `Subscribe `__, `Archives `__ Paste-1.7.5.1/docs/download/0000775000175000017500000000000011354714076014114 5ustar ianbianbPaste-1.7.5.1/docs/download/index.txt0000664000175000017500000000221011354714076015757 0ustar ianbianbDownloads ========= Each of these packages is available in several formats. The source distribution is a complete set of documentation, tests, and the source files themselves. There are also two "Egg" files: these are files `easy_install `_ can install directly into your ``site-packages/`` directory, and are Python-version specific. The download files for the latest version are always located on the Cheese Shop pages (listed below). * `Paste `_ * `Paste Script `_ * `Paste Deploy `_ * `Paste WebKit `_ * `Wareweb `_ All the packages are available in the Mercurial repositories rooted in http://bitbucket.org/ianb/ * http://bitbucket.org/ianb/paste * http://bitbucket.org/ianb/pastescript * http://bitbucket.org/ianb/pastedeploy * http://bitbucket.org/ianb/webob * ... and others Use:: hg clone http://bitbucket.org/ianb/paste to check out a working copy of Paste. Paste-1.7.5.1/docs/modules/0000775000175000017500000000000011354703364013753 5ustar ianbianbPaste-1.7.5.1/docs/modules/cascade.txt0000664000175000017500000000042111354703364016074 0ustar ianbianb:mod:`paste.cascade` -- send requests to multiple applications until success ============================================================================ .. automodule:: paste.cascade Module Contents --------------- .. autoclass:: Cascade .. autofunction:: make_cascade Paste-1.7.5.1/docs/modules/debug.prints.txt0000664000175000017500000000031711354703364017121 0ustar ianbianb:mod:`paste.debug.prints` -- capture print output ================================================= .. automodule:: paste.debug.prints Module Contents --------------- .. autoclass:: PrintDebugMiddleware Paste-1.7.5.1/docs/modules/auth.cas.txt0000664000175000017500000000027211354703364016223 0ustar ianbianb:mod:`paste.auth.cas` -- CAS authentication =========================================== .. automodule:: paste.auth.cas Module Contents --------------- .. autoclass:: AuthCASHandler Paste-1.7.5.1/docs/modules/httpheaders.txt0000664000175000017500000000033011354703364017023 0ustar ianbianb:mod:`paste.httpheaders` -- Manipulate HTTP Headers =================================================== .. comment: I just don't feel like documenting the items... .. automodule:: paste.httpheaders :members: Paste-1.7.5.1/docs/modules/url.txt0000664000175000017500000000027111354703364015316 0ustar ianbianb:mod:`paste.url` -- URL convenience class ========================================= .. automodule:: paste.url Module Contents --------------- .. autoclass:: URL .. autoclass:: Image Paste-1.7.5.1/docs/modules/reloader.txt0000664000175000017500000000044511354703364016314 0ustar ianbianb:mod:`paste.reloader` -- Monitor for file changes to restart the process ======================================================================== .. automodule:: paste.reloader Module Contents --------------- .. autofunction:: install .. autoclass:: Monitor .. autofunction:: watch_file Paste-1.7.5.1/docs/modules/request.txt0000664000175000017500000000076511354703364016214 0ustar ianbianb:mod:`paste.request` -- Utility functions for the WSGI environment ================================================================== .. automodule:: paste.request Module Contents --------------- .. autofunction:: get_cookies .. autofunction:: get_cookie_dict .. autofunction:: parse_querystring .. autofunction:: parse_formvars .. autofunction:: construct_url .. autofunction:: path_info_split .. autofunction:: path_info_pop .. autofunction:: resolve_relative_url .. autoclass:: EnvironHeaders Paste-1.7.5.1/docs/modules/debug.debugapp.txt0000664000175000017500000000043011354703364017365 0ustar ianbianb:mod:`paste.debug.debugapp` -- debug app ======================================== .. automodule:: paste.debug.debugapp Module Contents --------------- .. autoclass:: SimpleApplication .. autoclass:: SlowConsumer .. autofunction:: make_test_app .. autofunction:: make_slow_app Paste-1.7.5.1/docs/modules/wsgiwrappers.txt0000664000175000017500000000042711354703364017254 0ustar ianbianb:mod:`paste.wsgiwrappers` -- Wrapper functions for WSGI request and response ============================================================================ .. automodule:: paste.wsgiwrappers Module Contents --------------- .. autoclass:: WSGIRequest .. autoclass:: WSGIResponse Paste-1.7.5.1/docs/modules/fixture.txt0000664000175000017500000000117111354703364016202 0ustar ianbianb:mod:`paste.fixture` -- Test applications ========================================= .. contents:: .. automodule:: paste.fixture Module Contents --------------- .. autoclass:: TestApp :members: .. autoclass:: TestRequest Forms ----- .. autoclass:: Form :members: .. autoclass:: Field :members: .. autoclass:: Select .. autoclass:: Radio .. autoclass:: Checkbox .. autoclass:: Text .. autoclass:: Textarea .. autoclass:: Hidden .. autoclass:: Submit Script Testing -------------- .. autoclass:: TestFileEnvironment :members: .. autoclass:: ProcResult :members: .. autoclass:: FoundFile .. autoclass:: FoundDir Paste-1.7.5.1/docs/modules/lint.txt0000664000175000017500000000042011354703364015456 0ustar ianbianb:mod:`paste.lint` -- Check for the validity of WSGI requests and responses ========================================================================== .. automodule:: paste.lint Module Contents --------------- .. autofunction:: middleware .. autoexception:: WSGIWarning Paste-1.7.5.1/docs/modules/response.txt0000664000175000017500000000054111354703364016352 0ustar ianbianb:mod:`paste.response` -- Utility functions for producing responses ================================================================== .. automodule:: paste.response Module Contents --------------- .. autoclass:: HeaderDict .. autofunction:: has_header .. autofunction:: header_value .. autofunction:: remove_header .. autofunction:: replace_header Paste-1.7.5.1/docs/modules/registry.txt0000664000175000017500000000056411354703364016371 0ustar ianbianb:mod:`paste.registry` -- Manage thread-local request-specific objects ===================================================================== .. automodule:: paste.registry Module Contents --------------- .. autoclass:: StackedObjectProxy .. autoclass:: Registry .. autoclass:: RegistryManager .. autoclass:: StackedObjectRestorer .. autofunction:: make_registry_manager Paste-1.7.5.1/docs/modules/exceptions.txt0000664000175000017500000000235311354703364016700 0ustar ianbianb:mod:`paste.exceptions` -- Catch, display, and notify for exceptions ==================================================================== .. automodule:: paste.exceptions.errormiddleware Module Contents --------------- .. autoclass:: ErrorMiddleware .. autofunction:: handle_exception .. autofunction:: make_error_middleware :mod:`paste.exceptions.collector` -- Collection information from exceptions =========================================================================== .. automodule:: paste.exceptions.collector Module Contents --------------- .. autoclass:: ExceptionCollector .. autofunction:: collect_exception :mod:`paste.exceptions.formatter` -- Format exception information ================================================================= .. automodule:: paste.exceptions.formatter Module Contents --------------- .. autoclass:: TextFormatter .. autoclass:: HTMLFormatter .. autofunction:: format_html .. autofunction:: format_text :mod:`paste.exceptions.reporter` -- Report exceptions ===================================================== .. automodule:: paste.exceptions.reporter Module Contents --------------- .. autoclass:: EmailReporter .. autoclass:: LogReporter .. autoclass:: FileReporter .. autoclass:: WSGIAppReporter Paste-1.7.5.1/docs/modules/auth.grantip.txt0000664000175000017500000000042011354703364017114 0ustar ianbianb:mod:`paste.auth.grantip` -- Set user and groups based on IP address ==================================================================== .. automodule:: paste.auth.grantip Module Contents --------------- .. autoclass:: GrantIPMiddleware .. autofunction:: make_grantip Paste-1.7.5.1/docs/modules/auth.form.txt0000664000175000017500000000036211354703364016420 0ustar ianbianb:mod:`paste.auth.form` -- HTML form/cookie authentication ========================================================= .. automodule:: paste.auth.form Module Contents --------------- .. autoclass:: AuthFormHandler .. autofunction:: make_form Paste-1.7.5.1/docs/modules/auth.multi.txt0000664000175000017500000000035611354703364016612 0ustar ianbianb:mod:`paste.auth.multi` -- Authentication via one of multiple methods ===================================================================== .. automodule:: paste.auth.multi Module Contents --------------- .. autoclass:: MultiHandler Paste-1.7.5.1/docs/modules/translogger.txt0000664000175000017500000000032011354703364017036 0ustar ianbianb:mod:`paste.translogger` -- Log requests ======================================== .. automodule:: paste.translogger Module Contents --------------- .. autoclass:: TransLogger .. autofunction:: make_filter Paste-1.7.5.1/docs/modules/debug.profile.txt0000664000175000017500000000047011354703364017242 0ustar ianbianb:mod:`paste.debug.profile` -- profile applications and requests =============================================================== .. automodule:: paste.debug.profile Module Contents --------------- .. autoclass:: ProfileMiddleware .. autofunction:: make_profile_middleware .. autofunction:: profile_decorator Paste-1.7.5.1/docs/modules/gzipper.txt0000664000175000017500000000034211354703364016173 0ustar ianbianb:mod:`paste.gzipper` -- Gzip-compress responses =============================================== .. automodule:: paste.gzipper Module Contents --------------- .. autoclass:: middleware .. autofunction:: make_gzip_middleware Paste-1.7.5.1/docs/modules/auth.cookie.txt0000664000175000017500000000047211354703364016730 0ustar ianbianb:mod:`paste.auth.cookie` -- Cookie-based authentication ======================================================= .. automodule:: paste.auth.cookie Module Contents --------------- .. autoclass:: AuthCookieSigner .. autoclass:: AuthCookieHandler .. autoclass:: AuthCookieEnviron .. autofunction:: make_auth_cookie Paste-1.7.5.1/docs/modules/util.multidict.txt0000664000175000017500000000040411354703364017464 0ustar ianbianb:mod:`paste.util.multidict` -- Dictionaries with multiple values ================================================================ .. automodule:: paste.util.multidict Module Contents --------------- .. autoclass:: MultiDict .. autoclass:: UnicodeMultiDict Paste-1.7.5.1/docs/modules/fileapp.txt0000664000175000017500000000037011354703364016134 0ustar ianbianb:mod:`paste.fileapp` -- Serve files =================================== .. automodule:: paste.fileapp Module Contents --------------- .. autoclass:: FileApp .. autoclass:: DirectoryApp .. autofunction:: DataApp .. autofunction:: ArchiveStore Paste-1.7.5.1/docs/modules/httpserver.txt0000664000175000017500000000031211354703364016716 0ustar ianbianb:mod:`paste.httpserver` -- HTTP server ====================================== .. automodule:: paste.httpserver Module Contents --------------- .. autofunction:: serve .. autofunction:: server_runner Paste-1.7.5.1/docs/modules/urlparser.txt0000664000175000017500000000061011354703364016530 0ustar ianbianb:mod:`paste.urlparser` -- Handle URL paths and server static files ================================================================== .. automodule:: paste.urlparser Module Contents --------------- .. autoclass:: StaticURLParser .. autofunction:: make_static .. autoclass:: PkgResourcesParser .. autofunction:: make_pkg_resources .. autoclass:: URLParser .. autofunction:: make_url_parser Paste-1.7.5.1/docs/modules/session.txt0000664000175000017500000000036311354703364016201 0ustar ianbianb:mod:`paste.session` -- Simple file-based sessions ================================================== .. automodule:: paste.session Module Contents --------------- .. autoclass:: SessionMiddleware .. autofunction:: make_session_middleware Paste-1.7.5.1/docs/modules/auth.digest.txt0000664000175000017500000000045111354703364016733 0ustar ianbianb:mod:`paste.auth.digest` -- HTTP Digest login ============================================= .. automodule:: paste.auth.digest Module Contents --------------- .. autoclass:: AuthDigestAuthenticator .. autoclass:: AuthDigestHandler .. autofunction:: digest_password .. autofunction:: make_digest Paste-1.7.5.1/docs/modules/debug.watchthreads.txt0000664000175000017500000000050511354703364020262 0ustar ianbianb:mod:`paste.debug.watchthreads` -- watch thread workers in paste.httpserver =========================================================================== .. automodule:: paste.debug.watchthreads Module Contents --------------- .. autoclass:: WatchThreads .. autofunction:: make_watch_threads .. autofunction:: make_bad_app Paste-1.7.5.1/docs/modules/util.import_string.txt0000664000175000017500000000051411354703364020370 0ustar ianbianb:mod:`paste.util.import_string` -- Import objects from strings ============================================================== .. automodule:: paste.util.import_string Module Contents --------------- .. autofunction:: eval_import .. autofunction:: simple_import .. autofunction:: import_module .. autofunction:: try_import_module Paste-1.7.5.1/docs/modules/cgiapp.txt0000664000175000017500000000043111354703364015755 0ustar ianbianb:mod:`paste.cgiapp` -- run CGI scripts as WSGI applications =========================================================== .. automodule:: paste.cgiapp Module Contents --------------- .. autoclass:: CGIApplication .. autoexception:: CGIError .. autofunction:: make_cgi_application Paste-1.7.5.1/docs/modules/evalexception.txt0000664000175000017500000000034211354703364017361 0ustar ianbianb:mod:`paste.evalexception` -- Interactive debugging for errors ============================================================== .. automodule:: paste.evalexception Module Contents --------------- .. autoclass:: EvalException Paste-1.7.5.1/docs/modules/pony.txt0000664000175000017500000000035011354703364015477 0ustar ianbianb:mod:`paste.pony` -- Add pony power to your application ======================================================= .. automodule:: paste.pony Module Contents --------------- .. autoclass:: PonyMiddleware .. autofunction:: make_pony Paste-1.7.5.1/docs/modules/errordocument.txt0000664000175000017500000000044011354703364017402 0ustar ianbianb:mod:`paste.errordocument` -- Do internal redirects for error responses ======================================================================= .. automodule:: paste.errordocument Module Contents --------------- .. autoclass:: StatusBasedForward .. autofunction:: make_errordocument Paste-1.7.5.1/docs/modules/progress.txt0000664000175000017500000000036611354703364016365 0ustar ianbianb:mod:`paste.progress` -- Track progress of uploads ================================================== .. automodule:: paste.progress Module Contents --------------- .. autoclass:: UploadProgressMonitor .. autoclass:: UploadProgressReporter Paste-1.7.5.1/docs/modules/urlmap.txt0000664000175000017500000000034011354703364016011 0ustar ianbianb:mod:`paste.urlmap` -- Map URL paths ==================================== .. automodule:: paste.urlmap Module Contents --------------- .. autoclass:: URLMap .. autofunction:: urlmap_factory .. autoclass:: PathProxyURLMap Paste-1.7.5.1/docs/modules/httpexceptions.txt0000664000175000017500000000310411354703364017573 0ustar ianbianb:mod:`paste.httpexceptions` -- Easily product HTTP errors ========================================================= .. automodule:: paste.httpexceptions Module Contents --------------- .. autoclass:: HTTPExceptionHandler .. autofunction:: make_middleware Exceptions ---------- .. autoexception:: HTTPException .. autoexception:: HTTPError .. autoexception:: HTTPRedirection .. autoexception:: HTTPMultipleChoices .. autoexception:: HTTPMovedPermanently .. autoexception:: HTTPFound .. autoexception:: HTTPNotModified .. autoexception:: HTTPUseProxy .. autoexception:: HTTPTemporaryRedirect .. autoexception:: HTTPClientError .. autoexception:: HTTPBadRequest .. autoexception:: HTTPUnauthorized .. autoexception:: HTTPPaymentRequired .. autoexception:: HTTPForbidden .. autoexception:: HTTPNotFound .. autoexception:: HTTPMethodNotAllowed .. autoexception:: HTTPNotAcceptable .. autoexception:: HTTPProxyAuthenticationRequired .. autoexception:: HTTPRequestTimeout .. autoexception:: HTTPConflict .. autoexception:: HTTPGone .. autoexception:: HTTPLengthRequired .. autoexception:: HTTPPreconditionFailed .. autoexception:: HTTPRequestEntityTooLarge .. autoexception:: HTTPRequestURITooLong .. autoexception:: HTTPUnsupportedMediaType .. autoexception:: HTTPRequestRangeNotSatisfiable .. autoexception:: HTTPExpectationFailed .. autoexception:: HTTPServerError .. autoexception:: HTTPInternalServerError .. autoexception:: HTTPNotImplemented .. autoexception:: HTTPBadGateway .. autoexception:: HTTPServiceUnavailable .. autoexception:: HTTPGatewayTimeout .. autoexception:: HTTPVersionNotSupported Paste-1.7.5.1/docs/modules/cgitb_catcher.txt0000664000175000017500000000040411354703364017273 0ustar ianbianb:mod:`paste.cgitb_catcher` -- catch exceptions using cgitb ========================================================== .. automodule:: paste.cgitb_catcher Module Contents --------------- .. autoclass:: CgitbMiddleware .. autofunction:: make_cgitb_middleware Paste-1.7.5.1/docs/modules/debug.fsdiff.txt0000664000175000017500000000054211354703364017043 0ustar ianbianb:mod:`paste.debug.fsdiff` -- Show differences between directories ================================================================= .. automodule:: paste.debug.fsdiff Module Contents --------------- .. autoclass:: Diff .. autoclass:: Snapshot .. autoclass:: File .. autoclass:: Dir .. autofunction:: report_expected_diffs .. autofunction:: show_diff Paste-1.7.5.1/docs/modules/auth.basic.txt0000664000175000017500000000042111354703364016532 0ustar ianbianb:mod:`paste.auth.basic` -- Basic HTTP authentication ==================================================== .. automodule:: paste.auth.basic Module Contents --------------- .. autoclass:: AuthBasicAuthenticator .. autoclass:: AuthBasicHandler .. autofunction:: make_basic Paste-1.7.5.1/docs/modules/proxy.txt0000664000175000017500000000046211354703364015677 0ustar ianbianb:mod:`paste.proxy` -- Proxy WSGI requests to HTTP requests ========================================================== .. automodule:: paste.proxy Module Contents --------------- .. autoclass:: Proxy .. autofunction:: make_proxy .. autoclass:: TransparentProxy .. autofunction:: make_transparent_proxy Paste-1.7.5.1/docs/modules/transaction.txt0000664000175000017500000000043411354703364017042 0ustar ianbianb:mod:`paste.transaction` -- DB-API transactions =============================================== .. automodule:: paste.transaction Module Contents --------------- .. autoclass:: TransactionManagerMiddleware .. autoclass:: ConnectionFactory .. autofunction:: BasicTransactionHandler Paste-1.7.5.1/docs/modules/wsgilib.txt0000664000175000017500000000101011355172133016137 0ustar ianbianb:mod:`paste.wsgilib` -- Miscellaneous WSGI utility functions ============================================================ .. automodule:: paste.wsgilib Module Contents --------------- .. autofunction:: add_close .. autofunction:: add_start_close .. autofunction:: chained_app_iters .. autoclass:: encode_unicode_app_iter .. autofunction:: catch_errors .. autofunction:: catch_errors_app .. autofunction:: raw_interactive .. autofunction:: interactive .. autofunction:: dump_environ .. autofunction:: intercept_output Paste-1.7.5.1/docs/modules/debug.wdg_validate.txt0000664000175000017500000000036311354703364020235 0ustar ianbianb:mod:`paste.debug.debugapp` -- debug app ======================================== .. automodule:: paste.debug.wdg_validate Module Contents --------------- .. autoclass:: WDGValidateMiddleware .. autofunction:: make_wdg_validate_middleware Paste-1.7.5.1/docs/modules/recursive.txt0000664000175000017500000000035011354703364016521 0ustar ianbianb:mod:`paste.recursive` -- internal requests =========================================== .. automodule:: paste.recursive Module Contents --------------- .. autoclass:: RecursiveMiddleware .. autofunction:: ForwardRequestException Paste-1.7.5.1/docs/modules/auth.auth_tkt.txt0000664000175000017500000000047011354703364017300 0ustar ianbianb:mod:`paste.auth.auth_tkt` -- auth_tkt cookie parsing ===================================================== .. automodule:: paste.auth.auth_tkt Module Contents --------------- .. autoclass:: AuthTKTMiddleware .. autofunction:: make_auth_tkt_middleware .. autoclass:: AuthTicket .. autoexception:: BadTicket Paste-1.7.5.1/docs/news.txt0000664000175000017500000010257211445725456014036 0ustar ianbianbNews ==== .. contents:: 1.7.5.1 ------- * Fix bug introduced in :mod:`paste.auth.auth_tkt` (with ``url_unquote``) 1.7.5 ----- * Won't install ``tests/`` directory (also caused installation problems on some Mac systems). * Fixed problem with gzip middleware and zero-length responses. * Use ``X-Forwarded-For`` header in :mod:`paste.translogger` * Fixed problems with mimeparse code * Fixed some corner cases with CGI scripts * :mod:`paste.auth.auth_tkt` will URL-quote usernames, avoiding some errors with usernames with ``!`` in them. * Improve handling of errors in fetching error pages in :mod:`paste.errordocument`. 1.7.4 ----- * Fix XSS bug (security issue) with not found handlers for :class:`paste.urlparser.StaticURLParser` and :class:`paste.urlmap.URLMap`. If you ask for a path with ``/-->

Python Paste

This Page

Paste-1.7.5.1/docs/_build/include/contact.html0000664000175000017500000001107211443716126017505 0ustar ianbianb <no title> — Paste v1.7.5 documentation

If you have questions about this document, please contact the paste mailing list or try IRC (#pythonpaste on freenode.net). If there’s something that confused you and you want to give feedback, please submit an issue.

Python Paste

This Page

Paste-1.7.5.1/docs/_build/paste-httpserver-threadpool.html0000664000175000017500000003563011443716131022110 0ustar ianbianb The Paste HTTP Server Thread Pool — Paste v1.7.5 documentation

The Paste HTTP Server Thread Pool¶

This document describes how the thread pool in paste.httpserver works, and how it can adapt to problems.

Note all of the configuration parameters listed here are prefixed with threadpool_ when running through a Paste Deploy configuration.

Error Cases¶

When a WSGI application is called, it’s possible that it will block indefinitely. There’s two basic ways you can manage threads:

  • Start a thread on every request, close it down when the thread stops
  • Start a pool of threads, and reuse those threads for subsequent requests

In both cases things go wrong – if you start a thread every request you will have an explosion of threads, and with it memory and a loss of performance. This can culminate in really high loads, swapping, and the whole site grinds to a halt.

If you are using a pool of threads, all the threads can simply be used up. New requests go into a queue to be processed, but since that queue never moves forward everyone will just block. The site basically freezes, though memory usage doesn’t generally get worse.

Paste Thread Pool¶

The thread pool in Paste has some options to walk the razor’s edge between the two techniques, and to try to respond usefully in most cases.

The pool tracks all workers threads. Threads can be in a few states:

  • Idle, waiting for a request (“idle”)
  • Working on a request
    • For a reasonable amount of time (“busy”)
    • For an unreasonably long amount of time (“hung”)
  • Thread that should die
    • An exception has been injected that should kill the thread, but it hasn’t happened yet (“dying”)
    • An exception has been injected, but the thread has persisted for an unreasonable amount of time (“zombie”)

When a request comes in, if there are no idle worker threads waiting then the server looks at the workers; all workers are busy or hung. If too many are hung, another thread is opened up. The limit is if there are less than spawn_if_under busy threads. So if you have 10 workers, spawn_if_under is 5, and there are 6 hung threads and 4 busy threads, another thread will be opened (bringing the number of busy threads back to 5). Later those threads may be collected again if some of the threads become un-hung. A thread is hung if it has been working for longer than hung_thread_limit (default 30 seconds).

Every so often, the server will check all the threads for error conditions. This happens every hung_check_period requests (default 100). At this time if there are more than enough threads (because of spawn_if_under) some threads may be collected. If any threads have been working for longer than kill_thread_limit (default 1800 seconds, i.e., 30 minutes) then the thread will be killed.

To kill a thread the ctypes module must be installed. This will raise an exception (SystemExit) in the thread, which should cause the thread to stop. It can take quite a while for this to actually take effect, sometimes on the order of several minutes. This uses a non-public API (hence the ctypes requirement), and so it might not work in all cases. I’ve tried it in pure Python code and with a hung socket, and in both cases it worked. As soon as the thread is killed (before it is actually dead) another worker is added to the pool.

If the killed thread lives longer than dying_thread_limit (default 300 seconds, 5 minutes) then it is considered a zombie.

Zombie threads are not handled specially unless you set max_zombies_before_die. If you set this and there are more than this many zombie threads, then the entire process will be killed. This is useful if you are running the server under some process monitor, such as start-stop-daemon, daemontools, runit, or with paster serve --monitor. To make the process die, it may run os._exit, which is considered an impolite way to exit a process (akin to kill -9). It will try to run the functions registered with atexit (except for the thread cleanup functions, which are the ones which will block so long as there are living threads).

Notification¶

If you set error_email (including setting it globally in a Paste Deploy [DEFAULT] section) then you will be notified of two error conditions: when hung threads are killed, and when the process is killed due to too many zombie threads.

Missed Cases¶

If you have a worker pool size of 10, and 11 slow or hung requests come in, the first 10 will get handed off but the server won’t know yet that they will hang. The last request will stay stuck in a queue until another request comes in. When a later request comes later (after hung_thread_limit seconds) the server will notice the problem and add more threads, and the 11th request will come through.

If a trickle of bad requests keeps coming in, the number of hung threads will keep increasing. At 100 the hung_check_period may not clean them up fast enough.

Killing threads is not something Python really supports. Corruption of the process, memory leaks, or who knows what might occur. For the most part the threads seem to be killed in a fairly simple manner – an exception is raised, and finally blocks do get executed. But this hasn’t been tried much in production, so there’s not much experience with it.

watch_threads¶

If you want to see what’s going on in your process, you can install the application egg:Paste#watch_threads (in the paste.debug.watchthreads module). This lets you see requests and how long they have been running. In Python 2.5 you can see tracebacks of the running requests; before that you can only see request data (URLs, User-Agent, etc). If you set allow_kill = true then you can also kill threads from the application. The thread pool is intended to run reliably without intervention, but this can help debug problems or give you some feeling of what causes problems in the site.

This does open up privacy problems, as it gives you access to all the request data in the site, including cookies, IP addresses, etc. It shouldn’t be left on in a public setting.

socket_timeout¶

The HTTP server (not the thread pool) also accepts an argument socket_timeout. It is turned off by default. You might find it helpful to turn it on.

Paste-1.7.5.1/docs/_build/search.html0000664000175000017500000001044211443716131015670 0ustar ianbianb Search — Paste v1.7.5 documentation

Search

Please activate JavaScript to enable the search functionality.

From here you can search these documents. Enter your search words into the box below and click "search". Note that the search function will automatically search for all of the words. Pages containing fewer words won't appear in the result list.

Paste-1.7.5.1/docs/_build/license.html0000664000175000017500000001313411443716126016052 0ustar ianbianb <no title> — Paste v1.7.5 documentation

Copyright (c) 2006-2007 Ian Bicking and Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Python Paste

Previous topic

Downloads

This Page

Paste-1.7.5.1/docs/_build/DeveloperGuidelines.html0000664000175000017500000002707711443716126020401 0ustar ianbianb Python Paste Developer Guide — Paste v1.7.5 documentation

Python Paste Developer Guide¶

Hi. Welcome to Paste. I hope you enjoy your stay here.

I hope to bring together multiple efforts here, for Paste to support multiple frameworks and directions, while presenting a fairly integrated frontend to users. How to do that? That’s an open question, and this code is in some ways an exploration.

There’s some basic principles:

  • Keep stuff decoupled.
  • Must be testable. Of course tested is even better than testable.
  • Use WSGI standards for communication between decoupled libraries.
  • When possible, use HTTP semantics for communicating between libraries (e.g., indicate cachability using the appropriate HTTP headers).
  • When possible, use WSGI as a wrapper around web-neutral libraries. For instance, the configuration is a simple library, but the WSGI middleware that puts the configuration in place is really really simple. If it could be used outside of a web context, then having both a library and middleware form is good.
  • Entry into frameworks should be easy, but exit should also be easy. Heterogeneous frameworks and applications are the ambition. But we have to get some messiness into Paste before we can try to resolve that messiness.
  • When all is said and done, users should be able to ignore much of what we’ve done and focus on writing their applications, and Stuff Just Works. Documentation is good; stuff that works without user intervention is better.

Developer Info¶

Mostly, if there’s a problem we can discuss it and work it out, no one is going to bite your head off for committing something.

  • Framework-like things should go in subpackages, or perhaps in separate distributions entirely (Paste WebKit and Wareweb were extracted for this reason).
  • Integrating external servers and frameworks is also interesting, but it’s best to introduce that as a requirement instead of including the work here. Paste Script contains several wrappers for external projects (servers in particular).
  • Tests are good. We use py.test, because it is simple. I want to use doctests too, but the infrastructure isn’t really there now – but please feel free to use those too. unittest is kind of annoying, and py.test is both more powerful and easier to write for. Tests should go in the tests/ directory. paste.fixture contains some convenience functions for testing WSGI applications and middleware. Pay particular attention to TestApp.
  • If you move something around that someone may be using, keep their imports working and introduce a warning, like:

    def backward_compat_function(*args, **kw):
        import warnings
        # Deprecated on 2005 Mar 5
        warnings.warn('Moved to foo.function', DeprecationWarning, 2)
        return foo.function(*args, **kw)
    
  • If something is really experimental, put it in your home directory, or make a branch in your home directory. You can make a home directory for yourself, in http://svn.w4py.org/home/username.

  • Not everything in the repository or even in the trunk will necessarily go into the release. The release should contain stuff that is tested, documented, and useful. Each module or feature also needs a champion – someone who will stand by the code, answer questions, etc. It doesn’t have to be the original developer, but there has to be someone. So when a release is cut, if some modules don’t fulfill that they may be left out.

  • Try to keep to the Style Guidelines. But if you are bringing in outside work, don’t stress out too much about it. Still, if you have a choice, follow that. Those guidelines are meant to represent conventional Python style guides, there’s nothing out of the normal there.

  • Write your docstrings in restructured text. As time goes on, I want to rely on docstrings more for documentation, with shorter narrative documentation pointing into the documentation generated from docstrings.

    The generation is done with Pudge. To try generating the documentation, this should work:

    $ easy_install svn://lesscode.org/buildutils/trunk \
                   svn://lesscode.org/pudge/trunk
    $ cd Paste
    $ python setup.py pudge

    This will install Pudge and buildutils, and then generate the documentation into Paste/docs/html/.

Python Paste

Table Of Contents

Previous topic

Features

Next topic

Paste Style Guide

This Page

Paste-1.7.5.1/docs/_build/community/0000775000175000017500000000000011443716126015564 5ustar ianbianbPaste-1.7.5.1/docs/_build/community/repository.html0000664000175000017500000001274311443716126020700 0ustar ianbianb Repository — Paste v1.7.5 documentation

Repository¶

Paste is kept in a Mercurial (hg) repository at http://bitbucket.org/ianb/paste

If you are using a command-line Mercurial client, you can check it out like:

hg clone http://bitbucket.org/ianb/paste

Python Paste

Previous topic

Mailing Lists

Next topic

Downloads

This Page

Paste-1.7.5.1/docs/_build/community/index.html0000664000175000017500000001355411443716126017571 0ustar ianbianb Community — Paste v1.7.5 documentation

Community¶

Much of the communication goes on in the mailing lists; see that page for information on the lists.

For live IRC discussion, try the #pythonpaste channel on Freenode.

If you find bugs in the code or documentation, please submit a ticket. You can also view tickets.

Python Paste

Previous topic

URL Parsing With WSGI And Paste

Next topic

Mailing Lists

This Page

Paste-1.7.5.1/docs/_build/community/mailing-list.html0000664000175000017500000001373711443716126021056 0ustar ianbianb Mailing Lists — Paste v1.7.5 documentation

Mailing Lists¶

General discussion and questions should go to:

paste-users@googlegroups.org:
New posts are on Google Groups old posts are in their own archive

More abstract discussion of Python web programming should go to:

web-sig@python.org:
Subscribe, Archives

Python Paste

Previous topic

Community

Next topic

Repository

This Page

Paste-1.7.5.1/docs/_build/download/0000775000175000017500000000000011443716126015347 5ustar ianbianbPaste-1.7.5.1/docs/_build/download/index.html0000664000175000017500000001615111443716126017350 0ustar ianbianb Downloads — Paste v1.7.5 documentation

Downloads¶

Each of these packages is available in several formats. The source distribution is a complete set of documentation, tests, and the source files themselves. There are also two “Egg” files: these are files easy_install can install directly into your site-packages/ directory, and are Python-version specific. The download files for the latest version are always located on the Cheese Shop pages (listed below).

All the packages are available in the Mercurial repositories rooted in http://bitbucket.org/ianb/

Use:

hg clone http://bitbucket.org/ianb/paste

to check out a working copy of Paste.

Python Paste

Previous topic

Repository

Next topic

<no title>

This Page

Paste-1.7.5.1/docs/_build/testing-applications.html0000664000175000017500000004213011443716131020563 0ustar ianbianb Testing Applications with Paste — Paste v1.7.5 documentation

Testing Applications with Paste¶

author:Ian Bicking <ianb@colorstudy.com>
revision:$Rev$
date:$LastChangedDate$

Introduction¶

Paste includes functionality for testing your application in a convenient manner. These facilities are quite young, and feedback is invited. Feedback and discussion should take place on the Paste-users list.

These facilities let you test your Paste and WSGI-based applications easily and without a server.

If you have questions about this document, please contact the paste mailing list or try IRC (#pythonpaste on freenode.net). If there’s something that confused you and you want to give feedback, please submit an issue.

The Tests Themselves¶

The app object is a wrapper around your application, with many methods to make testing convenient. Here’s an example test script:

def test_myapp():
    res = app.get('/view', params={'id': 10})
    # We just got /view?id=10
    res.mustcontain('Item 10')
    res = app.post('/view', params={'id': 10, 'name': 'New item
        name'})
    # The app does POST-and-redirect...
    res = res.follow()
    assert res.request.url == '/view?id=10'
    res.mustcontain('New item name')
    res.mustcontain('Item updated')

The methods of the app object (a paste.tests.fixture.TestApp object):

get(url, params={}, headers={}, status=None):

Gets the URL. URLs are based in the root of your application; no domains are allowed. Parameters can be given as a dictionary, or included directly in the url. Headers can also be added.

This tests that the status is a 200 OK or a redirect header, unless you pass in a status. A status of "*" will never fail; or you can assert a specific status (like 500).

Also, if any errors are written to the error stream this will raise an error.

post(url, params={}, headers={}, status=None, upload_files=()):

POSTS to the URL. Like GET, except also allows for uploading files. The uploaded files are a list of (field_name, filename, file_content).

If you don’t want to do a urlencoded post body, you can put a content-type header in your header, and pass the body in as a string with params.

The response object:

header(header_name, [default]):
Returns the named header. It’s an error if there is more than one matching header. If you don’t provide a default, it is an error if there is no matching header.
all_headers(header_name):
Returns a list of all matching headers.
follow(**kw):
Follows the redirect, returning the new response. It is an error if this response wasn’t a redirect. Any keyword arguments are passed to app.get (e.g., status).
x in res:
Returns True if the string is found in the response. Whitespace is normalized for this test.
mustcontain(*strings):
Raises an error if any of the strings are not found in the response.
showbrowser():
Opens the HTML response in a browser; useful for debugging.
str(res):
Gives a slightly-compacted version of the response.
click(description=None, linkid=None, href=None, anchor=None, index=None, verbose=False):
Clicks the described link (see docstring for more)
forms:
Return a dictionary of forms; you can use both indexes (refer to the forms in order) or the string ids of forms (if you’ve given them ids) to identify the form. See Form Submissions for more on the form objects.

Request objects:

url:
The url requested.
environ:
The environment used for the request.
full_url:
The url with query string.

Form Submissions¶

You can fill out and submit forms from your tests. First you get the form:

res = testapp.get('/entry_form')
form = res.forms[0]

Then you fill it in fields:

# when there's one unambiguous name field:
form['name'] = 'Bob'
# Enter something into the first field named 'age'
form.set('age', '45', index=1)

Finally you submit:

# Submit with no particular submit button pressed:
form.submit()
# Or submit a button:
form.submit('submit_button_name')

Framework Hooks¶

Frameworks can detect that they are in a testing environment by the presence (and truth) of the WSGI environmental variable "paste.testing".

More generally, frameworks can detect that something (possibly a test fixture) is ready to catch unexpected errors by the presence and truth of "paste.throw_errors" (this is sometimes set outside of testing fixtures too, when an error-handling middleware is in place).

Frameworks that want to expose the inner structure of the request may use "paste.testing_variables". This will be a dictionary – any values put into that dictionary will become attributes of the response object. So if you do env["paste.testing_variables"]['template'] = template_name in your framework, then response.template will be template_name.

Paste-1.7.5.1/docs/_build/modules/0000775000175000017500000000000011443716130015203 5ustar ianbianbPaste-1.7.5.1/docs/_build/modules/wsgiwrappers.html0000664000175000017500000002273711443716130020641 0ustar ianbianb paste.wsgiwrappers – Wrapper functions for WSGI request and response — Paste v1.7.5 documentation

paste.wsgiwrappers – Wrapper functions for WSGI request and response¶

WSGI Wrappers for a Request and Response

The WSGIRequest and WSGIResponse objects are light wrappers to make it easier to deal with an incoming request and sending a response.

Module Contents¶

class paste.wsgiwrappers.WSGIRequest(environ)¶

WSGI Request API Object

This object represents a WSGI request with a more friendly interface. This does not expose every detail of the WSGI environment, and attempts to express nothing beyond what is available in the environment dictionary.

The only state maintained in this object is the desired charset, its associated errors handler, and the decode_param_names option.

The incoming parameter values will be automatically coerced to unicode objects of the charset encoding when charset is set. The incoming parameter names are not decoded to unicode unless the decode_param_names option is enabled.

When unicode is expected, charset will overridden by the the value of the Content-Type header’s charset parameter if one was specified by the client.

The class variable defaults specifies default values for charset, errors, and langauge. These can be overridden for the current request via the registry.

The language default value is considered the fallback during i18n translations to ensure in odd cases that mixed languages don’t occur should the language file contain the string but not another language in the accepted languages list. The language value only applies when getting a list of accepted languages from the HTTP Accept header.

This behavior is duplicated from Aquarium, and may seem strange but is very useful. Normally, everything in the code is in “en-us”. However, the “en-us” translation catalog is usually empty. If the user requests ["en-us", "zh-cn"] and a translation isn’t found for a string in “en-us”, you don’t want gettext to fallback to “zh-cn”. You want it to just use the string itself. Hence, if a string isn’t found in the language catalog, the string in the source code will be used.

All other state is kept in the environment dictionary; this is essential for interoperability.

You are free to subclass this object.

class paste.wsgiwrappers.WSGIResponse(content='', mimetype=None, code=200)¶

A basic HTTP response with content, headers, and out-bound cookies

The class variable defaults specifies default values for content_type, charset and errors. These can be overridden for the current request via the registry.

Paste-1.7.5.1/docs/_build/modules/auth.digest.html0000664000175000017500000002712311443716127020323 0ustar ianbianb paste.auth.digest – HTTP Digest login — Paste v1.7.5 documentation

paste.auth.digest – HTTP Digest login¶

Digest HTTP/1.1 Authentication

This module implements Digest authentication as described by RFC 2617 [1] .

Basically, you just put this module before your application, and it takes care of requesting and handling authentication requests. This module has been tested with several common browsers “out-in-the-wild”.

>>> from paste.wsgilib import dump_environ
>>> from paste.httpserver import serve
>>> # from paste.auth.digest import digest_password, AuthDigestHandler
>>> realm = 'Test Realm'
>>> def authfunc(environ, realm, username):
...     return digest_password(realm, username, username)
>>> serve(AuthDigestHandler(dump_environ, realm, authfunc))
serving on...

This code has not been audited by a security expert, please use with caution (or better yet, report security holes). At this time, this implementation does not provide for further challenges, nor does it support Authentication-Info header. It also uses md5, and an option to use sha would be a good thing.

[1]http://www.faqs.org/rfcs/rfc2617.html

Module Contents¶

class paste.auth.digest.AuthDigestAuthenticator(realm, authfunc)¶

implementation of RFC 2617 - HTTP Digest Authentication

class paste.auth.digest.AuthDigestHandler(application, realm, authfunc)¶

middleware for HTTP Digest authentication (RFC 2617)

This component follows the procedure below:

  1. If the REMOTE_USER environment variable is already populated; then this middleware is a no-op, and the request is passed along to the application.
  2. If the HTTP_AUTHORIZATION header was not provided or specifies an algorithem other than digest, then a HTTPUnauthorized response is generated with the challenge.
  3. If the response is malformed or or if the user’s credientials do not pass muster, another HTTPUnauthorized is raised.
  4. If all goes well, and the user’s credintials pass; then REMOTE_USER environment variable is filled in and the AUTH_TYPE is listed as ‘digest’.

Parameters:

application

The application object is called only upon successful authentication, and can assume environ['REMOTE_USER'] is set. If the REMOTE_USER is already set, this middleware is simply pass-through.

realm

This is a identifier for the authority that is requesting authorization. It is shown to the user and should be unique within the domain it is being used.

authfunc

This is a callback function which performs the actual authentication; the signature of this callback is:

authfunc(environ, realm, username) -> hashcode

This module provides a ‘digest_password’ helper function which can help construct the hashcode; it is recommended that the hashcode is stored in a database, not the user’s actual password (since you only need the hashcode).

paste.auth.digest.digest_password(realm, username, password)¶

construct the appropriate hashcode needed for HTTP digest

paste.auth.digest.make_digest(app, global_conf, realm, authfunc, **kw)¶

Grant access via digest authentication

Config looks like this:

[filter:grant]
use = egg:Paste#auth_digest
realm=myrealm
authfunc=somepackage.somemodule:somefunction
Paste-1.7.5.1/docs/_build/modules/fixture.html0000664000175000017500000007043311443716127017574 0ustar ianbianb paste.fixture – Test applications — Paste v1.7.5 documentation

paste.fixture – Test applications¶

Contents

Routines for testing WSGI applications.

Most interesting is the TestApp for testing WSGI applications, and the TestFileEnvironment class for testing the effects of command-line scripts.

Module Contents¶

class paste.fixture.TestApp(app, namespace=None, relative_to=None, extra_environ=None, pre_request_hook=None, post_request_hook=None)¶
delete(url, params='', headers=None, extra_environ=None, status=None, expect_errors=False)¶

Do a DELETE request. Very like the .get() method. params are put in the body of the request.

Returns a response object

do_request(req, status)¶

Executes the given request (req), with the expected status. Generally .get() and .post() are used instead.

encode_multipart(params, files)¶

Encodes a set of parameters (typically a name/value list) and a set of files (a list of (name, filename, file_body)) into a typical POST body, returning the (content_type, body).

get(url, params=None, headers=None, extra_environ=None, status=None, expect_errors=False)¶

Get the given url (well, actually a path like '/page.html').

params:
A query string, or a dictionary that will be encoded into a query string. You may also include a query string on the url.
headers:
A dictionary of extra headers to send.
extra_environ:
A dictionary of environmental variables that should be added to the request.
status:
The integer status code you expect (if not 200 or 3xx). If you expect a 404 response, for instance, you must give status=404 or it will be an error. You can also give a wildcard, like '3*' or '*'.
expect_errors:
If this is not true, then if anything is written to wsgi.errors it will be an error. If it is true, then non-200/3xx responses are also okay.

Returns a response object

post(url, params='', headers=None, extra_environ=None, status=None, upload_files=None, expect_errors=False)¶

Do a POST request. Very like the .get() method. params are put in the body of the request.

upload_files is for file uploads. It should be a list of [(fieldname, filename, file_content)]. You can also use just [(fieldname, filename)] and the file content will be read from disk.

Returns a response object

put(url, params='', headers=None, extra_environ=None, status=None, upload_files=None, expect_errors=False)¶

Do a PUT request. Very like the .get() method. params are put in the body of the request.

upload_files is for file uploads. It should be a list of [(fieldname, filename, file_content)]. You can also use just [(fieldname, filename)] and the file content will be read from disk.

Returns a response object

reset()¶

Resets the state of the application; currently just clears saved cookies.

class paste.fixture.TestRequest(url, environ, expect_errors=False)¶

Forms¶

class paste.fixture.Form(response, text)¶

This object represents a form that has been found in a page. This has a couple useful attributes:

text:
the full HTML of the form.
action:
the relative URI of the action.
method:
the method (e.g., 'GET').
id:
the id, or None if not given.
fields:
a dictionary of fields, each value is a list of fields by that name. <input type="radio"> and <select> are both represented as single fields with multiple options.
get(name, index=None, default=<class 'paste.fixture.NoDefault'>)¶

Get the named/indexed field object, or default if no field is found.

select(name, value, index=None)¶

Like .set(), except also confirms the target is a <select>.

set(name, value, index=None)¶

Set the given name, using index to disambiguate.

submit(name=None, index=None, **args)¶

Submits the form. If name is given, then also select that button (using index to disambiguate)``.

Any extra keyword arguments are passed to the .get() or .post() method.

Returns a response object.

submit_fields(name=None, index=None)¶

Return a list of [(name, value), ...] for the current state of the form.

class paste.fixture.Field(form, tag, name, pos, value=None, id=None, **attrs)¶

Field object.

force_value(value)¶

Like setting a value, except forces it even for, say, hidden fields.

class paste.fixture.Select(*args, **attrs)¶

Field representing <select>

class paste.fixture.Radio(*args, **attrs)¶

Field representing <input type="radio">

class paste.fixture.Checkbox(*args, **attrs)¶

Field representing <input type="checkbox">

class paste.fixture.Text(form, tag, name, pos, value='', id=None, **attrs)¶

Field representing <input type="text">

class paste.fixture.Textarea(form, tag, name, pos, value='', id=None, **attrs)¶

Field representing <textarea>

class paste.fixture.Hidden(form, tag, name, pos, value='', id=None, **attrs)¶

Field representing <input type="hidden">

class paste.fixture.Submit(form, tag, name, pos, value=None, id=None, **attrs)¶

Field representing <input type="submit"> and <button>

Script Testing¶

class paste.fixture.TestFileEnvironment(base_path, template_path=None, script_path=None, environ=None, cwd=None, start_clear=True, ignore_paths=None, ignore_hidden=True)¶

This represents an environment in which files will be written, and scripts will be run.

clear()¶

Delete all the files in the base directory.

run(script, *args, **kw)¶

Run the command, with the given arguments. The script argument can have space-separated arguments, or you can use the positional arguments.

Keywords allowed are:

expect_error: (default False)
Don’t raise an exception in case of errors
expect_stderr: (default expect_error)
Don’t raise an exception if anything is printed to stderr
stdin: (default "")
Input to the script
printresult: (default True)
Print the result after running
cwd: (default self.cwd)
The working directory to run in

Returns a ProcResponse object.

writefile(path, content=None, frompath=None)¶

Write a file to the given path. If content is given then that text is written, otherwise the file in frompath is used. frompath is relative to self.template_path

class paste.fixture.ProcResult(test_env, args, stdin, stdout, stderr, returncode, files_before, files_after)¶

Represents the results of running a command in TestFileEnvironment.

Attributes to pay particular attention to:

stdout, stderr:
What is produced
files_created, files_deleted, files_updated:
Dictionaries mapping filenames (relative to the base_dir) to FoundFile or FoundDir objects.
class paste.fixture.FoundFile(base_path, path)¶

Represents a single file found as the result of a command.

Has attributes:

path:
The path of the file, relative to the base_path
full:
The full path
stat:
The results of os.stat. Also mtime and size contain the .st_mtime and st_size of the stat.
bytes:
The contents of the file.

You may use the in operator with these objects (tested against the contents of the file), and the .mustcontain() method.

class paste.fixture.FoundDir(base_path, path)¶

Represents a directory created by a command.

Paste-1.7.5.1/docs/_build/modules/urlmap.html0000664000175000017500000001735611443716130017405 0ustar ianbianb paste.urlmap – Map URL paths — Paste v1.7.5 documentation

paste.urlmap – Map URL paths¶

Map URL prefixes to WSGI applications. See URLMap

Module Contents¶

class paste.urlmap.URLMap(not_found_app=None)¶

URLMap instances are dictionary-like object that dispatch to one of several applications based on the URL.

The dictionary keys are URLs to match (like PATH_INFO.startswith(url)), and the values are applications to dispatch to. URLs are matched most-specific-first, i.e., longest URL first. The SCRIPT_NAME and PATH_INFO environmental variables are adjusted to indicate the new context.

URLs can also include domains, like http://blah.com/foo, or as tuples ('blah.com', '/foo'). This will match domain names; without the http://domain or with a domain of None any domain will be matched (so long as no other explicit domain matches).

paste.urlmap.urlmap_factory(loader, global_conf, **local_conf)¶
class paste.urlmap.PathProxyURLMap(map, base_paste_url, base_path, builder)¶

This is a wrapper for URLMap that catches any strings that are passed in as applications; these strings are treated as filenames (relative to base_path) and are passed to the callable builder, which will return an application.

This is intended for cases when configuration files can be treated as applications.

base_paste_url is the URL under which all applications added through this wrapper must go. Use "" if you want this to not change incoming URLs.

Paste-1.7.5.1/docs/_build/modules/recursive.html0000664000175000017500000003725511443716130020114 0ustar ianbianb paste.recursive – internal requests — Paste v1.7.5 documentation

paste.recursive – internal requests¶

Middleware to make internal requests and forward requests internally.

When applied, several keys are added to the environment that will allow you to trigger recursive redirects and forwards.

paste.recursive.include:
When you call environ['paste.recursive.include'](new_path_info) a response will be returned. The response has a body attribute, a status attribute, and a headers attribute.
paste.recursive.script_name:
The SCRIPT_NAME at the point that recursive lives. Only paths underneath this path can be redirected to.
paste.recursive.old_path_info:
A list of previous PATH_INFO values from previous redirects.

Raise ForwardRequestException(new_path_info) to do a forward (aborting the current request).

Module Contents¶

class paste.recursive.RecursiveMiddleware(application, global_conf=None)¶

A WSGI middleware that allows for recursive and forwarded calls. All these calls go to the same ‘application’, but presumably that application acts differently with different URLs. The forwarded URLs must be relative to this container.

Interface is entirely through the paste.recursive.forward and paste.recursive.include environmental keys.

paste.recursive.ForwardRequestException(url=None, environ={}, factory=None, path_info=None)¶

Used to signal that a request should be forwarded to a different location.

url
The URL to forward to starting with a / and relative to RecursiveMiddleware. URL fragments can also contain query strings so /error?code=404 would be a valid URL fragment.
environ
An altertative WSGI environment dictionary to use for the forwarded request. If specified is used instead of the url_fragment
factory
If specifed factory is used instead of url or environ. factory is a callable that takes a WSGI application object as the first argument and returns an initialised WSGI middleware which can alter the forwarded response.

Basic usage (must have RecursiveMiddleware present) :

from paste.recursive import ForwardRequestException
def app(environ, start_response):
    if environ['PATH_INFO'] == '/hello':
        start_response("200 OK", [('Content-type', 'text/plain')])
        return ['Hello World!']
    elif environ['PATH_INFO'] == '/error':
        start_response("404 Not Found", [('Content-type', 'text/plain')])
        return ['Page not found']
    else:
        raise ForwardRequestException('/error')

from paste.recursive import RecursiveMiddleware
app = RecursiveMiddleware(app)

If you ran this application and visited /hello you would get a Hello World! message. If you ran the application and visited /not_found a ForwardRequestException would be raised and the caught by the RecursiveMiddleware. The RecursiveMiddleware would then return the headers and response from the /error URL but would display a 404 Not found status message.

You could also specify an environ dictionary instead of a url. Using the same example as before:

def app(environ, start_response):
    ... same as previous example ...
    else:
        new_environ = environ.copy()
        new_environ['PATH_INFO'] = '/error'
        raise ForwardRequestException(environ=new_environ)

Finally, if you want complete control over every aspect of the forward you can specify a middleware factory. For example to keep the old status code but use the headers and resposne body from the forwarded response you might do this:

from paste.recursive import ForwardRequestException
from paste.recursive import RecursiveMiddleware
from paste.errordocument import StatusKeeper

def app(environ, start_response):
    if environ['PATH_INFO'] == '/hello':
        start_response("200 OK", [('Content-type', 'text/plain')])
        return ['Hello World!']
    elif environ['PATH_INFO'] == '/error':
        start_response("404 Not Found", [('Content-type', 'text/plain')])
        return ['Page not found']
    else:
        def factory(app):
            return StatusKeeper(app, status='404 Not Found', url='/error')
        raise ForwardRequestException(factory=factory)

app = RecursiveMiddleware(app)
Paste-1.7.5.1/docs/_build/modules/errordocument.html0000664000175000017500000002161311443716127020772 0ustar ianbianb paste.errordocument – Do internal redirects for error responses — Paste v1.7.5 documentation

paste.errordocument – Do internal redirects for error responses¶

Middleware to display error documents for certain status codes

The middleware in this module can be used to intercept responses with specified status codes and internally forward the request to an appropriate URL where the content can be displayed to the user as an error document.

Module Contents¶

class paste.errordocument.StatusBasedForward(app, mapper, global_conf=None, **params)¶

Middleware that lets you test a response against a custom mapper object to programatically determine whether to internally forward to another URL and if so, which URL to forward to.

If you don’t need the full power of this middleware you might choose to use the simpler forward middleware instead.

The arguments are:

app
The WSGI application or middleware chain.
mapper
A callable that takes a status code as the first parameter, a message as the second, and accepts optional environ, global_conf and named argments afterwards. It should return a URL to forward to or None if the code is not to be intercepted.
global_conf
Optional default configuration from your config file. If debug is set to true a message will be written to wsgi.errors on each internal forward stating the URL forwarded to.
**params
Optional, any other configuration and extra arguments you wish to pass which will in turn be passed back to the custom mapper object.

Here is an example where a 404 File Not Found status response would be redirected to the URL /error?code=404&message=File%20Not%20Found. This could be useful for passing the status code and message into another application to display an error document:

from paste.errordocument import StatusBasedForward
from paste.recursive import RecursiveMiddleware
from urllib import urlencode

def error_mapper(code, message, environ, global_conf, kw)
    if code in [404, 500]:
        params = urlencode({'message':message, 'code':code})
        url = '/error?'%(params)
        return url
    else:
        return None

app = RecursiveMiddleware(
    StatusBasedForward(app, mapper=error_mapper),
)
paste.errordocument.make_errordocument(app, global_conf, **kw)¶

Paste Deploy entry point to create a error document wrapper.

Use like:

[filter-app:main]
use = egg:Paste#errordocument
next = real-app
500 = /lib/msg/500.html
404 = /lib/msg/404.html
Paste-1.7.5.1/docs/_build/modules/auth.cas.html0000664000175000017500000001645011443716127017613 0ustar ianbianb paste.auth.cas – CAS authentication — Paste v1.7.5 documentation

paste.auth.cas – CAS authentication¶

CAS 1.0 Authentication

The Central Authentication System is a straight-forward single sign-on mechanism developed by Yale University’s ITS department. It has since enjoyed widespread success and is deployed at many major universities and some corporations.

https://clearinghouse.ja-sig.org/wiki/display/CAS/Home http://www.yale.edu/tp/auth/usingcasatyale.html

This implementation has the goal of maintaining current path arguments passed to the system so that it can be used as middleware at any stage of processing. It has the secondary goal of allowing for other authentication methods to be used concurrently.

Module Contents¶

class paste.auth.cas.AuthCASHandler¶

middleware to implement CAS 1.0 authentication

There are several possible outcomes:

  1. If the REMOTE_USER environment variable is already populated; then this middleware is a no-op, and the request is passed along to the application.
  2. If a query argument ‘ticket’ is found, then an attempt to validate said ticket /w the authentication service done. If the ticket is not validated; an 403 ‘Forbidden’ exception is raised. Otherwise, the REMOTE_USER variable is set with the NetID that was validated and AUTH_TYPE is set to “cas”.
  3. Otherwise, a 303 ‘See Other’ is returned to the client directing them to login using the CAS service. After logon, the service will send them back to this same URL, only with a ‘ticket’ query argument.

Parameters:

authority

This is a fully-qualified URL to a CAS 1.0 service. The URL should end with a ‘/’ and have the ‘login’ and ‘validate’ sub-paths as described in the CAS 1.0 documentation.
Paste-1.7.5.1/docs/_build/modules/cascade.html0000664000175000017500000001547711443716127017500 0ustar ianbianb paste.cascade – send requests to multiple applications until success — Paste v1.7.5 documentation

paste.cascade – send requests to multiple applications until success¶

Cascades through several applications, so long as applications return 404 Not Found.

Module Contents¶

class paste.cascade.Cascade(applications, catch=(404, ))¶

Passed a list of applications, Cascade will try each of them in turn. If one returns a status code listed in catch (by default just 404 Not Found) then the next application is tried.

If all applications fail, then the last application’s failure response is used.

Instances of this class are WSGI applications.

paste.cascade.make_cascade(loader, global_conf, catch='404', **local_conf)¶

Entry point for Paste Deploy configuration

Expects configuration like:

[composit:cascade]
use = egg:Paste#cascade
# all start with 'app' and are sorted alphabetically
app1 = foo
app2 = bar
...
catch = 404 500 ...
Paste-1.7.5.1/docs/_build/modules/debug.prints.html0000664000175000017500000001560611443716127020513 0ustar ianbianb paste.debug.prints – capture print output — Paste v1.7.5 documentation

paste.debug.prints – capture print output¶

Middleware that displays everything that is printed inline in application pages.

Anything printed during the request will get captured and included on the page. It will usually be included as a floating element in the top right hand corner of the page. If you want to override this you can include a tag in your template where it will be placed:

<pre id="paste-debug-prints"></pre>

You might want to include style="white-space: normal", as all the whitespace will be quoted, and this allows the text to wrap if necessary.

Module Contents¶

class paste.debug.prints.PrintDebugMiddleware(app, global_conf=None, force_content_type=False, print_wsgi_errors=True, replace_stdout=False)¶

This middleware captures all the printed statements, and inlines them in HTML pages, so that you can see all the (debug-intended) print statements in the page itself.

There are two keys added to the environment to control this: environ['paste.printdebug_listeners'] is a list of functions that will be called everytime something is printed.

environ['paste.remove_printdebug'] is a function that, if called, will disable printing of output for that request.

If you have replace_stdout=True then stdout is replaced, not captured.

Paste-1.7.5.1/docs/_build/modules/url.html0000664000175000017500000002502111443716130016673 0ustar ianbianb paste.url – URL convenience class — Paste v1.7.5 documentation

paste.url – URL convenience class¶

This module implements a class for handling URLs.

Module Contents¶

class paste.url.URL(url, vars=None, attrs=None, params=None)¶
>>> u = URL('http://localhost')
>>> u
<URL http://localhost>
>>> u = u['view']
>>> str(u)
'http://localhost/view'
>>> u['//foo'].param(content='view').html
'<a href="http://localhost/view/foo">view</a>'
>>> u.param(confirm='Really?', content='goto').html
'<a href="http://localhost/view" onclick="return confirm(\'Really?\')">goto</a>'
>>> u(title='See "it"', content='goto').html
'<a href="http://localhost/view?title=See+%22it%22">goto</a>'
>>> u('another', var='fuggetaboutit', content='goto').html
'<a href="http://localhost/view/another?var=fuggetaboutit">goto</a>'
>>> u.attr(content='goto').html
Traceback (most recent call last):
    ....
ValueError: You must give a content param to <URL http://localhost/view attrs(content="goto")> generate anchor tags
>>> str(u['foo=bar%20stuff'])
'http://localhost/view?foo=bar+stuff'
class paste.url.Image(url, vars=None, attrs=None, params=None)¶
>>> i = Image('/images')
>>> i = i / '/foo.png'
>>> i.html
'<img src="/images/foo.png" />'
>>> str(i['alt=foo'])
'<img src="/images/foo.png" alt="foo" />'
>>> i.href
'/images/foo.png'
Paste-1.7.5.1/docs/_build/modules/session.html0000664000175000017500000001747611443716130017573 0ustar ianbianb paste.session – Simple file-based sessions — Paste v1.7.5 documentation

paste.session – Simple file-based sessions¶

Creates a session object in your WSGI environment.

Use like:

..code-block:: Python

environ[‘paste.session.factory’]()

This will return a dictionary. The contents of this dictionary will be saved to disk when the request is completed. The session will be created when you first fetch the session dictionary, and a cookie will be sent in that case. There’s current no way to use sessions without cookies, and there’s no way to delete a session except to clear its data.

@@: This doesn’t do any locking, and may cause problems when a single session is accessed concurrently. Also, it loads and saves the session for each request, with no caching. Also, sessions aren’t expired.

Module Contents¶

class paste.session.SessionMiddleware(application, global_conf=None, **factory_kw)¶
paste.session.make_session_middleware(app, global_conf, session_expiration=<dynamic default>, expiration=<dynamic default>, cookie_name=<dynamic default>, session_file_path=<dynamic default>, chmod=<dynamic default>)¶

Adds a middleware that handles sessions for your applications. The session is a peristent dictionary. To get this dictionary in your application, use environ['paste.session.factory']() which returns this persistent dictionary.

Configuration:

session_expiration:
The time each session lives, in minutes. This controls the cookie expiration. Default 12 hours.
expiration:
The time each session lives on disk. Old sessions are culled from disk based on this. Default 48 hours.
cookie_name:
The cookie name used to track the session. Use different names to avoid session clashes.
session_file_path:
Sessions are put in this location, default /tmp.
chmod:
The octal chmod you want to apply to new sessions (e.g., 660 to make the sessions group readable/writable)

Each of these also takes from the global configuration. cookie_name and chmod take from session_cookie_name and session_chmod

Paste-1.7.5.1/docs/_build/modules/pony.html0000664000175000017500000001332211443716130017057 0ustar ianbianb paste.pony – Add pony power to your application — Paste v1.7.5 documentation

paste.pony – Add pony power to your application¶

We have a pony and/or a unicorn.

Module Contents¶

class paste.pony.PonyMiddleware(application)¶
paste.pony.make_pony(app, global_conf)¶

Adds pony power to any application, at /pony

Paste-1.7.5.1/docs/_build/modules/request.html0000664000175000017500000003473411443716130017574 0ustar ianbianb paste.request – Utility functions for the WSGI environment — Paste v1.7.5 documentation

paste.request – Utility functions for the WSGI environment¶

This module provides helper routines with work directly on a WSGI environment to solve common requirements.

  • get_cookies(environ)

  • parse_querystring(environ)

  • parse_formvars(environ, include_get_vars=True)

  • construct_url(environ, with_query_string=True, with_path_info=True,

    script_name=None, path_info=None, querystring=None)

  • path_info_split(path_info)

  • path_info_pop(environ)

  • resolve_relative_url(url, environ)

Module Contents¶

paste.request.get_cookies(environ)¶

Gets a cookie object (which is a dictionary-like object) from the request environment; caches this value in case get_cookies is called again for the same request.

Return a plain dictionary of cookies as found in the request.

Unlike get_cookies this returns a dictionary, not a SimpleCookie object. For incoming cookies a dictionary fully represents the information. Like get_cookies this caches and checks the cache.

paste.request.parse_querystring(environ)¶

Parses a query string into a list like [(name, value)]. Caches this value in case parse_querystring is called again for the same request.

You can pass the result to dict(), but be aware that keys that appear multiple times will be lost (only the last value will be preserved).

paste.request.parse_formvars(environ, include_get_vars=True)¶

Parses the request, returning a MultiDict of form variables.

If include_get_vars is true then GET (query string) variables will also be folded into the MultiDict.

All values should be strings, except for file uploads which are left as FieldStorage instances.

If the request was not a normal form request (e.g., a POST with an XML body) then environ['wsgi.input'] won’t be read.

paste.request.construct_url(environ, with_query_string=True, with_path_info=True, script_name=None, path_info=None, querystring=None)¶

Reconstructs the URL from the WSGI environment.

You may override SCRIPT_NAME, PATH_INFO, and QUERYSTRING with the keyword arguments.

paste.request.path_info_split(path_info)¶

Splits off the first segment of the path. Returns (first_part, rest_of_path). first_part can be None (if PATH_INFO is empty), ‘’ (if PATH_INFO is ‘/’), or a name without any /’s. rest_of_path can be ‘’ or a string starting with /.

paste.request.path_info_pop(environ)¶

‘Pops’ off the next segment of PATH_INFO, pushing it onto SCRIPT_NAME, and returning that segment.

For instance:

>>> def call_it(script_name, path_info):
...     env = {'SCRIPT_NAME': script_name, 'PATH_INFO': path_info}
...     result = path_info_pop(env)
...     print 'SCRIPT_NAME=%r; PATH_INFO=%r; returns=%r' % (
...         env['SCRIPT_NAME'], env['PATH_INFO'], result)
>>> call_it('/foo', '/bar')
SCRIPT_NAME='/foo/bar'; PATH_INFO=''; returns='bar'
>>> call_it('/foo/bar', '')
SCRIPT_NAME='/foo/bar'; PATH_INFO=''; returns=None
>>> call_it('/foo/bar', '/')
SCRIPT_NAME='/foo/bar/'; PATH_INFO=''; returns=''
>>> call_it('', '/1/2/3')
SCRIPT_NAME='/1'; PATH_INFO='/2/3'; returns='1'
>>> call_it('', '//1/2')
SCRIPT_NAME='//1'; PATH_INFO='/2'; returns='1'
paste.request.resolve_relative_url(url, environ)¶

Resolve the given relative URL as being relative to the location represented by the environment. This can be used for redirecting to a relative path. Note: if url is already absolute, this function will (intentionally) have no effect on it.

class paste.request.EnvironHeaders(environ)¶

An object that represents the headers as present in a WSGI environment.

This object is a wrapper (with no internal state) for a WSGI request object, representing the CGI-style HTTP_* keys as a dictionary. Because a CGI environment can only hold one value for each key, this dictionary is single-valued (unlike outgoing headers).

Paste-1.7.5.1/docs/_build/modules/gzipper.html0000664000175000017500000001401311443716127017556 0ustar ianbianb paste.gzipper – Gzip-compress responses — Paste v1.7.5 documentation

paste.gzipper – Gzip-compress responses¶

WSGI middleware

Gzip-encodes the response.

Module Contents¶

class paste.gzipper.middleware(application, compress_level=6)¶
paste.gzipper.make_gzip_middleware(app, global_conf, compress_level=6)¶

Wrap the middleware, so that it applies gzipping to a response when it is supported by the browser and the content is of type text/* or application/*

Paste-1.7.5.1/docs/_build/modules/auth.form.html0000664000175000017500000002601411443716127020005 0ustar ianbianb paste.auth.form – HTML form/cookie authentication — Paste v1.7.5 documentation

paste.auth.form – HTML form/cookie authentication¶

Authentication via HTML Form

This is a very simple HTML form login screen that asks for the username and password. This middleware component requires that an authorization function taking the name and passsword and that it be placed in your application stack. This class does not include any session management code or way to save the user’s authorization; however, it is easy enough to put paste.auth.cookie in your application stack.

>>> from paste.wsgilib import dump_environ
>>> from paste.httpserver import serve
>>> from paste.auth.cookie import AuthCookieHandler
>>> from paste.auth.form import AuthFormHandler
>>> def authfunc(environ, username, password):
...    return username == password
>>> serve(AuthCookieHandler(
...           AuthFormHandler(dump_environ, authfunc)))
serving on...

Module Contents¶

class paste.auth.form.AuthFormHandler(application, authfunc, template=None)¶

HTML-based login middleware

This causes a HTML form to be returned if REMOTE_USER is not found in the environ. If the form is returned, the username and password combination are given to a user-supplied authentication function, authfunc. If this is successful, then application processing continues.

Parameters:

application

The application object is called only upon successful authentication, and can assume environ['REMOTE_USER'] is set. If the REMOTE_USER is already set, this middleware is simply pass-through.

authfunc

This is a mandatory user-defined function which takes a environ, username and password for its first three arguments. It should return True if the user is authenticated.

template

This is an optional (a default is provided) HTML fragment that takes exactly one %s substution argument; which must be used for the form’s action to ensure that this middleware component does not alter the current path. The HTML form must use POST and have two input names: username and password.

Since the authentication form is submitted (via POST) neither the PATH_INFO nor the QUERY_STRING are accessed, and hence the current path remains _unaltered_ through the entire authentication process. If authentication succeeds, the REQUEST_METHOD is converted from a POST to a GET, so that a redirect is unnecessary (unlike most form auth implementations)

paste.auth.form.make_form(app, global_conf, realm, authfunc, **kw)¶

Grant access via form authentication

Config looks like this:

[filter:grant]
use = egg:Paste#auth_form
realm=myrealm
authfunc=somepackage.somemodule:somefunction
Paste-1.7.5.1/docs/_build/modules/httpheaders.html0000664000175000017500000006604411443716130020416 0ustar ianbianb paste.httpheaders – Manipulate HTTP Headers — Paste v1.7.5 documentation

paste.httpheaders – Manipulate HTTP Headers¶

HTTP Message Header Fields (see RFC 4229)

This contains general support for HTTP/1.1 message headers [1] in a manner that supports WSGI environ [2] and response_headers [3]. Specifically, this module defines a HTTPHeader class whose instances correspond to field-name items. The actual field-content for the message-header is stored in the appropriate WSGI collection (either the environ for requests, or response_headers for responses).

Each HTTPHeader instance is a callable (defining __call__) that takes one of the following:

  • an environ dictionary, returning the corresponding header value by according to the WSGI’s HTTP_ prefix mechanism, e.g., USER_AGENT(environ) returns environ.get('HTTP_USER_AGENT')
  • a response_headers list, giving a comma-delimited string for each corresponding header_value tuple entries (see below).
  • a sequence of string *args that are comma-delimited into a single string value: CONTENT_TYPE("text/html","text/plain") returns "text/html, text/plain"
  • a set of **kwargs keyword arguments that are used to create a header value, in a manner dependent upon the particular header in question (to make value construction easier and error-free): CONTENT_DISPOSITION(max_age=CONTENT_DISPOSITION.ONEWEEK) returns "public, max-age=60480"

Each HTTPHeader instance also provides several methods to act on a WSGI collection, for removing and setting header values.

delete(collection)

This method removes all entries of the corresponding header from the given collection (environ or response_headers), e.g., USER_AGENT.delete(environ) deletes the ‘HTTP_USER_AGENT’ entry from the environ.

update(collection, *args, **kwargs)

This method does an in-place replacement of the given header entry, for example: CONTENT_LENGTH(response_headers,len(body))

The first argument is a valid environ dictionary or response_headers list; remaining arguments are passed on to __call__(*args, **kwargs) for value construction.

apply(collection, **kwargs)

This method is similar to update, only that it may affect other headers. For example, according to recommendations in RFC 2616, certain Cache-Control configurations should also set the Expires header for HTTP/1.0 clients. By default, apply() is simply update() but limited to keyword arguments.

This particular approach to managing headers within a WSGI collection has several advantages:

  1. Typos in the header name are easily detected since they become a NameError when executed. The approach of using header strings directly can be problematic; for example, the following should return None : environ.get("HTTP_ACCEPT_LANGUAGES")
  2. For specific headers with validation, using __call__ will result in an automatic header value check. For example, the _ContentDisposition header will reject a value having maxage or max_age (the appropriate parameter is max-age ).
  3. When appending/replacing headers, the field-name has the suggested RFC capitalization (e.g. Content-Type or ETag) for user-agents that incorrectly use case-sensitive matches.
  4. Some headers (such as Content-Type) are 0, that is, only one entry of this type may occur in a given set of response_headers. This module knows about those cases and enforces this cardinality constraint.
  5. The exact details of WSGI header management are abstracted so the programmer need not worry about operational differences between environ dictionary or response_headers list.
  6. Sorting of HTTPHeaders is done following the RFC suggestion that general-headers come first, followed by request and response headers, and finishing with entity-headers.
  7. Special care is given to exceptional cases such as Set-Cookie which violates the RFC’s recommendation about combining header content into a single entry using comma separation.

A particular difficulty with HTTP message headers is a categorization of sorts as described in section 4.2:

Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible to combine the multiple header fields into one “field-name: field-value” pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma.

This creates three fundamentally different kinds of headers:

  • Those that do not have a #(values) production, and hence are singular and may only occur once in a set of response fields; this case is handled by the _SingleValueHeader subclass.
  • Those which have the #(values) production and follow the combining rule outlined above; our _MultiValueHeader case.
  • Those which are multi-valued, but cannot be combined (such as the Set-Cookie header due to its Expires parameter); or where combining them into a single header entry would cause common user-agents to fail (WWW-Authenticate, Warning) since they fail to handle dates even when properly quoted. This case is handled by _MultiEntryHeader.

Since this project does not have time to provide rigorous support and validation for all headers, it does a basic construction of headers listed in RFC 2616 (plus a few others) so that they can be obtained by simply doing from paste.httpheaders import *; the name of the header instance is the “common name” less any dashes to give CamelCase style names.

[1]http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
[2]http://www.python.org/peps/pep-0333.html#environ-variables
[3]http://www.python.org/peps/pep-0333.html#the-start-response-callable
paste.httpheaders.get_header(name, raiseError=True)¶

find the given HTTPHeader instance

This function finds the corresponding HTTPHeader for the name provided. So that python-style names can be used, underscores are converted to dashes before the lookup.

paste.httpheaders.list_headers(general=None, request=None, response=None, entity=None)¶

list all headers for a given category

paste.httpheaders.normalize_headers(response_headers, strict=True)¶

sort headers as suggested by RFC 2616

This alters the underlying response_headers to use the common name for each header; as well as sorting them with general headers first, followed by request/response headers, then entity headers, and unknown headers last.

class paste.httpheaders.HTTPHeader¶

an HTTP header

HTTPHeader instances represent a particular field-name of an HTTP message header. They do not hold a field-value, but instead provide operations that work on is corresponding values. Storage of the actual field values is done with WSGI environ or response_headers as appropriate. Typically, a sub-classes that represent a specific HTTP header, such as _ContentDisposition, are 0. Once constructed the HTTPHeader instances themselves are immutable and stateless.

For purposes of documentation a “container” refers to either a WSGI environ dictionary, or a response_headers list.

Member variables (and correspondingly constructor arguments).

name

the field-name of the header, in “common form” as presented in RFC 2616; e.g. ‘Content-Type’

category

one of ‘general’, ‘request’, ‘response’, or ‘entity’

version

version of HTTP (informational) with which the header should be recognized

sort_order

sorting order to be applied before sorting on field-name when ordering headers in a response

Special Methods:

__call__

The primary method of the HTTPHeader instance is to make it a callable, it takes either a collection, a string value, or keyword arguments and attempts to find/construct a valid field-value

__lt__

This method is used so that HTTPHeader objects can be sorted in a manner suggested by RFC 2616.

__str__

The string-value for instances of this class is the field-name.

Primary Methods:

delete()

remove the all occurrences (if any) of the given header in the collection provided

update()

replaces (if they exist) all field-value items in the given collection with the value provided

tuples()

returns a set of (field-name, field-value) tuples 5 for extending response_headers

Custom Methods (these may not be implemented):

apply()

similar to update, but with two differences; first, only keyword arguments can be used, and second, specific sub-classes may introduce side-effects

parse()

converts a string value of the header into a more usable form, such as time in seconds for a date header, etc.

The collected versions of initialized header instances are immediately registered and accessible through the get_header function. Do not inherit from this directly, use one of _SingleValueHeader, _MultiValueHeader, or _MultiEntryHeader as appropriate.

apply(collection, **kwargs)¶

update the collection /w header value (may have side effects)

This method is similar to update only that usage may result in other headers being changed as recommended by the corresponding specification. The return value is defined by the particular sub-class. For example, the _CacheControl.apply() sets the Expires header in addition to its normal behavior.

compose(**kwargs)¶

build header value from keyword arguments

This method is used to build the corresponding header value when keyword arguments (or no arguments) were provided. The result should be a sequence of values. For example, the Expires header takes a keyword argument time (e.g. time.time()) from which it returns a the corresponding date.

delete(collection)¶

removes all occurances of the header from the collection provided

parse(*args, **kwargs)¶

convert raw header value into more usable form

This method invokes values() with the arguments provided, parses the header results, and then returns a header-specific data structure corresponding to the header. For example, the Expires header returns seconds (as returned by time.time())

update(collection, *args, **kwargs)¶

updates the collection with the provided header value

This method replaces (in-place when possible) all occurrences of the given header with the provided value. If no value is provided, this is the same as remove (note that this case can only occur if the target is a collection w/o a corresponding header value). The return value is the new header value (which could be a list for _MultiEntryHeader instances).

values(*args, **kwargs)¶

find/construct field-value(s) for the given header

Resolution is done according to the following arguments:

  • If only keyword arguments are given, then this is equivalent to compose(**kwargs).
  • If the first (and only) argument is a dict, it is assumed to be a WSGI environ and the result of the corresponding HTTP_ entry is returned.
  • If the first (and only) argument is a list, it is assumed to be a WSGI response_headers and the field-value(s) for this header are collected and returned.
  • In all other cases, the arguments are collected, checked that they are string values, possibly verified by the header’s logic, and returned.

At this time it is an error to provide keyword arguments if args is present (this might change). It is an error to provide both a WSGI object and also string arguments. If no arguments are provided, then compose() is called to provide a default value for the header; if there is not default it is an error.

class paste.httpheaders.EnvironVariable¶

a CGI environ variable as described by WSGI

This is a helper object so that standard WSGI environ variables can be extracted w/o syntax error possibility.

Python Paste

This Page

Paste-1.7.5.1/docs/_build/modules/debug.debugapp.html0000664000175000017500000001601411443716127020755 0ustar ianbianb paste.debug.debugapp – debug app — Paste v1.7.5 documentation

paste.debug.debugapp – debug app¶

Various Applications for Debugging/Testing Purposes

Module Contents¶

class paste.debug.debugapp.SimpleApplication¶

Produces a simple web page

class paste.debug.debugapp.SlowConsumer(chunk_size=4096, delay=1, progress=True)¶

Consumes an upload slowly...

NOTE: This should use the iterator form of wsgi.input,
but it isn’t implemented in paste.httpserver.
paste.debug.debugapp.make_test_app(global_conf)¶

Produces a simple web page

paste.debug.debugapp.make_slow_app(global_conf, chunk_size=4096, delay=1, progress=True)¶

Consumes an upload slowly...

NOTE: This should use the iterator form of wsgi.input,
but it isn’t implemented in paste.httpserver.
Paste-1.7.5.1/docs/_build/modules/debug.profile.html0000664000175000017500000002044411443716127020630 0ustar ianbianb paste.debug.profile – profile applications and requests — Paste v1.7.5 documentation

paste.debug.profile – profile applications and requests¶

Middleware that profiles the request and displays profiling information at the bottom of each page.

Module Contents¶

class paste.debug.profile.ProfileMiddleware(app, global_conf=None, log_filename='profile.log.tmp', limit=40)¶

Middleware that profiles all requests.

All HTML pages will have profiling information appended to them. The data is isolated to that single request, and does not include data from previous requests.

This uses the hotshot module, which affects performance of the application. It also runs in a single-threaded mode, so it is only usable in development environments.

paste.debug.profile.make_profile_middleware(app, global_conf, log_filename='profile.log.tmp', limit=40)¶

Wrap the application in a component that will profile each request. The profiling data is then appended to the output of each page.

Note that this serializes all requests (i.e., removing concurrency). Therefore never use this in production.

paste.debug.profile.profile_decorator(**options)¶

Profile a single function call.

Used around a function, like:

@profile_decorator(options...)
def ...

All calls to the function will be profiled. The options are all keywords, and are:

log_file:
The filename to log to (or 'stdout' or 'stderr'). Default: stderr.
display_limit:
Only show the top N items, default: 20.
sort_stats:
A list of string-attributes to sort on. Default ('time', 'calls').
strip_dirs:
Strip directories/module names from files? Default True.
add_info:
If given, this info will be added to the report (for your own tracking). Default: none.
log_filename:
The temporary filename to log profiling data to. Default; ./profile_data.log.tmp
no_profile:
If true, then don’t actually profile anything. Useful for conditional profiling.
Paste-1.7.5.1/docs/_build/modules/util.import_string.html0000664000175000017500000002022311443716130021744 0ustar ianbianb paste.util.import_string – Import objects from strings — Paste v1.7.5 documentation

paste.util.import_string – Import objects from strings¶

‘imports’ a string – converts a string to a Python object, importing any necessary modules and evaluating the expression. Everything before the : in an import expression is the module path; everything after is an expression to be evaluated in the namespace of that module.

Alternately, if no : is present, then import the modules and get the attributes as necessary. Arbitrary expressions are not allowed in that case.

Module Contents¶

paste.util.import_string.eval_import(s)¶

Import a module, or import an object from a module.

A module name like foo.bar:baz() can be used, where foo.bar is the module, and baz() is an expression evaluated in the context of that module. Note this is not safe on arbitrary strings because of the eval.

paste.util.import_string.simple_import(s)¶

Import a module, or import an object from a module.

A name like foo.bar.baz can be a module foo.bar.baz or a module foo.bar with an object baz in it, or a module foo with an object bar with an attribute baz.

paste.util.import_string.import_module(s)¶

Import a module.

paste.util.import_string.try_import_module(module_name)¶

Imports a module, but catches import errors. Only catches errors when that module doesn’t exist; if that module itself has an import error it will still get raised. Returns None if the module doesn’t exist.

Paste-1.7.5.1/docs/_build/modules/auth.open_id.html0000664000175000017500000001203211355171742020452 0ustar ianbianb paste.auth.open_id – OpenID authentication — Paste v1.7.3 documentation

paste.auth.open_id – OpenID authentication¶

Module Contents¶

Paste-1.7.5.1/docs/_build/modules/evalexception.html0000664000175000017500000001301211443716127020742 0ustar ianbianb paste.evalexception – Interactive debugging for errors — Paste v1.7.5 documentation

paste.evalexception – Interactive debugging for errors¶

An exception handler for interactive debugging

Module Contents¶

class paste.evalexception.EvalException(application, global_conf=None, xmlhttp_key=None)¶
Paste-1.7.5.1/docs/_build/modules/fileapp.html0000664000175000017500000002272311443716127017525 0ustar ianbianb paste.fileapp – Serve files — Paste v1.7.5 documentation

paste.fileapp – Serve files¶

This module handles sending static content such as in-memory data or files. At this time it has cache helpers and understands the if-modified-since request header.

Module Contents¶

class paste.fileapp.FileApp(filename, headers=None, **kwargs)¶

Returns an application that will send the file at the given filename. Adds a mime type based on mimetypes.guess_type(). See DataApp for the arguments beyond filename.

class paste.fileapp.DirectoryApp(path)¶

Returns an application that dispatches requests to corresponding FileApps based on PATH_INFO. FileApp instances are cached. This app makes sure not to serve any files that are not in a subdirectory. To customize FileApp creation override DirectoryApp.make_fileapp

paste.fileapp.DataApp(content, headers=None, allowed_methods=None, **kwargs)¶

Returns an application that will send content in a single chunk, this application has support for setting cache-control and for responding to conditional (or HEAD) requests.

Constructor Arguments:

content the content being sent to the client

headers the headers to send /w the response

The remaining kwargs correspond to headers, where the underscore is replaced with a dash. These values are only added to the headers if they are not already provided; thus, they can be used for default values. Examples include, but are not limited to:

content_type content_encoding content_location

cache_control()

This method provides validated construction of the Cache-Control header as well as providing for automated filling out of the EXPIRES header for HTTP/1.0 clients.

set_content()

This method provides a mechanism to set the content after the application has been constructed. This method does things like changing Last-Modified and Content-Length headers.
paste.fileapp.ArchiveStore(filepath)¶

Returns an application that serves up a DataApp for items requested in a given zip or tar archive.

Constructor Arguments:

filepath the path to the archive being served

cache_control()

This method provides validated construction of the Cache-Control header as well as providing for automated filling out of the EXPIRES header for HTTP/1.0 clients.
Paste-1.7.5.1/docs/_build/modules/httpserver.html0000664000175000017500000004722311443716130020307 0ustar ianbianb paste.httpserver – HTTP server — Paste v1.7.5 documentation

paste.httpserver – HTTP server¶

WSGI HTTP Server

This is a minimalistic WSGI server using Python’s built-in BaseHTTPServer; if pyOpenSSL is installed, it also provides SSL capabilities.

Module Contents¶

paste.httpserver.serve(application, host=None, port=None, handler=None, ssl_pem=None, ssl_context=None, server_version=None, protocol_version=None, start_loop=True, daemon_threads=None, socket_timeout=None, use_threadpool=None, threadpool_workers=10, threadpool_options=None, request_queue_size=5)¶

Serves your application over HTTP(S) via WSGI interface

host

This is the ipaddress to bind to (or a hostname if your nameserver is properly configured). This defaults to 127.0.0.1, which is not a public interface.

port

The port to run on, defaults to 8080 for HTTP, or 4443 for HTTPS. This can be a string or an integer value.

handler

This is the HTTP request handler to use, it defaults to WSGIHandler in this module.

ssl_pem

This an optional SSL certificate file (via OpenSSL). You can supply * and a development-only certificate will be created for you, or you can generate a self-signed test PEM certificate file as follows:

$ openssl genrsa 1024 > host.key
$ chmod 400 host.key
$ openssl req -new -x509 -nodes -sha1 -days 365  \
              -key host.key > host.cert
$ cat host.cert host.key > host.pem
$ chmod 400 host.pem

ssl_context

This an optional SSL context object for the server. A SSL context will be automatically constructed for you if you supply ssl_pem. Supply this to use a context of your own construction.

server_version

The version of the server as reported in HTTP response line. This defaults to something like “PasteWSGIServer/0.5”. Many servers hide their code-base identity with a name like ‘Amnesiac/1.0’

protocol_version

This sets the protocol used by the server, by default HTTP/1.0. There is some support for HTTP/1.1, which defaults to nicer keep-alive connections. This server supports 100 Continue, but does not yet support HTTP/1.1 Chunked Encoding. Hence, if you use HTTP/1.1, you’re somewhat in error since chunked coding is a mandatory requirement of a HTTP/1.1 server. If you specify HTTP/1.1, every response must have a Content-Length and you must be careful not to read past the end of the socket.

start_loop

This specifies if the server loop (aka server.serve_forever()) should be called; it defaults to True.

daemon_threads

This flag specifies if when your webserver terminates all in-progress client connections should be droppped. It defaults to False. You might want to set this to True if you are using HTTP/1.1 and don’t set a socket_timeout.

socket_timeout

This specifies the maximum amount of time that a connection to a given client will be kept open. At this time, it is a rude disconnect, but at a later time it might follow the RFC a bit more closely.

use_threadpool

Server requests from a pool of worker threads (threadpool_workers) rather than creating a new thread for each request. This can substantially reduce latency since there is a high cost associated with thread creation.

threadpool_workers

Number of worker threads to create when use_threadpool is true. This can be a string or an integer value.

threadpool_options

A dictionary of options to be used when instantiating the threadpool. See paste.httpserver.ThreadPool for specific options (threadpool_workers is a specific option that can also go here).

request_queue_size

The ‘backlog’ argument to socket.listen(); specifies the maximum number of queued connections.
paste.httpserver.server_runner(wsgi_app, global_conf, **kwargs)¶

Serves your application over HTTP(S) via WSGI interface

host

This is the ipaddress to bind to (or a hostname if your nameserver is properly configured). This defaults to 127.0.0.1, which is not a public interface.

port

The port to run on, defaults to 8080 for HTTP, or 4443 for HTTPS. This can be a string or an integer value.

handler

This is the HTTP request handler to use, it defaults to WSGIHandler in this module.

ssl_pem

This an optional SSL certificate file (via OpenSSL). You can supply * and a development-only certificate will be created for you, or you can generate a self-signed test PEM certificate file as follows:

$ openssl genrsa 1024 > host.key
$ chmod 400 host.key
$ openssl req -new -x509 -nodes -sha1 -days 365  \
              -key host.key > host.cert
$ cat host.cert host.key > host.pem
$ chmod 400 host.pem

ssl_context

This an optional SSL context object for the server. A SSL context will be automatically constructed for you if you supply ssl_pem. Supply this to use a context of your own construction.

server_version

The version of the server as reported in HTTP response line. This defaults to something like “PasteWSGIServer/0.5”. Many servers hide their code-base identity with a name like ‘Amnesiac/1.0’

protocol_version

This sets the protocol used by the server, by default HTTP/1.0. There is some support for HTTP/1.1, which defaults to nicer keep-alive connections. This server supports 100 Continue, but does not yet support HTTP/1.1 Chunked Encoding. Hence, if you use HTTP/1.1, you’re somewhat in error since chunked coding is a mandatory requirement of a HTTP/1.1 server. If you specify HTTP/1.1, every response must have a Content-Length and you must be careful not to read past the end of the socket.

start_loop

This specifies if the server loop (aka server.serve_forever()) should be called; it defaults to True.

daemon_threads

This flag specifies if when your webserver terminates all in-progress client connections should be droppped. It defaults to False. You might want to set this to True if you are using HTTP/1.1 and don’t set a socket_timeout.

socket_timeout

This specifies the maximum amount of time that a connection to a given client will be kept open. At this time, it is a rude disconnect, but at a later time it might follow the RFC a bit more closely.

use_threadpool

Server requests from a pool of worker threads (threadpool_workers) rather than creating a new thread for each request. This can substantially reduce latency since there is a high cost associated with thread creation.

threadpool_workers

Number of worker threads to create when use_threadpool is true. This can be a string or an integer value.

threadpool_options

A dictionary of options to be used when instantiating the threadpool. See paste.httpserver.ThreadPool for specific options (threadpool_workers is a specific option that can also go here).

request_queue_size

The ‘backlog’ argument to socket.listen(); specifies the maximum number of queued connections.

You can also set these threadpool options:

threadpool_max_requests:

The maximum number of requests a worker thread will process before dying (and replacing itself with a new worker thread). Default 100.

threadpool_hung_thread_limit:

The number of seconds a thread can work on a task before it is considered hung (stuck). Default 30 seconds.

threadpool_kill_thread_limit:

The number of seconds a thread can work before you should kill it (assuming it will never finish). Default 600 seconds (10 minutes).

threadpool_dying_limit:

The length of time after killing a thread that it should actually disappear. If it lives longer than this, it is considered a “zombie”. Note that even in easy situations killing a thread can be very slow. Default 300 seconds (5 minutes).

threadpool_spawn_if_under:

If there are no idle threads and a request comes in, and there are less than this number of busy threads, then add workers to the pool. Busy threads are threads that have taken less than threadpool_hung_thread_limit seconds so far. So if you get lots of requests but they complete in a reasonable amount of time, the requests will simply queue up (adding more threads probably wouldn’t speed them up). But if you have lots of hung threads and one more request comes in, this will add workers to handle it. Default 5.

threadpool_max_zombie_threads_before_die:

If there are more zombies than this, just kill the process. This is only good if you have a monitor that will automatically restart the server. This can clean up the mess. Default 0 (disabled).

threadpool_hung_check_period`:

Every X requests, check for hung threads that need to be killed, or for zombie threads that should cause a restart. Default 100 requests.

threadpool_logger:

Logging messages will go the logger named here.

threadpool_error_email (or global error_email setting):

When threads are killed or the process restarted, this email address will be contacted (using an SMTP server on localhost).
Paste-1.7.5.1/docs/_build/modules/transaction.html0000664000175000017500000001616611443716130020430 0ustar ianbianb paste.transaction – DB-API transactions — Paste v1.7.5 documentation

paste.transaction – DB-API transactions¶

Middleware related to transactions and database connections.

At this time it is very basic; but will eventually sprout all that two-phase commit goodness that I don’t need.

Note

This is experimental, and will change in the future.

Module Contents¶

class paste.transaction.TransactionManagerMiddleware(application)¶
class paste.transaction.ConnectionFactory(module, *args, **kwargs)¶

Provides a callable interface for connecting to ADBAPI databases in a WSGI style (using the environment). More advanced connection factories might use the REMOTE_USER and/or other environment variables to make the connection returned depend upon the request.

paste.transaction.BasicTransactionHandler(application, factory)¶

Provides a simple mechanism for starting a transaction based on the factory; and for either committing or rolling back the transaction depending on the result. It checks for the response’s current status code either through the latest call to start_response; or through a HTTPException’s code. If it is a 100, 200, or 300; the transaction is committed; otherwise it is rolled back.

Paste-1.7.5.1/docs/_build/modules/debug.watchthreads.html0000664000175000017500000001627711443716127021662 0ustar ianbianb paste.debug.watchthreads – watch thread workers in paste.httpserver — Paste v1.7.5 documentation

paste.debug.watchthreads – watch thread workers in paste.httpserver¶

Watches the key paste.httpserver.thread_pool to see how many threads there are and report on any wedged threads.

Module Contents¶

class paste.debug.watchthreads.WatchThreads(allow_kill=False)¶

Application that watches the threads in paste.httpserver, showing the length each thread has been working on a request.

If allow_kill is true, then you can kill errant threads through this application.

This application can expose private information (specifically in the environment, like cookies), so it should be protected.

paste.debug.watchthreads.make_watch_threads(global_conf, allow_kill=False)¶

Application that watches the threads in paste.httpserver, showing the length each thread has been working on a request.

If allow_kill is true, then you can kill errant threads through this application.

This application can expose private information (specifically in the environment, like cookies), so it should be protected.

paste.debug.watchthreads.make_bad_app(global_conf, pause=0)¶
Paste-1.7.5.1/docs/_build/modules/httpexceptions.html0000664000175000017500000007411511443716127021170 0ustar ianbianb paste.httpexceptions – Easily product HTTP errors — Paste v1.7.5 documentation

paste.httpexceptions – Easily product HTTP errors¶

HTTP Exception Middleware

This module processes Python exceptions that relate to HTTP exceptions by defining a set of exceptions, all subclasses of HTTPException, and a request handler (middleware) that catches these exceptions and turns them into proper responses.

This module defines exceptions according to RFC 2068 [1] : codes with 100-300 are not really errors; 400’s are client errors, and 500’s are server errors. According to the WSGI specification [2] , the application can call start_response more then once only under two conditions: (a) the response has not yet been sent, or (b) if the second and subsequent invocations of start_response have a valid exc_info argument obtained from sys.exc_info(). The WSGI specification then requires the server or gateway to handle the case where content has been sent and then an exception was encountered.

Exceptions in the 5xx range and those raised after start_response has been called are treated as serious errors and the exc_info is filled-in with information needed for a lower level module to generate a stack trace and log information.

Exception
HTTPException
HTTPRedirection
  • 300 - HTTPMultipleChoices
  • 301 - HTTPMovedPermanently
  • 302 - HTTPFound
  • 303 - HTTPSeeOther
  • 304 - HTTPNotModified
  • 305 - HTTPUseProxy
  • 306 - Unused (not implemented, obviously)
  • 307 - HTTPTemporaryRedirect
HTTPError
HTTPClientError
  • 400 - HTTPBadRequest
  • 401 - HTTPUnauthorized
  • 402 - HTTPPaymentRequired
  • 403 - HTTPForbidden
  • 404 - HTTPNotFound
  • 405 - HTTPMethodNotAllowed
  • 406 - HTTPNotAcceptable
  • 407 - HTTPProxyAuthenticationRequired
  • 408 - HTTPRequestTimeout
  • 409 - HTTPConfict
  • 410 - HTTPGone
  • 411 - HTTPLengthRequired
  • 412 - HTTPPreconditionFailed
  • 413 - HTTPRequestEntityTooLarge
  • 414 - HTTPRequestURITooLong
  • 415 - HTTPUnsupportedMediaType
  • 416 - HTTPRequestRangeNotSatisfiable
  • 417 - HTTPExpectationFailed
HTTPServerError
  • 500 - HTTPInternalServerError
  • 501 - HTTPNotImplemented
  • 502 - HTTPBadGateway
  • 503 - HTTPServiceUnavailable
  • 504 - HTTPGatewayTimeout
  • 505 - HTTPVersionNotSupported

References:

[1]http://www.python.org/peps/pep-0333.html#error-handling
[2]http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5

Module Contents¶

class paste.httpexceptions.HTTPExceptionHandler(application, warning_level=None)¶

catches exceptions and turns them into proper HTTP responses

This middleware catches any exceptions (which are subclasses of HTTPException) and turns them into proper HTTP responses. Note if the headers have already been sent, the stack trace is always maintained as this indicates a programming error.

Note that you must raise the exception before returning the app_iter, and you cannot use this with generator apps that don’t raise an exception until after their app_iter is iterated over.

paste.httpexceptions.make_middleware(app, global_conf=None, warning_level=None)¶

httpexceptions middleware; this catches any paste.httpexceptions.HTTPException exceptions (exceptions like HTTPNotFound, HTTPMovedPermanently, etc) and turns them into proper HTTP responses.

warning_level can be an integer corresponding to an HTTP code. Any code over that value will be passed ‘up’ the chain, potentially reported on by another piece of middleware.

Exceptions¶

exception paste.httpexceptions.HTTPException(detail=None, headers=None, comment=None)¶

the HTTP exception base class

This encapsulates an HTTP response that interrupts normal application flow; but one which is not necessarly an error condition. For example, codes in the 300’s are exceptions in that they interrupt normal processing; however, they are not considered errors.

This class is complicated by 4 factors:

  1. The content given to the exception may either be plain-text or as html-text.
  2. The template may want to have string-substitutions taken from the current environ or values from incoming headers. This is especially troublesome due to case sensitivity.
  3. The final output may either be text/plain or text/html mime-type as requested by the client application.
  4. Each exception has a default explanation, but those who raise exceptions may want to provide additional detail.

Attributes:

code
the HTTP status code for the exception
title
remainder of the status line (stuff after the code)
explanation
a plain-text explanation of the error message that is not subject to environment or header substitutions; it is accessible in the template via %(explanation)s
detail
a plain-text message customization that is not subject to environment or header substitutions; accessible in the template via %(detail)s
template
a content fragment (in HTML) used for environment and header substitution; the default template includes both the explanation and further detail provided in the message
required_headers
a sequence of headers which are required for proper construction of the exception

Parameters:

detail
a plain-text override of the default detail
headers
a list of (k,v) header pairs
comment
a plain-text additional information which is usually stripped/hidden for end-users

To override the template (which is HTML content) or the plain-text explanation, one must subclass the given exception; or customize it after it has been created. This particular breakdown of a message into explanation, detail and template allows both the creation of plain-text and html messages for various clients as well as error-free substitution of environment variables and headers.

exception paste.httpexceptions.HTTPError(detail=None, headers=None, comment=None)¶

base class for status codes in the 400’s and 500’s

This is an exception which indicates that an error has occurred, and that any work in progress should not be committed. These are typically results in the 400’s and 500’s.

exception paste.httpexceptions.HTTPRedirection(detail=None, headers=None, comment=None)¶

base class for 300’s status code (redirections)

This is an abstract base class for 3xx redirection. It indicates that further action needs to be taken by the user agent in order to fulfill the request. It does not necessarly signal an error condition.

exception paste.httpexceptions.HTTPMultipleChoices(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPMovedPermanently(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPFound(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPNotModified(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPUseProxy(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPTemporaryRedirect(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPClientError(detail=None, headers=None, comment=None)¶

base class for the 400’s, where the client is in-error

This is an error condition in which the client is presumed to be in-error. This is an expected problem, and thus is not considered a bug. A server-side traceback is not warranted. Unless specialized, this is a ‘400 Bad Request’

exception paste.httpexceptions.HTTPBadRequest(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPUnauthorized(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPPaymentRequired(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPForbidden(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPNotFound(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPMethodNotAllowed(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPNotAcceptable(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPProxyAuthenticationRequired(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPRequestTimeout(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPConflict(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPGone(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPLengthRequired(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPPreconditionFailed(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPRequestEntityTooLarge(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPRequestURITooLong(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPUnsupportedMediaType(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPRequestRangeNotSatisfiable(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPExpectationFailed(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPServerError(detail=None, headers=None, comment=None)¶

base class for the 500’s, where the server is in-error

This is an error condition in which the server is presumed to be in-error. This is usually unexpected, and thus requires a traceback; ideally, opening a support ticket for the customer. Unless specialized, this is a ‘500 Internal Server Error’

exception paste.httpexceptions.HTTPInternalServerError(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPNotImplemented(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPBadGateway(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPServiceUnavailable(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPGatewayTimeout(detail=None, headers=None, comment=None)¶
exception paste.httpexceptions.HTTPVersionNotSupported(detail=None, headers=None, comment=None)¶
Paste-1.7.5.1/docs/_build/modules/util.multidict.html0000664000175000017500000001607311443716130021052 0ustar ianbianb paste.util.multidict – Dictionaries with multiple values — Paste v1.7.5 documentation

paste.util.multidict – Dictionaries with multiple values¶

Module Contents¶

class paste.util.multidict.MultiDict(*args, **kw)¶

An ordered dictionary that can have multiple values for each key. Adds the methods getall, getone, mixed, and add to the normal dictionary interface.

class paste.util.multidict.UnicodeMultiDict(multi=None, encoding=None, errors='strict', decode_keys=False)¶

A MultiDict wrapper that decodes returned values to unicode on the fly. Decoding is not applied to assigned values.

The key/value contents are assumed to be str/strs or str/FieldStorages (as is returned by the paste.request.parse_ functions).

Can optionally also decode keys when the decode_keys argument is True.

FieldStorage instances are cloned, and the clone’s filename variable is decoded. Its name variable is decoded when decode_keys is enabled.

Paste-1.7.5.1/docs/_build/modules/debug.fsdiff.html0000664000175000017500000002204211443716127020425 0ustar ianbianb paste.debug.fsdiff – Show differences between directories — Paste v1.7.5 documentation

paste.debug.fsdiff – Show differences between directories¶

Module to find differences over time in a filesystem

Basically this takes a snapshot of a directory, then sees what changes were made. The contents of the files are not checked, so you can detect that the content was changed, but not what the old version of the file was.

Module Contents¶

class paste.debug.fsdiff.Diff(before, after)¶

Represents the difference between two snapshots

class paste.debug.fsdiff.Snapshot(base_path, files=None, ignore_wildcards=(), ignore_paths=(), ignore_hidden=True)¶

Represents a snapshot of a set of files. Has a dictionary-like interface, keyed relative to base_path

class paste.debug.fsdiff.File(base_path, path)¶

Represents a single file found as the result of a command.

Has attributes:

path:
The path of the file, relative to the base_path
full:
The full path
stat:
The results of os.stat. Also mtime and size contain the .st_mtime and st_size of the stat.
bytes:
The contents of the file.

You may use the in operator with these objects (tested against the contents of the file), and the .mustcontain() method.

class paste.debug.fsdiff.Dir(base_path, path)¶

Represents a directory created by a command.

paste.debug.fsdiff.report_expected_diffs(diffs, colorize=False)¶

Takes the output of compare_expected, and returns a string description of the differences.

paste.debug.fsdiff.show_diff(actual_content, expected_content)¶
Paste-1.7.5.1/docs/_build/modules/wsgilib.html0000664000175000017500000003667311443716130017550 0ustar ianbianb paste.wsgilib – Miscellaneous WSGI utility functions — Paste v1.7.5 documentation

paste.wsgilib – Miscellaneous WSGI utility functions¶

A module of many disparate routines.

Module Contents¶

paste.wsgilib.add_close(app_iterable, close_func)¶

An an iterable that iterates over app_iter, then calls close_func.

paste.wsgilib.add_start_close(app_iterable, start_func, close_func=None)¶

An an iterable that iterates over app_iter, calls start_func before the first item is returned, then calls close_func at the end.

paste.wsgilib.chained_app_iters(*chained)¶

Chains several app_iters together, also delegating .close() to each of them.

class paste.wsgilib.encode_unicode_app_iter(app_iterable, encoding='ascii', errors='strict')¶

Encodes an app_iterable’s unicode responses as strings

paste.wsgilib.catch_errors(application, environ, start_response, error_callback, ok_callback=None)¶

Runs the application, and returns the application iterator (which should be passed upstream). If an error occurs then error_callback will be called with exc_info as its sole argument. If no errors occur and ok_callback is given, then it will be called with no arguments.

paste.wsgilib.catch_errors_app(application, environ, start_response, error_callback_app, ok_callback=None, catch=<type 'exceptions.Exception'>)¶

Like catch_errors, except error_callback_app should be a callable that will receive three arguments – environ, start_response, and exc_info. It should call start_response (with the exc_info argument!) and return an iterator.

paste.wsgilib.raw_interactive(application, path='', raise_on_wsgi_error=False, **environ)¶

Runs the application in a fake environment.

paste.wsgilib.interactive(*args, **kw)¶

Runs the application interatively, wrapping raw_interactive but returning the output in a formatted way.

paste.wsgilib.dump_environ(environ, start_response)¶

Application which simply dumps the current environment variables out as a plain text response.

paste.wsgilib.intercept_output(environ, application, conditional=None, start_response=None)¶

Runs application with environ and captures status, headers, and body. None are sent on; you must send them on yourself (unlike capture_output)

Typically this is used like:

def dehtmlifying_middleware(application):
    def replacement_app(environ, start_response):
        status, headers, body = intercept_output(
            environ, application)
        start_response(status, headers)
        content_type = header_value(headers, 'content-type')
        if (not content_type
            or not content_type.startswith('text/html')):
            return [body]
        body = re.sub(r'<.*?>', '', body)
        return [body]
    return replacement_app

A third optional argument conditional should be a function that takes conditional(status, headers) and returns False if the request should not be intercepted. In that case start_response will be called and (None, None, app_iter) will be returned. You must detect that in your code and return the app_iter, like:

def dehtmlifying_middleware(application):
    def replacement_app(environ, start_response):
        status, headers, body = intercept_output(
            environ, application,
            lambda s, h: header_value(headers, 'content-type').startswith('text/html'),
            start_response)
        if status is None:
            return body
        start_response(status, headers)
        body = re.sub(r'<.*?>', '', body)
        return [body]
    return replacement_app
Paste-1.7.5.1/docs/_build/modules/debug.wdg_validate.html0000664000175000017500000001756411443716127021633 0ustar ianbianb paste.debug.debugapp – debug app — Paste v1.7.5 documentation

paste.debug.debugapp – debug app¶

Middleware that tests the validity of all generated HTML using the WDG HTML Validator

Module Contents¶

class paste.debug.wdg_validate.WDGValidateMiddleware(app, global_conf=None, wdg_path='validate')¶

Middleware that checks HTML and appends messages about the validity of the HTML. Uses: http://www.htmlhelp.com/tools/validator/ – interacts with the command line client. Use the configuration wdg_path to override the path (default: looks for validate in $PATH).

To install, in your web context’s __init__.py:

def urlparser_wrap(environ, start_response, app):
    return wdg_validate.WDGValidateMiddleware(app)(
        environ, start_response)

Or in your configuration:

middleware.append('paste.wdg_validate.WDGValidateMiddleware')
paste.debug.wdg_validate.make_wdg_validate_middleware(app, global_conf, wdg_path='validate')¶

Wraps the application in the WDG validator from http://www.htmlhelp.com/tools/validator/

Validation errors are appended to the text of each page. You can configure this by giving the path to the validate executable (by default picked up from $PATH)

Paste-1.7.5.1/docs/_build/modules/auth.cookie.html0000664000175000017500000004646411443716127020326 0ustar ianbianb paste.auth.cookie – Cookie-based authentication — Paste v1.7.5 documentation

paste.auth.cookie – Cookie-based authentication¶

Cookie “Saved” Authentication

This authentication middleware saves the current REMOTE_USER, REMOTE_SESSION, and any other environment variables specified in a cookie so that it can be retrieved during the next request without requiring re-authentication. This uses a session cookie on the client side (so it goes away when the user closes their window) and does server-side expiration.

Following is a very simple example where a form is presented asking for a user name (no actual checking), and dummy session identifier (perhaps corresponding to a database session id) is stored in the cookie.

>>> from paste.httpserver import serve
>>> from paste.fileapp import DataApp
>>> from paste.httpexceptions import *
>>> from paste.auth.cookie import AuthCookieHandler
>>> from paste.wsgilib import parse_querystring
>>> def testapp(environ, start_response):
...     user = dict(parse_querystring(environ)).get('user','')
...     if user:
...         environ['REMOTE_USER'] = user
...         environ['REMOTE_SESSION'] = 'a-session-id'
...     if environ.get('REMOTE_USER'):
...         page = '<html><body>Welcome %s (%s)</body></html>'
...         page %= (environ['REMOTE_USER'], environ['REMOTE_SESSION'])
...     else:
...         page = ('<html><body><form><input name="user" />'
...                 '<input type="submit" /></form></body></html>')
...     return DataApp(page, content_type="text/html")(
...                    environ, start_response)
>>> serve(AuthCookieHandler(testapp))
serving on...

Module Contents¶

class paste.auth.cookie.AuthCookieSigner(secret=None, timeout=None, maxlen=None)¶

save/restore environ entries via digially signed cookie

This class converts content into a timed and digitally signed cookie, as well as having the facility to reverse this procedure. If the cookie, after the content is encoded and signed exceeds the maximum length (4096), then CookieTooLarge exception is raised.

The timeout of the cookie is handled on the server side for a few reasons. First, if a ‘Expires’ directive is added to a cookie, then the cookie becomes persistent (lasting even after the browser window has closed). Second, the user’s clock may be wrong (perhaps intentionally). The timeout is specified in minutes; and expiration date returned is rounded to one second.

Constructor Arguments:

secret

This is a secret key if you want to syncronize your keys so that the cookie will be good across a cluster of computers. It is recommended via the HMAC specification (RFC 2104) that the secret key be 64 bytes since this is the block size of the hashing. If you do not provide a secret key, a random one is generated each time you create the handler; this should be sufficient for most cases.

timeout

This is the time (in minutes) from which the cookie is set to expire. Note that on each request a new (replacement) cookie is sent, hence this is effectively a session timeout parameter for your entire cluster. If you do not provide a timeout, it is set at 30 minutes.

maxlen

This is the maximum size of the signed cookie; hence the actual content signed will be somewhat less. If the cookie goes over this size, a CookieTooLarge exception is raised so that unexpected handling of cookies on the client side are avoided. By default this is set at 4k (4096 bytes), which is the standard cookie size limit.
class paste.auth.cookie.AuthCookieHandler(application, cookie_name=None, scanlist=None, signer=None, secret=None, timeout=None, maxlen=None)¶

the actual handler that should be put in your middleware stack

This middleware uses cookies to stash-away a previously authenticated user (and perhaps other variables) so that re-authentication is not needed. This does not implement sessions; and therefore N servers can be syncronized to accept the same saved authentication if they all use the same cookie_name and secret.

By default, this handler scans the environ for the REMOTE_USER and REMOTE_SESSION key; if found, it is stored. It can be configured to scan other environ keys as well – but be careful not to exceed 2-3k (so that the encoded and signed cookie does not exceed 4k). You can ask it to handle other environment variables by doing:

environ['paste.auth.cookie'].append('your.environ.variable')

Constructor Arguments:

application

This is the wrapped application which will have access to the environ['REMOTE_USER'] restored by this middleware.

cookie_name

The name of the cookie used to store this content, by default it is PASTE_AUTH_COOKIE.

scanlist

This is the initial set of environ keys to save/restore to the signed cookie. By default is consists only of REMOTE_USER and REMOTE_SESSION; any tuple or list of environment keys will work. However, be careful, as the total saved size is limited to around 3k.

signer

This is the signer object used to create the actual cookie values, by default, it is AuthCookieSigner and is passed the remaining arguments to this function: secret, timeout, and maxlen.

At this time, each cookie is individually signed. To store more than the 4k of data; it is possible to sub-class this object to provide different environ_name and cookie_name

class paste.auth.cookie.AuthCookieEnviron(handler, scanlist)¶

a list of environment keys to be saved via cookie

An instance of this object, found at environ['paste.auth.cookie'] lists the environ keys that were restored from or will be added to the digially signed cookie. This object can be accessed from an environ variable by using this module’s name.

This middleware uses cookies to stash-away a previously authenticated user (and perhaps other variables) so that re-authentication is not needed. This does not implement sessions; and therefore N servers can be syncronized to accept the same saved authentication if they all use the same cookie_name and secret.

By default, this handler scans the environ for the REMOTE_USER and REMOTE_SESSION key; if found, it is stored. It can be configured to scan other environ keys as well – but be careful not to exceed 2-3k (so that the encoded and signed cookie does not exceed 4k). You can ask it to handle other environment variables by doing:

environ['paste.auth.cookie'].append('your.environ.variable')

Configuration:

cookie_name

The name of the cookie used to store this content, by default it is PASTE_AUTH_COOKIE.

scanlist

This is the initial set of environ keys to save/restore to the signed cookie. By default is consists only of REMOTE_USER and REMOTE_SESSION; any space-separated list of environment keys will work. However, be careful, as the total saved size is limited to around 3k.

secret

The secret that will be used to sign the cookies. If you don’t provide one (and none is set globally) then a random secret will be created. Each time the server is restarted a new secret will then be created and all cookies will become invalid! This can be any string value.

timeout

The time to keep the cookie, expressed in minutes. This is handled server-side, so a new cookie with a new timeout is added to every response.

maxlen

The maximum length of the cookie that is sent (default 4k, which is a typical browser maximum)
Paste-1.7.5.1/docs/_build/modules/exceptions.html0000664000175000017500000007557111443716127020277 0ustar ianbianb paste.exceptions – Catch, display, and notify for exceptions — Paste v1.7.5 documentation

paste.exceptions – Catch, display, and notify for exceptions¶

Error handler middleware

Module Contents¶

class paste.exceptions.errormiddleware.ErrorMiddleware(application, global_conf=None, debug=<NoDefault>, error_email=None, error_log=None, show_exceptions_in_wsgi_errors=<NoDefault>, from_address=None, smtp_server=None, smtp_username=None, smtp_password=None, smtp_use_tls=False, error_subject_prefix=None, error_message=None, xmlhttp_key=None)¶

Error handling middleware

Usage:

error_catching_wsgi_app = ErrorMiddleware(wsgi_app)

Settings:

debug:
If true, then tracebacks will be shown in the browser.
error_email:
an email address (or list of addresses) to send exception reports to
error_log:
a filename to append tracebacks to
show_exceptions_in_wsgi_errors:
If true, then errors will be printed to wsgi.errors (frequently a server error log, or stderr).
from_address, smtp_server, error_subject_prefix, smtp_username, smtp_password, smtp_use_tls:
variables to control the emailed exception reports
error_message:
When debug mode is off, the error message to show to users.
xmlhttp_key:
When this key (default _) is in the request GET variables (not POST!), expect that this is an XMLHttpRequest, and the response should be more minimal; it should not be a complete HTML page.

Environment Configuration:

paste.throw_errors:
If this setting in the request environment is true, then this middleware is disabled. This can be useful in a testing situation where you don’t want errors to be caught and transformed.
paste.expected_exceptions:
When this middleware encounters an exception listed in this environment variable and when the start_response has not yet occurred, the exception will be re-raised instead of being caught. This should generally be set by middleware that may (but probably shouldn’t be) installed above this middleware, and wants to get certain exceptions. Exceptions raised after start_response have been called are always caught since by definition they are no longer expected.
paste.exceptions.errormiddleware.handle_exception(exc_info, error_stream, html=True, debug_mode=False, error_email=None, error_log=None, show_exceptions_in_wsgi_errors=False, error_email_from='errors@localhost', smtp_server='localhost', smtp_username=None, smtp_password=None, smtp_use_tls=False, error_subject_prefix='', error_message=None, simple_html_error=False)¶

For exception handling outside of a web context

Use like:

import sys
from paste.exceptions.errormiddleware import handle_exception
try:
    do stuff
except:
    handle_exception(
        sys.exc_info(), sys.stderr, html=False, ...other config...)

If you want to report, but not fully catch the exception, call raise after handle_exception, which (when given no argument) will reraise the exception.

paste.exceptions.errormiddleware.make_error_middleware(app, global_conf, **kw)¶

Settings:

debug:
If true, then tracebacks will be shown in the browser.
error_email:
an email address (or list of addresses) to send exception reports to
error_log:
a filename to append tracebacks to
show_exceptions_in_wsgi_errors:
If true, then errors will be printed to wsgi.errors (frequently a server error log, or stderr).
from_address, smtp_server, error_subject_prefix, smtp_username, smtp_password, smtp_use_tls:
variables to control the emailed exception reports
error_message:
When debug mode is off, the error message to show to users.
xmlhttp_key:
When this key (default _) is in the request GET variables (not POST!), expect that this is an XMLHttpRequest, and the response should be more minimal; it should not be a complete HTML page.

Environment Configuration:

paste.throw_errors:
If this setting in the request environment is true, then this middleware is disabled. This can be useful in a testing situation where you don’t want errors to be caught and transformed.
paste.expected_exceptions:
When this middleware encounters an exception listed in this environment variable and when the start_response has not yet occurred, the exception will be re-raised instead of being caught. This should generally be set by middleware that may (but probably shouldn’t be) installed above this middleware, and wants to get certain exceptions. Exceptions raised after start_response have been called are always caught since by definition they are no longer expected.

paste.exceptions.collector – Collection information from exceptions¶

An exception collector that finds traceback information plus supplements

Module Contents¶

class paste.exceptions.collector.ExceptionCollector(limit=None)¶

Produces a data structure that can be used by formatters to display exception reports.

Magic variables:

If you define one of these variables in your local scope, you can add information to tracebacks that happen in that context. This allows applications to add all sorts of extra information about the context of the error, including URLs, environmental variables, users, hostnames, etc. These are the variables we look for:

__traceback_supplement__:

You can define this locally or globally (unlike all the other variables, which must be defined locally).

__traceback_supplement__ is a tuple of (factory, arg1, arg2...). When there is an exception, factory(arg1, arg2, ...) is called, and the resulting object is inspected for supplemental information.

__traceback_info__:
This information is added to the traceback, usually fairly literally.
__traceback_hide__:

If set and true, this indicates that the frame should be hidden from abbreviated tracebacks. This way you can hide some of the complexity of the larger framework and let the user focus on their own errors.

By setting it to 'before', all frames before this one will be thrown away. By setting it to 'after' then all frames after this will be thrown away until 'reset' is found. In each case the frame where it is set is included, unless you append '_and_this' to the value (e.g., 'before_and_this').

Note that formatters will ignore this entirely if the frame that contains the error wouldn’t normally be shown according to these rules.

__traceback_reporter__:
This should be a reporter object (see the reporter module), or a list/tuple of reporter objects. All reporters found this way will be given the exception, innermost first.
__traceback_decorator__:
This object (defined in a local or global scope) will get the result of this function (the CollectedException defined below). It may modify this object in place, or return an entirely new object. This gives the object the ability to manipulate the traceback arbitrarily.

The actually interpretation of these values is largely up to the reporters and formatters.

collect_exception(*sys.exc_info()) will return an object with several attributes:

frames:
A list of frames
exception_formatted:
The formatted exception, generally a full traceback
exception_type:
The type of the exception, like ValueError
exception_value:
The string value of the exception, like 'x not in list'
identification_code:
A hash of the exception data meant to identify the general exception, so that it shares this code with other exceptions that derive from the same problem. The code is a hash of all the module names and function names in the traceback, plus exception_type. This should be shown to users so they can refer to the exception later. (@@: should it include a portion that allows identification of the specific instance of the exception as well?)

The list of frames goes innermost first. Each frame has these attributes; some values may be None if they could not be determined.

modname:
the name of the module
filename:
the filename of the module
lineno:
the line of the error
revision:
the contents of __version__ or __revision__
name:
the function name
supplement:
an object created from __traceback_supplement__
supplement_exception:
a simple traceback of any exception __traceback_supplement__ created
traceback_info:
the str() of any __traceback_info__ variable found in the local scope (@@: should it str()-ify it or not?)
traceback_hide:
the value of any __traceback_hide__ variable
traceback_log:
the value of any __traceback_log__ variable

__traceback_supplement__ is thrown away, but a fixed set of attributes are captured; each of these attributes is optional.

object:
the name of the object being visited
source_url:
the original URL requested
line:
the line of source being executed (for interpreters, like ZPT)
column:
the column of source being executed
expression:
the expression being evaluated (also for interpreters)
warnings:
a list of (string) warnings to be displayed
getInfo:
a function/method that takes no arguments, and returns a string describing any extra information
extraData:
a function/method that takes no arguments, and returns a dictionary. The contents of this dictionary will not be displayed in the context of the traceback, but globally for the exception. Results will be grouped by the keys in the dictionaries (which also serve as titles). The keys can also be tuples of (importance, title); in this case the importance should be important (shows up at top), normal (shows up somewhere; unspecified), supplemental (shows up at bottom), or extra (shows up hidden or not at all).

These are used to create an object with attributes of the same names (getInfo becomes a string attribute, not a method). __traceback_supplement__ implementations should be careful to produce values that are relatively static and unlikely to cause further errors in the reporting system – any complex introspection should go in getInfo() and should ultimately return a string.

Note that all attributes are optional, and under certain circumstances may be None or may not exist at all – the collector can only do a best effort, but must avoid creating any exceptions itself.

Formatters may want to use __traceback_hide__ as a hint to hide frames that are part of the ‘framework’ or underlying system. There are a variety of rules about special values for this variables that formatters should be aware of.

TODO:

More attributes in __traceback_supplement__? Maybe an attribute that gives a list of local variables that should also be collected? Also, attributes that would be explicitly meant for the entire request, not just a single frame. Right now some of the fixed set of attributes (e.g., source_url) are meant for this use, but there’s no explicit way for the supplement to indicate new values, e.g., logged-in user, HTTP referrer, environment, etc. Also, the attributes that do exist are Zope/Web oriented.

More information on frames? cgitb, for instance, produces extensive information on local variables. There exists the possibility that getting this information may cause side effects, which can make debugging more difficult; but it also provides fodder for post-mortem debugging. However, the collector is not meant to be configurable, but to capture everything it can and let the formatters be configurable. Maybe this would have to be a configuration value, or maybe it could be indicated by another magical variable (which would probably mean ‘show all local variables below this frame’)

paste.exceptions.collector.collect_exception(t, v, tb, limit=None)¶

Collection an exception from sys.exc_info().

Use like:

try:
    blah blah
except:
    exc_data = collect_exception(*sys.exc_info())

paste.exceptions.formatter – Format exception information¶

Formatters for the exception data that comes from ExceptionCollector.

Module Contents¶

class paste.exceptions.formatter.TextFormatter(show_hidden_frames=False, include_reusable=True, show_extra_data=True, trim_source_paths=())¶
class paste.exceptions.formatter.HTMLFormatter(show_hidden_frames=False, include_reusable=True, show_extra_data=True, trim_source_paths=())¶
paste.exceptions.formatter.format_html(exc_data, include_hidden_frames=False, **ops)¶
paste.exceptions.formatter.format_text(exc_data, **ops)¶

paste.exceptions.reporter – Report exceptions¶

Module Contents¶

class paste.exceptions.reporter.EmailReporter(**conf)¶
class paste.exceptions.reporter.LogReporter(**conf)¶
class paste.exceptions.reporter.FileReporter(**conf)¶
class paste.exceptions.reporter.WSGIAppReporter(exc_data)¶
Paste-1.7.5.1/docs/_build/modules/proxy.html0000664000175000017500000002224611443716130017260 0ustar ianbianb paste.proxy – Proxy WSGI requests to HTTP requests — Paste v1.7.5 documentation

paste.proxy – Proxy WSGI requests to HTTP requests¶

An application that proxies WSGI requests to a remote server.

TODO:

  • Send Via header? It’s not clear to me this is a Via in the style of a typical proxy.

  • Other headers or metadata? I put in X-Forwarded-For, but that’s it.

  • Signed data of non-HTTP keys? This would be for things like REMOTE_USER.

  • Something to indicate what the original URL was? The original host, scheme, and base path.

  • Rewriting Location headers? mod_proxy does this.

  • Rewriting body? (Probably not on this one – that can be done with a different middleware that wraps this middleware)

  • Example:

    use = egg:Paste#proxy
    address = http://server3:8680/exist/rest/db/orgs/sch/config/
    allowed_request_methods = GET

Module Contents¶

class paste.proxy.Proxy(address, allowed_request_methods=(), suppress_http_headers=())¶
paste.proxy.make_proxy(global_conf, address, allowed_request_methods='', suppress_http_headers='')¶

Make a WSGI application that proxies to another address:

address
the full URL ending with a trailing /
allowed_request_methods:
a space seperated list of request methods (e.g., GET POST)
suppress_http_headers
a space seperated list of http headers (lower case, without the leading http_) that should not be passed on to target host
class paste.proxy.TransparentProxy(force_host=None, force_scheme='http')¶

A proxy that sends the request just as it was given, including respecting HTTP_HOST, wsgi.url_scheme, etc.

This is a way of translating WSGI requests directly to real HTTP requests. All information goes in the environment; modify it to modify the way the request is made.

If you specify force_host (and optionally force_scheme) then HTTP_HOST won’t be used to determine where to connect to; instead a specific host will be connected to, but the Host header in the request will remain intact.

paste.proxy.make_transparent_proxy(global_conf, force_host=None, force_scheme='http')¶

Create a proxy that connects to a specific host, but does absolutely no other filtering, including the Host header.

Paste-1.7.5.1/docs/_build/modules/reloader.html0000664000175000017500000002004611443716130017670 0ustar ianbianb paste.reloader – Monitor for file changes to restart the process — Paste v1.7.5 documentation

paste.reloader – Monitor for file changes to restart the process¶

A file monitor and server restarter.

Use this like:

..code-block:: Python

import reloader reloader.install()

Then make sure your server is installed with a shell script like:

err=3
while test "$err" -eq 3 ; do
    python server.py
    err="$?"
done

or is run from this .bat file (if you use Windows):

@echo off
:repeat
    python server.py
if %errorlevel% == 3 goto repeat

or run a monitoring process in Python (paster serve --reload does this).

Use the watch_file(filename) function to cause a reload/restart for other other non-Python files (e.g., configuration files). If you have a dynamic set of files that grows over time you can use something like:

def watch_config_files():
    return CONFIG_FILE_CACHE.keys()
paste.reloader.add_file_callback(watch_config_files)

Then every time the reloader polls files it will call watch_config_files and check all the filenames it returns.

Module Contents¶

paste.reloader.install(poll_interval=1)¶

Install the reloading monitor.

On some platforms server threads may not terminate when the main thread does, causing ports to remain open/locked. The raise_keyboard_interrupt option creates a unignorable signal which causes the whole application to shut-down (rudely).

class paste.reloader.Monitor(poll_interval)¶
paste.reloader.watch_file(func, obj, type)¶
Paste-1.7.5.1/docs/_build/modules/cgitb_catcher.html0000664000175000017500000001532711443716127020670 0ustar ianbianb paste.cgitb_catcher – catch exceptions using cgitb — Paste v1.7.5 documentation

paste.cgitb_catcher – catch exceptions using cgitb¶

WSGI middleware

Captures any exceptions and prints a pretty report. See the cgitb documentation for more.

Module Contents¶

class paste.cgitb_catcher.CgitbMiddleware(app, global_conf=None, display=<class 'paste.cgitb_catcher.NoDefault'>, logdir=None, context=5, format='html')¶
paste.cgitb_catcher.make_cgitb_middleware(app, global_conf, display=<class 'paste.cgitb_catcher.NoDefault'>, logdir=None, context=5, format='html')¶

Wraps the application in the cgitb (standard library) error catcher.

display:
If true (or debug is set in the global configuration) then the traceback will be displayed in the browser
logdir:
Writes logs of all errors in that directory
context:
Number of lines of context to show around each line of source code
Paste-1.7.5.1/docs/_build/modules/auth.basic.html0000664000175000017500000002402611443716127020124 0ustar ianbianb paste.auth.basic – Basic HTTP authentication — Paste v1.7.5 documentation

paste.auth.basic – Basic HTTP authentication¶

Basic HTTP/1.0 Authentication

This module implements Basic authentication as described in HTTP/1.0 specification [1] . Do not use this module unless you are using SSL or need to work with very out-dated clients, instead use digest authentication.

>>> from paste.wsgilib import dump_environ
>>> from paste.httpserver import serve
>>> # from paste.auth.basic import AuthBasicHandler
>>> realm = 'Test Realm'
>>> def authfunc(environ, username, password):
...     return username == password
>>> serve(AuthBasicHandler(dump_environ, realm, authfunc))
serving on...
[1]http://www.w3.org/Protocols/HTTP/1.0/draft-ietf-http-spec.html#BasicAA

Module Contents¶

class paste.auth.basic.AuthBasicAuthenticator(realm, authfunc)¶

implements Basic authentication details

class paste.auth.basic.AuthBasicHandler(application, realm, authfunc)¶

HTTP/1.0 Basic authentication middleware

Parameters:

application

The application object is called only upon successful authentication, and can assume environ['REMOTE_USER'] is set. If the REMOTE_USER is already set, this middleware is simply pass-through.

realm

This is a identifier for the authority that is requesting authorization. It is shown to the user and should be unique within the domain it is being used.

authfunc

This is a mandatory user-defined function which takes a environ, username and password for its first three arguments. It should return True if the user is authenticated.
paste.auth.basic.make_basic(app, global_conf, realm, authfunc, **kw)¶

Grant access via basic authentication

Config looks like this:

[filter:grant]
use = egg:Paste#auth_basic
realm=myrealm
authfunc=somepackage.somemodule:somefunction
Paste-1.7.5.1/docs/_build/modules/response.html0000664000175000017500000002167611443716130017743 0ustar ianbianb paste.response – Utility functions for producing responses — Paste v1.7.5 documentation

paste.response – Utility functions for producing responses¶

Routines to generate WSGI responses

Module Contents¶

class paste.response.HeaderDict¶

This represents response headers. It handles the headers as a dictionary, with case-insensitive keys.

Also there is an .add(key, value) method, which sets the key, or adds the value to the current value (turning it into a list if necessary).

For passing to WSGI there is a .headeritems() method which is like .items() but unpacks value that are lists. It also handles encoding – all headers are encoded in ASCII (if they are unicode).

@@: Should that encoding be ISO-8859-1 or UTF-8? I’m not sure what the spec says.

paste.response.has_header(headers, name)¶

Is header named name present in headers?

paste.response.header_value(headers, name)¶

Returns the header’s value, or None if no such header. If a header appears more than once, all the values of the headers are joined with ‘,’. Note that this is consistent /w RFC 2616 section 4.2 which states:

It MUST be possible to combine the multiple header fields into one “field-name: field-value” pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma.

However, note that the original netscape usage of ‘Set-Cookie’, especially in MSIE which contains an ‘expires’ date will is not compatible with this particular concatination method.

paste.response.remove_header(headers, name)¶

Removes the named header from the list of headers. Returns the value of that header, or None if no header found. If multiple headers are found, only the last one is returned.

paste.response.replace_header(headers, name, value)¶

Updates the headers replacing the first occurance of the given name with the value provided; asserting that no further occurances happen. Note that this is _not_ the same as remove_header and then append, as two distinct operations (del followed by an append) are not atomic in a threaded environment. Returns the previous header value for the provided name, if any. Clearly one should not use this function with set-cookie or other names that may have more than one occurance in the headers.

Paste-1.7.5.1/docs/_build/modules/auth.multi.html0000664000175000017500000002400611443716127020173 0ustar ianbianb paste.auth.multi – Authentication via one of multiple methods — Paste v1.7.5 documentation

paste.auth.multi – Authentication via one of multiple methods¶

Authentication via Multiple Methods

In some environments, the choice of authentication method to be used depends upon the environment and is not “fixed”. This middleware allows N authentication methods to be registered along with a goodness function which determines which method should be used. The following example demonstrates how to use both form and digest authentication in a server stack; by default it uses form-based authentication unless *authmeth=digest is specified as a query argument.

>>> from paste.auth import form, cookie, digest, multi
>>> from paste.wsgilib import dump_environ
>>> from paste.httpserver import serve
>>>
>>> multi = multi.MultiHandler(dump_environ)
>>> def authfunc(environ, realm, user):
...     return digest.digest_password(realm, user, user)
>>> multi.add_method('digest', digest.middleware, "Test Realm", authfunc)
>>> multi.set_query_argument('digest')
>>>
>>> def authfunc(environ, username, password):
...     return username == password
>>> multi.add_method('form', form.middleware, authfunc)
>>> multi.set_default('form')
>>> serve(cookie.middleware(multi))
serving on...

Module Contents¶

class paste.auth.multi.MultiHandler(application)¶

Multiple Authentication Handler

This middleware provides two othogonal facilities:

  • a manner to register any number of authentication middlewares
  • a mechanism to register predicates which cause one of the registered middlewares to be used depending upon the request

If none of the predicates returns True, then the application is invoked directly without middleware

Paste-1.7.5.1/docs/_build/modules/auth.auth_tkt.html0000664000175000017500000003327211443716126020670 0ustar ianbianb paste.auth.auth_tkt – auth_tkt cookie parsing — Paste v1.7.5 documentation

paste.auth.auth_tkt – auth_tkt cookie parsing¶

Implementation of cookie signing as done in mod_auth_tkt.

mod_auth_tkt is an Apache module that looks for these signed cookies and sets REMOTE_USER, REMOTE_USER_TOKENS (a comma-separated list of groups) and REMOTE_USER_DATA (arbitrary string data).

This module is an alternative to the paste.auth.cookie module; it’s primary benefit is compatibility with mod_auth_tkt, which in turn makes it possible to use the same authentication process with non-Python code run under Apache.

Module Contents¶

class paste.auth.auth_tkt.AuthTKTMiddleware(app, secret, cookie_name='auth_tkt', secure=False, include_ip=True, logout_path=None, httponly=False, no_domain_cookie=True, current_domain_cookie=True, wildcard_cookie=True)¶

Middleware that checks for signed cookies that match what mod_auth_tkt looks for (if you have mod_auth_tkt installed, you don’t need this middleware, since Apache will set the environmental variables for you).

Arguments:

secret:
A secret that should be shared by any instances of this application. If this app is served from more than one machine, they should all have the same secret.
cookie_name:
The name of the cookie to read and write from. Default auth_tkt.
secure:
If the cookie should be set as ‘secure’ (only sent over SSL) and if the login must be over SSL. (Defaults to False)
httponly:
If the cookie should be marked as HttpOnly, which means that it’s not accessible to JavaScript. (Defaults to False)
include_ip:
If the cookie should include the user’s IP address. If so, then if they change IPs their cookie will be invalid.
logout_path:
The path under this middleware that should signify a logout. The page will be shown as usual, but the user will also be logged out when they visit this page.

If used with mod_auth_tkt, then these settings (except logout_path) should match the analogous Apache configuration settings.

This also adds two functions to the request:

environ['paste.auth_tkt.set_user'](userid, tokens='', user_data='')

This sets a cookie that logs the user in. tokens is a string (comma-separated groups) or a list of strings. user_data is a string for your own use.

environ['paste.auth_tkt.logout_user']()

Logs out the user.
paste.auth.auth_tkt.make_auth_tkt_middleware(app, global_conf, secret=None, cookie_name='auth_tkt', secure=False, include_ip=True, logout_path=None)¶

Creates the AuthTKTMiddleware.

secret is requird, but can be set globally or locally.

class paste.auth.auth_tkt.AuthTicket(secret, userid, ip, tokens=(), user_data='', time=None, cookie_name='auth_tkt', secure=False)¶

This class represents an authentication token. You must pass in the shared secret, the userid, and the IP address. Optionally you can include tokens (a list of strings, representing role names), ‘user_data’, which is arbitrary data available for your own use in later scripts. Lastly, you can override the cookie name and timestamp.

Once you provide all the arguments, use .cookie_value() to generate the appropriate authentication ticket. .cookie() generates a Cookie object, the str() of which is the complete cookie header to be sent.

CGI usage:

token = auth_tkt.AuthTick('sharedsecret', 'username',
    os.environ['REMOTE_ADDR'], tokens=['admin'])
print 'Status: 200 OK'
print 'Content-type: text/html'
print token.cookie()
print
... redirect HTML ...

Webware usage:

token = auth_tkt.AuthTick('sharedsecret', 'username',
    self.request().environ()['REMOTE_ADDR'], tokens=['admin'])
self.response().setCookie('auth_tkt', token.cookie_value())

Be careful not to do an HTTP redirect after login; use meta refresh or Javascript – some browsers have bugs where cookies aren’t saved when set on a redirect.

exception paste.auth.auth_tkt.BadTicket(msg, expected=None)¶

Exception raised when a ticket can’t be parsed. If we get far enough to determine what the expected digest should have been, expected is set. This should not be shown by default, but can be useful for debugging.

Paste-1.7.5.1/docs/_build/modules/registry.html0000664000175000017500000004115011443716130017742 0ustar ianbianb paste.registry – Manage thread-local request-specific objects — Paste v1.7.5 documentation

paste.registry – Manage thread-local request-specific objects¶

Registry for handling request-local module globals sanely

Dealing with module globals in a thread-safe way is good if your application is the sole responder in a thread, however that approach fails to properly account for various scenarios that occur with WSGI applications and middleware.

What is actually needed in the case where a module global is desired that is always set properly depending on the current request, is a stacked thread-local object. Such an object is popped or pushed during the request cycle so that it properly represents the object that should be active for the current request.

To make it easy to deal with such variables, this module provides a special StackedObjectProxy class which you can instantiate and attach to your module where you’d like others to access it. The object you’d like this to actually “be” during the request is then registered with the RegistryManager middleware, which ensures that for the scope of the current WSGI application everything will work properly.

Example:

#yourpackage/__init__.py

from paste.registry import RegistryManager, StackedObjectProxy
myglobal = StackedObjectProxy()

#wsgi app stack
app = RegistryManager(yourapp)

#inside your wsgi app
class yourapp(object):
    def __call__(self, environ, start_response):
        obj = someobject  # The request-local object you want to access
                          # via yourpackage.myglobal
        if environ.has_key('paste.registry'):
            environ['paste.registry'].register(myglobal, obj)

You will then be able to import yourpackage anywhere in your WSGI app or in the calling stack below it and be assured that it is using the object you registered with Registry.

RegistryManager can be in the WSGI stack multiple times, each time it appears it registers a new request context.

Performance¶

The overhead of the proxy object is very minimal, however if you are using proxy objects extensively (Thousands of accesses per request or more), there are some ways to avoid them. A proxy object runs approximately 3-20x slower than direct access to the object, this is rarely your performance bottleneck when developing web applications.

Should you be developing a system which may be accessing the proxy object thousands of times per request, the performance of the proxy will start to become more noticeable. In that circumstance, the problem can be avoided by getting at the actual object via the proxy with the _current_obj function:

#sessions.py
Session = StackedObjectProxy()
# ... initialization code, etc.

# somemodule.py
import sessions

def somefunc():
    session = sessions.Session._current_obj()
    # ... tons of session access

This way the proxy is used only once to retrieve the object for the current context and the overhead is minimized while still making it easy to access the underlying object. The _current_obj function is preceded by an underscore to more likely avoid clashing with the contained object’s attributes.

NOTE: This is highly unlikely to be an issue in the vast majority of cases, and requires incredibly large amounts of proxy object access before one should consider the proxy object to be causing slow-downs. This section is provided solely in the extremely rare case that it is an issue so that a quick way to work around it is documented.

Module Contents¶

class paste.registry.StackedObjectProxy(default=<class 'paste.registry.NoDefault'>, name='Default')¶

Track an object instance internally using a stack

The StackedObjectProxy proxies access to an object internally using a stacked thread-local. This makes it safe for complex WSGI environments where access to the object may be desired in multiple places without having to pass the actual object around.

New objects are added to the top of the stack with _push_object while objects can be removed with _pop_object.

class paste.registry.Registry¶

Track objects and stacked object proxies for removal

The Registry object is instantiated a single time for the request no matter how many times the RegistryManager is used in a WSGI stack. Each RegistryManager must call prepare before continuing the call to start a new context for object registering.

Each context is tracked with a dict inside a list. The last list element is the currently executing context. Each context dict is keyed by the id of the StackedObjectProxy instance being proxied, the value is a tuple of the StackedObjectProxy instance and the object being tracked.

class paste.registry.RegistryManager(application, streaming=False)¶

Creates and maintains a Registry context

RegistryManager creates a new registry context for the registration of StackedObjectProxy instances. Multiple RegistryManager’s can be in a WSGI stack and will manage the context so that the StackedObjectProxies always proxy to the proper object.

The object being registered can be any object sub-class, list, or dict.

Registering objects is done inside a WSGI application under the RegistryManager instance, using the environ['paste.registry'] object which is a Registry instance.

class paste.registry.StackedObjectRestorer¶

Track StackedObjectProxies and their proxied objects for automatic restoration within EvalException’s interactive debugger.

An instance of this class tracks all StackedObjectProxy state in existence when unexpected exceptions are raised by WSGI applications housed by EvalException and RegistryManager. Like EvalException, this information is stored for the life of the process.

When an unexpected exception occurs and EvalException is present in the WSGI stack, save_registry_state is intended to be called to store the Registry state and enable automatic restoration on all currently registered StackedObjectProxies.

With restoration enabled, those StackedObjectProxies’ _current_obj (overwritten by _current_obj_restoration) method’s strategy is modified: it will return its appropriate proxied object from the restorer when a restoration context is active in the current thread.

The StackedObjectProxies’ _push/pop_object methods strategies are also changed: they no-op when a restoration context is active in the current thread (because the pushing/popping work is all handled by the Registry/restorer).

The request’s Registry objects’ reglists are restored from the restorer when a restoration context begins, enabling the Registry methods to work while their changes are tracked by the restorer.

The overhead of enabling restoration is negligible (another threadlocal access for the changed StackedObjectProxy methods) for normal use outside of a restoration context, but worth mentioning when combined with StackedObjectProxies normal overhead. Once enabled it does not turn off, however:

o Enabling restoration only occurs after an unexpected exception is detected. The server is likely to be restarted shortly after the exception is raised to fix the cause

o StackedObjectRestorer is only enabled when EvalException is enabled (not on a production server) and RegistryManager exists in the middleware stack

paste.registry.make_registry_manager(app, global_conf)¶

Creates and maintains a Registry context

RegistryManager creates a new registry context for the registration of StackedObjectProxy instances. Multiple RegistryManager’s can be in a WSGI stack and will manage the context so that the StackedObjectProxies always proxy to the proper object.

The object being registered can be any object sub-class, list, or dict.

Registering objects is done inside a WSGI application under the RegistryManager instance, using the environ['paste.registry'] object which is a Registry instance.

Paste-1.7.5.1/docs/_build/modules/progress.html0000664000175000017500000003056211443716130017743 0ustar ianbianb paste.progress – Track progress of uploads — Paste v1.7.5 documentation

paste.progress – Track progress of uploads¶

Upload Progress Monitor

This is a WSGI middleware component which monitors the status of files being uploaded. It includes a small query application which will return a list of all files being uploaded by particular session/user.

>>> from paste.httpserver import serve
>>> from paste.urlmap import URLMap
>>> from paste.auth.basic import AuthBasicHandler
>>> from paste.debug.debugapp import SlowConsumer, SimpleApplication
>>> # from paste.progress import *
>>> realm = 'Test Realm'
>>> def authfunc(username, password):
...     return username == password
>>> map = URLMap({})
>>> ups = UploadProgressMonitor(map, threshold=1024)
>>> map['/upload'] = SlowConsumer()
>>> map['/simple'] = SimpleApplication()
>>> map['/report'] = UploadProgressReporter(ups)
>>> serve(AuthBasicHandler(ups, realm, authfunc))
serving on...

Note

This is experimental, and will change in the future.

Module Contents¶

class paste.progress.UploadProgressMonitor(application, threshold=None, timeout=None)¶

monitors and reports on the status of uploads in progress

Parameters:

application

This is the next application in the WSGI stack.

threshold

This is the size in bytes that is needed for the upload to be included in the monitor.

timeout

This is the amount of time (in seconds) that a upload remains in the monitor after it has finished.

Methods:

uploads()

This returns a list of environ dict objects for each upload being currently monitored, or finished but whose time has not yet expired.

For each request environ that is monitored, there are several variables that are stored:

paste.bytes_received

This is the total number of bytes received for the given request; it can be compared with CONTENT_LENGTH to build a percentage complete. This is an integer value.

paste.request_started

This is the time (in seconds) when the request was started as obtained from time.time(). One would want to format this for presentation to the user, if necessary.

paste.request_finished

This is the time (in seconds) when the request was finished, canceled, or otherwise disconnected. This is None while the given upload is still in-progress.
TODO: turn monitor into a queue and purge queue of finished
requests that have passed the timeout period.
class paste.progress.UploadProgressReporter(monitor)¶

reports on the progress of uploads for a given user

This reporter returns a JSON file (for use in AJAX) listing the uploads in progress for the given user. By default, this reporter uses the REMOTE_USER environment to compare between the current request and uploads in-progress. If they match, then a response record is formed.

match()

This member function can be overriden to provide alternative matching criteria. It takes two environments, the first is the current request, the second is a current upload.

report()

This member function takes an environment and builds a dict that will be used to create a JSON mapping for the given upload. By default, this just includes the percent complete and the request url.
Paste-1.7.5.1/docs/_build/modules/urlparser.html0000664000175000017500000003053211443716130020113 0ustar ianbianb paste.urlparser – Handle URL paths and server static files — Paste v1.7.5 documentation

paste.urlparser – Handle URL paths and server static files¶

WSGI applications that parse the URL and dispatch to on-disk resources

Module Contents¶

class paste.urlparser.StaticURLParser(directory, root_directory=None, cache_max_age=None)¶

Like URLParser but only serves static files.

cache_max_age:
integer specifies Cache-Control max_age in seconds
paste.urlparser.make_static(global_conf, document_root, cache_max_age=None)¶

Return a WSGI application that serves a directory (configured with document_root)

cache_max_age - integer specifies CACHE_CONTROL max_age in seconds

class paste.urlparser.PkgResourcesParser(egg_or_spec, resource_name, manager=None, root_resource=None)¶
paste.urlparser.make_pkg_resources(global_conf, egg, resource_name='')¶

A static file parser that loads data from an egg using pkg_resources. Takes a configuration value egg, which is an egg spec, and a base resource_name (default empty string) which is the path in the egg that this starts at.

class paste.urlparser.URLParser(global_conf, directory, base_python_name, index_names=<class 'paste.urlparser.NoDefault'>, hide_extensions=<class 'paste.urlparser.NoDefault'>, ignore_extensions=<class 'paste.urlparser.NoDefault'>, constructors=None, **constructor_conf)¶

WSGI middleware

Application dispatching, based on URL. An instance of URLParser is an application that loads and delegates to other applications. It looks for files in its directory that match the first part of PATH_INFO; these may have an extension, but are not required to have one, in which case the available files are searched to find the appropriate file. If it is ambiguous, a 404 is returned and an error logged.

By default there is a constructor for .py files that loads the module, and looks for an attribute application, which is a ready application object, or an attribute that matches the module name, which is a factory for building applications, and is called with no arguments.

URLParser will also look in __init__.py for special overrides. These overrides are:

urlparser_hook(environ)
This can modify the environment. Its return value is ignored, and it cannot be used to change the response in any way. You can use this, for example, to manipulate SCRIPT_NAME/PATH_INFO (try to keep them consistent with the original URL – but consuming PATH_INFO and moving that to SCRIPT_NAME is ok).
urlparser_wrap(environ, start_response, app):
After URLParser finds the application, it calls this function (if present). If this function doesn’t call app(environ, start_response) then the application won’t be called at all! This can be used to allocate resources (with try:finally:) or otherwise filter the output of the application.
not_found_hook(environ, start_response):
If no file can be found (in this directory) to match the request, then this WSGI application will be called. You can use this to change the URL and pass the request back to URLParser again, or on to some other application. This doesn’t catch all 404 Not Found responses, just missing files.
application(environ, start_response):
This basically overrides URLParser completely, and the given application is used for all requests. urlparser_wrap and urlparser_hook are still called, but the filesystem isn’t searched in any way.
paste.urlparser.make_url_parser(global_conf, directory, base_python_name, index_names=None, hide_extensions=None, ignore_extensions=None, **constructor_conf)¶

Create a URLParser application that looks in directory, which should be the directory for the Python package named in base_python_name. index_names are used when viewing the directory (like 'index' for 'index.html'). hide_extensions are extensions that are not viewable (like '.pyc') and ignore_extensions are viewable but only if an explicit extension is given.

Paste-1.7.5.1/docs/_build/modules/auth.grantip.html0000664000175000017500000001741211443716127020510 0ustar ianbianb paste.auth.grantip – Set user and groups based on IP address — Paste v1.7.5 documentation

paste.auth.grantip – Set user and groups based on IP address¶

Grant roles and logins based on IP address.

Module Contents¶

class paste.auth.grantip.GrantIPMiddleware(app, ip_map, clobber_username=True)¶

On each request, ip_map is checked against REMOTE_ADDR and logins and roles are assigned based on that.

ip_map is a map of {ip_mask: (username, roles)}. Either username or roles may be None. Roles may also be prefixed with -, like '-system' meaning that role should be revoked. '__remove__' for a username will remove the username.

If clobber_username is true (default) then any user specification will override the current value of REMOTE_USER. '__remove__' will always clobber the username.

ip_mask is something that paste.util.ip4:IP4Range can parse. Simple IP addresses, IP/mask, ip<->ip ranges, and hostnames are allowed.

paste.auth.grantip.make_grantip(app, global_conf, clobber_username=False, **kw)¶

Grant roles or usernames based on IP addresses.

Config looks like this:

[filter:grant]
use = egg:Paste#grantip
clobber_username = true
# Give localhost system role (no username):
127.0.0.1 = -:system
# Give everyone in 192.168.0.* editor role:
192.168.0.0/24 = -:editor
# Give one IP the username joe:
192.168.0.7 = joe
# And one IP is should not be logged in:
192.168.0.10 = __remove__:-editor
Paste-1.7.5.1/docs/_build/modules/cgiapp.html0000664000175000017500000001651111443716127017346 0ustar ianbianb paste.cgiapp – run CGI scripts as WSGI applications — Paste v1.7.5 documentation

paste.cgiapp – run CGI scripts as WSGI applications¶

Application that runs a CGI script.

Module Contents¶

class paste.cgiapp.CGIApplication(global_conf, script, path=None, include_os_environ=True, query_string=None)¶

This object acts as a proxy to a CGI application. You pass in the script path (script), an optional path to search for the script (if the name isn’t absolute) (path). If you don’t give a path, then $PATH will be used.

exception paste.cgiapp.CGIError¶

Raised when the CGI script can’t be found or doesn’t act like a proper CGI script.

paste.cgiapp.make_cgi_application(global_conf, script, path=None, include_os_environ=None, query_string=None)¶

Paste Deploy interface for CGIApplication

This object acts as a proxy to a CGI application. You pass in the script path (script), an optional path to search for the script (if the name isn’t absolute) (path). If you don’t give a path, then $PATH will be used.

Paste-1.7.5.1/docs/_build/modules/translogger.html0000664000175000017500000001533411443716130020426 0ustar ianbianb paste.translogger – Log requests — Paste v1.7.5 documentation

paste.translogger – Log requests¶

Middleware for logging requests, using Apache combined log format

Module Contents¶

class paste.translogger.TransLogger(application, logger=None, format=None, logging_level=20, logger_name='wsgi', setup_console_handler=True, set_logger_level=10)¶

This logging middleware will log all requests as they go through. They are, by default, sent to a logger named 'wsgi' at the INFO level.

If setup_console_handler is true, then messages for the named logger will be sent to the console.

paste.translogger.make_filter(app, global_conf, logger_name='wsgi', format=None, logging_level=20, setup_console_handler=True, set_logger_level=10)¶

This logging middleware will log all requests as they go through. They are, by default, sent to a logger named 'wsgi' at the INFO level.

If setup_console_handler is true, then messages for the named logger will be sent to the console.

Paste-1.7.5.1/docs/_build/modules/lint.html0000664000175000017500000002272711443716130017051 0ustar ianbianb paste.lint – Check for the validity of WSGI requests and responses — Paste v1.7.5 documentation

paste.lint – Check for the validity of WSGI requests and responses¶

Middleware to check for obedience to the WSGI specification.

Some of the things this checks:

  • Signature of the application and start_response (including that keyword arguments are not used).
  • Environment checks:
    • Environment is a dictionary (and not a subclass).
    • That all the required keys are in the environment: REQUEST_METHOD, SERVER_NAME, SERVER_PORT, wsgi.version, wsgi.input, wsgi.errors, wsgi.multithread, wsgi.multiprocess, wsgi.run_once
    • That HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH are not in the environment (these headers should appear as CONTENT_LENGTH and CONTENT_TYPE).
    • Warns if QUERY_STRING is missing, as the cgi module acts unpredictably in that case.
    • That CGI-style variables (that don’t contain a .) have (non-unicode) string values
    • That wsgi.version is a tuple
    • That wsgi.url_scheme is ‘http’ or ‘https’ (@@: is this too restrictive?)
    • Warns if the REQUEST_METHOD is not known (@@: probably too restrictive).
    • That SCRIPT_NAME and PATH_INFO are empty or start with /
    • That at least one of SCRIPT_NAME or PATH_INFO are set.
    • That CONTENT_LENGTH is a positive integer.
    • That SCRIPT_NAME is not ‘/’ (it should be ‘’, and PATH_INFO should be ‘/’).
    • That wsgi.input has the methods read, readline, readlines, and __iter__
    • That wsgi.errors has the methods flush, write, writelines
  • The status is a string, contains a space, starts with an integer, and that integer is in range (> 100).
  • That the headers is a list (not a subclass, not another kind of sequence).
  • That the items of the headers are tuples of strings.
  • That there is no ‘status’ header (that is used in CGI, but not in WSGI).
  • That the headers don’t contain newlines or colons, end in _ or -, or contain characters codes below 037.
  • That Content-Type is given if there is content (CGI often has a default content type, but WSGI does not).
  • That no Content-Type is given when there is no content (@@: is this too restrictive?)
  • That the exc_info argument to start_response is a tuple or None.
  • That all calls to the writer are with strings, and no other methods on the writer are accessed.
  • That wsgi.input is used properly:
    • .read() is called with zero or one argument
    • That it returns a string
    • That readline, readlines, and __iter__ return strings
    • That .close() is not called
    • No other methods are provided
  • That wsgi.errors is used properly:
    • .write() and .writelines() is called with a string
    • That .close() is not called, and no other methods are provided.
  • The response iterator:
    • That it is not a string (it should be a list of a single string; a string will work, but perform horribly).
    • That .next() returns a string
    • That the iterator is not iterated over until start_response has been called (that can signal either a server or application error).
    • That .close() is called (doesn’t raise exception, only prints to sys.stderr, because we only know it isn’t called when the object is garbage collected).

Module Contents¶

paste.lint.middleware(application, global_conf=None)¶

When applied between a WSGI server and a WSGI application, this middleware will check for WSGI compliancy on a number of levels. This middleware does not modify the request or response in any way, but will throw an AssertionError if anything seems off (except for a failure to close the application iterator, which will be printed to stderr – there’s no way to throw an exception at that point).

exception paste.lint.WSGIWarning¶

Raised in response to WSGI-spec-related warnings

Paste-1.7.5.1/docs/_build/url-parsing-with-wsgi.html0000664000175000017500000006655611443716131020627 0ustar ianbianb URL Parsing With WSGI And Paste — Paste v1.7.5 documentation

URL Parsing With WSGI And Paste¶

author:Ian Bicking <ianb@colorstudy.com>
revision:$Rev$
date:$LastChangedDate$

Introduction and Audience¶

This document is intended for web framework authors and integrators, and people who want to understand the internal architecture of Paste.

If you have questions about this document, please contact the paste mailing list or try IRC (#pythonpaste on freenode.net). If there’s something that confused you and you want to give feedback, please submit an issue.

URL Parsing¶

Note

Sometimes people use “URL”, and sometimes “URI”. I think URLs are a subset of URIs. But in practice you’ll almost never see URIs that aren’t URLs, and certainly not in Paste. URIs that aren’t URLs are abstract Identifiers, that cannot necessarily be used to Locate the resource. This document is all about locating.

Most generally, URL parsing is about taking a URL and determining what “resource” the URL refers to. “Resource” is a rather vague term, intentionally. It’s really just a metaphor – in reality there aren’t any “resources” in HTTP; there are only requests and responses.

In Paste, everything is about WSGI. But that can seem too fancy. There are four core things involved: the request (personified in the WSGI environment), the response (personified inthe start_response callback and the return iterator), the WSGI application, and the server that calls that application. The application and request are objects, while the server and response are really more like actions than concrete objects.

In this context, URL parsing is about mapping a URL to an application and a request. The request actually gets modified as it moves through different parts of the system. Two dictionary keys in particular relate to URLs – SCRIPT_NAME and PATH_INFO – but any part of the environment can be modified as it is passed through the system.

Dispatching¶

Note

WSGI isn’t object oriented? Well, if you look at it, you’ll notice there’s no objects except built-in types, so it shouldn’t be a surprise. Additionally, the interface and promises of the objects we do see are very minimal. An application doesn’t have any interface except one method – __call__ – and that method does things, it doesn’t give any other information.

Because WSGI is action-oriented, rather than object-oriented, it’s more important what we do. “Finding” an application is probably an intermediate step, but “running” the application is our ultimate goal, and the only real judge of success. An application that isn’t run is useless to us, because it doesn’t have any other useful methods.

So what we’re really doing is dispatching – we’re handing the request and responsibility for the response off to another object (another actor, really). In the process we can actually retain some control – we can capture and transform the response, and we can modify the request – but that’s not what the typical URL resolver will do.

Motivations¶

The most obvious kind of URL parsing is finding a WSGI application.

Typically when a framework first supports WSGI or is integrated into Paste, it is “monolithic” with respect to URLs. That is, you define (in Paste, or maybe in Apache) a “root” URL, and everything under that goes into the framework. What the framework does internally, Paste does not know – it probably finds internal objects to dispatch to, but the framework is opaque to Paste. Not just to Paste, but to any code that isn’t in that framework.

That means that we can’t mix code from multiple frameworks, or as easily share services, or use WSGI middleware that doesn’t apply to the entire framework/application.

An example of someplace we might want to use an “application” that isn’t part of the framework would be uploading large files. It’s possible to keep track of upload progress, and report that back to the user, but no framework typically is capable of this. This is usually because the POST request is completely read and parsed before it invokes any application code.

This is resolvable in WSGI – a WSGI application can provide its own code to read and parse the POST request, and simultaneously report progress (usually in a way that another WSGI application/request can read and report to the user on that progress). This is an example where you want to allow “foreign” applications to be intermingled with framework application code.

Finding Applications¶

OK, enough theory. How does a URL parser work? Well, it is a WSGI application, and a WSGI server, in the typical “WSGI middleware” style. Except that it determines which application it will serve for each request.

Let’s consider Paste’s URLParser (in paste.urlparser). This class takes a directory name as its only required argument, and instances are WSGI applications.

When a request comes in, the parser looks at PATH_INFO to see what’s left to parse. SCRIPT_NAME represents where we are now; it’s the part of the URL that has been parsed.

There’s a couple special cases:

The empty string:

URLParser serves directories. When PATH_INFO is empty, that means we got a request with no trailing /, like say /blog If URLParser serves the blog directory, then this won’t do – the user is requesting the blog page. We have to redirect them to /blog/.

A single /:

So, we got a trailing /. This means we need to serve the “index” page. In URLParser, this is some file named index, though that’s really an implementation detail. You could create an index dynamically (like Apache’s file listings), or whatever.

Otherwise we get a string like /path.... Note that PATH_INFO must start with a /, or it must be empty.

URLParser pulls off the first part of the path. E.g., if PATH_INFO is /blog/edit/285, then the first part is blog. It appends this to SCRIPT_NAME, and strips it off PATH_INFO (which becomes /edit/285).

It then searches for a file that matches “blog”. In URLParser, this means it looks for a filename which matches that name (ignoring the extension). It then uses the type of that file (determined by extension) to create a WSGI application.

One case is that the file is a directory. In that case, the application is another URLParser instance, this time with the new directory.

URLParser actually allows per-extension “plugins” – these are just functions that get a filename, and produce a WSGI application. One of these is make_py – this function imports the module, and looks for special symbols; if it finds a symbol application, it assumes this is a WSGI application that is ready to accept the request. If it finds a symbol that matches the name of the module (e.g., edit), then it assumes that is an application factory, meaning that when you call it with no arguments you get a WSGI application.

Another function takes “unknown” files (files for which no better constructor exists) and creates an application that simply responds with the contents of that file (and the appropriate Content-Type).

In any case, URLParser delegates as soon as it can. It doesn’t parse the entire path – it just finds the next application, which in turn may delegate to yet another application.

Here’s a very simple implementation of URLParser:

class URLParser(object):
    def __init__(self, dir):
        self.dir = dir
    def __call__(self, environ, start_response):
        segment = wsgilib.path_info_pop(environ)
        if segment is None: # No trailing /
            # do a redirect...
        for filename in os.listdir(self.dir):
            if os.path.splitext(filename)[0] == segment:
                return self.serve_application(
                    environ, start_response, filename)
        # do a 404 Not Found
    def serve_application(self, environ, start_response, filename):
        basename, ext = os.path.splitext(filename)
        filename = os.path.join(self.dir, filename)
        if os.path.isdir(filename):
            return URLParser(filename)(environ, start_response)
        elif ext == '.py':
            module = import_module(filename)
            if hasattr(module, 'application'):
                return module.application(environ, start_response)
            elif hasattr(module, basename):
                return getattr(module, basename)(
                    environ, start_response)
        else:
            return wsgilib.send_file(filename)

Modifying The Request¶

Well, URLParser is one kind of parser. But others are possible, and aren’t too hard to write.

Lets imagine a URL like /2004/05/01/edit. It’s likely that /2004/05/01 doesn’t point to anything on file, but is really more of a “variable” that gets passed to edit. So we can pull them off and put them somewhere. This is a good place for a WSGI extension. Lets put them in environ["app.url_date"].

We’ll pass one other applications in – once we get the date (if any) we need to pass the request onto an application that can actually handle it. This “application” might be a URLParser or similar system (that figures out what /edit means).

class GrabDate(object):
    def __init__(self, subapp):
        self.subapp = subapp
    def __call__(self, environ, start_response):
        date_parts = []
        while len(date_parts) < 3:
           first, rest = wsgilib.path_info_split(environ['PATH_INFO'])
           try:
               date_parts.append(int(first))
               wsgilib.path_info_pop(environ)
           except (ValueError, TypeError):
               break
        environ['app.date_parts'] = date_parts
        return self.subapp(environ, start_response)

This is really like traditional “middleware”, in that it sits between the server and just one application.

Assuming you put this class in the myapp.grabdate module, you could install it by adding this to your configuration:

middleware.append('myapp.grabdate.GrabDate')

Object Publishing¶

Besides looking in the filesystem, “object publishing” is another popular way to do URL parsing. This is pretty easy to implement as well – it usually just means use getattr with the popped segments. But we’ll implement a rough approximation of Quixote’s URL parsing:

class ObjectApp(object):
    def __init__(self, obj):
        self.obj = obj
    def __call__(self, environ, start_response):
        next = wsgilib.path_info_pop(environ)
        if next is None:
            # This is the object, lets serve it...
            return self.publish(obj, environ, start_response)
        next = next or '_q_index' # the default index method
        if next in obj._q_export and getattr(obj, next, None):
            return ObjectApp(getattr(obj, next))(
                environ, start_reponse)
        next_obj = obj._q_traverse(next)
        if not next_obj:
            # Do a 404
        return ObjectApp(next_obj)(environ, start_response)

    def publish(self, obj, environ, start_response):
        if callable(obj):
            output = str(obj())
        else:
            output = str(obj)
        start_response('200 OK', [('Content-type', 'text/html')])
        return [output]

The publish object is a little weak, and functions like _q_traverse aren’t passed interesting information about the request, but this is only a rough approximation of the framework. Things to note:

  • The object has standard attributes and methods – _q_exports (attributes that are public to the web) and _q_traverse (a way of overriding the traversal without having an attribute for each possible path segment).
  • The object isn’t rendered until the path is completely consumed (when next is None). This means _q_traverse has to consume extra segments of the path. In this version _q_traverse is only given the next piece of the path; Quixote gives it the entire path (as a list of segments).
  • publish is really a small and lame way to turn a Quixote object into a WSGI application. For any serious framework you’d want to do a better job than what I do here.
  • It would be even better if you used something like Adaptation to convert objects into applications. This would include removing the explicit creation of new ObjectApp instances, which could also be a kind of fall-back adaptation.

Anyway, this example is less complete, but maybe it will get you thinking.

Paste-1.7.5.1/docs/_build/StyleGuide.html0000664000175000017500000002564511443716126016520 0ustar ianbianb Paste Style Guide — Paste v1.7.5 documentation

Paste Style Guide¶

Generally you should follow the recommendations in PEP 8, the Python Style Guide. Some things to take particular note of:

  • No tabs. Not anywhere. Always indent with 4 spaces.

  • I don’t stress too much on line length. But try to break lines up by grouping with parenthesis instead of with backslashes (if you can). Do asserts like:

    assert some_condition(a, b), (
        "Some condition failed, %r isn't right!" % a)
    
  • But if you are having problems with line length, maybe you should just break the expression up into multiple statements.

  • Blank lines between methods, unless they are very small and closely bound to each other.

  • Don’t use the form condition and trueValue or falseValue. Break it out and use a variable.

  • I (Ian Bicking) am very picky about whitespace. There’s one and only one right way to do it. Good examples:

    short = 3
    longerVar = 4
    
    if x == 4:
        do stuff
    
    func(arg1='a', arg2='b')
    func((a + b)*10)

    Bad examples:

    short    =3
    longerVar=4
    
    if x==4: do stuff
    
    func(arg1 = 'a', arg2 = 'b')
    func(a,b)
    func( a, b )
    [ 1, 2, 3 ]

    If the whitespace isn’t right, it’ll annoy me and I’ll feel a need to fix it. Really, this whitespace stuff shouldn’t be that controversial should it? Some particular points that I feel strongly about:

    • No space after a function name (bad: func (arg)).
    • No space after or before a parenthesis (bad: func( arg )).
    • Always one space after a comma (bad: func(a,b)).
  • Use @@ to mark something that is suboptimal, or where you have a concern that it’s not right. Try to also date it and put your username there.

  • Docstrings are good. They should look like:

    class AClass(object):
        """
        doc string...
        """
    

    Don’t use single quotes (‘’‘) – they tend to cause problems in Emacs. Don’t bother trying make the string less vertically compact.

  • Comments go right before the thing they are commenting on.

  • Methods never, ever, ever start with capital letters. Generally only classes are capitalized. But definitely never methods.

  • Use cls to refer to a class. Use meta to refer to a metaclass (which also happens to be a class, but calling a metaclass cls will be confusing).

  • Use isinstance instead of comparing types. E.g.:

    if isinstance(var, str): ...
    # Bad:
    if type(var) is StringType: ...
    
  • Never, ever use two leading underscores. This is annoyingly private. If name clashes are a concern, use explicit name mangling instead (e.g., _MyThing_blahblah). This is essentially the same thing as double-underscore, only it’s transparent where double underscore obscures.

  • Module names should be unique in the package. Subpackages shouldn’t share module names with sibling or parent packages. Sadly this isn’t possible for __init__.py, but it’s otherwise easy enough.

  • Module names should be all lower case, and probably have no underscores (smushedwords).

Python Paste

Previous topic

Python Paste Developer Guide

Next topic

The Paste HTTP Server Thread Pool

This Page

Paste-1.7.5.1/docs/_build/py-modindex.html0000664000175000017500000003507311443716131016667 0ustar ianbianb Python Module Index — Paste v1.7.5 documentation Paste-1.7.5.1/docs/_build/developer-features.html0000664000175000017500000004637111443716126020242 0ustar ianbianb Features — Paste v1.7.5 documentation

Features¶

Testing¶

Server¶

Dispatching¶

  • Chain and cascade WSGI applications (returning the first non-error response) in paste.cascade
  • Dispatch to several WSGI applications based on URL prefixes, in paste.urlmap
  • Allow applications to make subrequests and forward requests internally, in paste.recursive
  • Redirect error pages (e.g., 404 Not Found) to custom error pages, in paste.errordocument.

Web Application¶

  • Easily deal with incoming requests and sending a response in paste.wsgiwrappers
  • Work directly with the WSGI environment in paste.request
  • Run CGI programs as WSGI applications in paste.cgiapp
  • Traverse files and load WSGI applications from .py files (or static files), in paste.urlparser
  • Serve static directories of files, also in paste.urlparser; also serve using the Setuptools pkg_resources resource API.
  • Proxy to other servers, treating external HTTP servers as WSGI applications, in paste.proxy.
  • Serve files (with support for If-Modified-Since, etc) in paste.fileapp

Tools¶

Authentication¶

Debugging Filters¶

  • Catch (optionally email) errors with extended tracebacks (using Zope/ZPT conventions) in paste.exceptions
  • During debugging, show tracebacks with information about each stack frame, including an interactive prompt that runs in the individual stack frames, in paste.evalexception.
  • Catch errors presenting a cgitb-based output, in paste.cgitb_catcher.
  • Profile each request and append profiling information to the HTML, in paste.debug.profile
  • Capture print output and present it in the browser for debugging, in paste.debug.prints
  • Validate all HTML output from applications using the WDG Validator, appending any errors or warnings to the page, in paste.debug.wdg_validator

Other Tools¶

  • A file monitor to allow restarting the server when files have been updated (for automatic restarting when editing code) in paste.reloader
  • A class for generating and traversing URLs, and creating associated HTML code, in paste.url
  • A small templating language (for internal use) in paste.util.template
  • A class to help with loops in templates, in paste.util.looper
  • Import modules and objects given a string, in paste.util.import_string
  • Ordered dictionary that can have multiple values with the same key, in paste.util.multidict
Paste-1.7.5.1/docs/_build/future.html0000664000175000017500000002403511443716126015744 0ustar ianbianb The Future Of Paste — Paste v1.7.5 documentation

The Future Of Paste¶

Introduction¶

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.

The future of these pieces is to split them into independent packages, and refactor the internal Paste dependencies to rely instead on WebOb.

Already Extracted¶

paste.fixture:
WebTest ScriptTest
paste.lint:
wsgiref.validate
paste.exceptions and paste.evalexception:
WebError
paste.util.template:
Tempita

To Be Separated¶

paste.httpserver and paste.debug.watchthreads:
Not sure what to call this.
paste.cascade and paste.errordocuments:
Not sure; Ben has an implementation of errordocuments in pylons.middleware.StatusCodeRedirect
paste.urlmap, paste.deploy.config.PrefixMiddleware:
In... some routing thing? Together with the previous package?
paste.proxy:
WSGIProxy (needs lots of cleanup though)
paste.fileapp, paste.urlparser.StaticURLParser, paste.urlparser.PkgResourcesParser:
In some new file-serving package.
paste.cgiapp, wphp.fcgi_app:
Some proxyish app... maybe WSGIProxy?
paste.translogger, paste.debug.prints, paste.util.threadedprint, wsgifilter.proxyapp.DebugHeaders:
Some... other place. Something loggy.
paste.registry, paste.config:
Not sure. Alberto Valverde expressed interest in splitting out paste.registry.
paste.cgitb_catcher:
Move to WebError? Not sure if it matters. For some reason people use this, though.

To Deprecate¶

(In that, I won’t extract these anywhere; I’m not going to do any big deletes anytime soon, though)

paste.recursive
Better to do it manually (with webob.Request.get_response)
paste.wsgiwrappers, paste.request, paste.response, paste.wsgilib, paste.httpheaders, paste.httpexceptions:
All the functionality is already in WebOb.
paste.urlparser.URLParser:
Really this is tied to paste.webkit more than anything.
paste.auth.*:
Well, these all need to be refactored, and replacements exist in AuthKit and repoze.who. Some pieces might still have utility.
paste.debug.profile:
I think repoze.profile supersedes this.
paste.debug.wdg_validator:
It could get reimplemented with more options for validators, but I’m not really that interested at the moment. The code is nothing fancy.
paste.transaction:
More general in repoze.tm
paste.url:
No one uses this

Undecided¶

paste.debug.fsdiff:
Maybe ScriptTest?
paste.session:
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.
paste.gzipper:
I’m a little uncomfortable with this in concept. It’s largely in WebOb right now, but not as middleware.
paste.reloader:
Maybe this should be moved to paste.script (i.e., paster serve)
paste.debug.debugapp, paste.script.testapp:
Alongside other debugging tools, I guess
paste.progress:
Not sure this works.
Paste-1.7.5.1/docs/_build/news.html0000664000175000017500000023334311443716131015406 0ustar ianbianb News — Paste v1.7.5 documentation

News¶

1.7.5¶

  • Won’t install tests/ directory (also caused installation problems on some Mac systems).
  • Fixed problem with gzip middleware and zero-length responses.
  • Use X-Forwarded-For header in paste.translogger
  • Fixed problems with mimeparse code
  • Fixed some corner cases with CGI scripts
  • paste.auth.auth_tkt will URL-quote usernames, avoiding some errors with usernames with ! in them.
  • Improve handling of errors in fetching error pages in paste.errordocument.

1.7.4¶

  • Fix XSS bug (security issue) with not found handlers for paste.urlparser.StaticURLParser and paste.urlmap.URLMap. If you ask for a path with /--><script>... that will be inserted in the error page and can execute Javascript. Reported by Tim Wintle with further details from Georg-Christian Pranschke.
  • Replaced paste.util.mimeparse.desired_match()

1.7.3.1¶

1.7.3¶

  • I got a fever and the only prescription is more paste.cowbell!
  • Fix paste.httpserver on Python 2.6.
  • Fix paste.auth.cookie, which would insert newlines for long cookies.
  • paste.util.mimeparse parses a single * in Accept headers (sent by IE 6).
  • Fix some problems with the wdg_validate middleware.
  • Improvements to paste.auth.auth_tkt: add httponly support, don’t always aggressively set cookies without the wildcard_cookie option. Also on logout, make cookies expire.
  • In paste.proxy.Proxy handle Content-Length of -1.
  • In paste.httpexceptions avoid some unicode errors.
  • In paste.httpserver handle .read() from 100 Continue properly (because of a typo it was doing a readline).
  • Update paste.util.mimeparse from upstream.

1.7.2¶

  • In paste.proxy, added some more headers that are disallowed in WSGI (e.g., Keep-Alive). Send Content-Length. Also fix the missing query string when using paste.proxy.Proxy (paste.proxy.TransparentProxy already worked).
  • Make paste.debug.prints work with Google App Engine.
  • Make environ['wsgi.input'] with paste.httpserver only have a seek method if it is wrapping something with a seek method (which usually it is not).
  • In paste.httpserver re-raise KeyboardInterrupt in worker threads.
  • Added support for the HttpOnly Cookie property to paste.wsgiwrappers
  • Added paste.reloader.add_file_callback(), which lets you watch files based on a callback.
  • Quiet Python 2.6 deprecation warnings.
  • Fix paste.auth.cookie generating bad headers.
  • Added paste.reloader.JythonMonitor for an experimental, optimized reloader on Jython.

1.7.1¶

  • Normalize and make absolute the paths passed to paste.urlparser.StaticURLParser (before passing a relative-to-cwd path to that class would cause Forbidden errors).
  • Deprecate paste.fixture.setup_module()

1.7¶

  • Fixed bug in paste.fixture.TestApp that would submit forms with unnamed fields (like an unnamed submit button). Also made checkboxes with no explicit value send on instead of checked (which is what browsers do).
  • Fixed bug in paste.httpserver where environ['wsgi.input'].readline(max_size) ignored the max_size argument, which can lead to large memory usage (from Jakub Stolarski)
  • Make paste.cascade notice sockets that have stopped producing data. From Casey Zednick.
  • In paste.fixture.TestApp Accept MultiDict values for the params argument in requests. (Anything with a .items() method will have its items encoded as the request parameters.)
  • Fix paste.httpserver to allow binding to port 0.
  • In paste.auth.auth_tkt, set the same cookies (with the same domains) in set_cookie as get unset in logout_user_cookie.
  • In paste.translogger save REQUEST_METHOD in case it gets overridden somewhere (e.g., when using errordocuments POST would show up as GET).
  • Exceptions with unicode messages don’t cause the collector to fail.
  • Sometimes paste.exceptions.errormiddleware.ErrorMiddleware would not call start_response properly; this is fixed (from Andreas Kloecker).
  • paste.fixture.TestApp can store multiple cookie values (previously only one cookie was stored; from Andrey Lebedev)
  • u'' in TestApp(app).get('/') will work when the body isn’t ASCII (before it would give a unicode error). This problem wasn’t present in the recommended WebTest.
  • paste.debug.profile won’t break when content is served with no Content-Type.
  • Accept relative paths and paths with /../ in them for paste.urlparser.StaticURLParser (from Eric Larson). Also fix problem with case normalization on Windows (from Ionel Maries Cristian).
  • paste.registry.StackedObjectProxy‘s now include the proxied object’s names via __dir__ (for Python 2.6).
  • Use environ['wsgi.file_wrapper'] when available (in paste.fileapp).
  • Make paste.debug.prints compatible with App Engine.
  • Fix the domain keyword in paste.wsgiwrappers.WSGIResponse.delete_cookie().

1.6.1¶

  • Fixed bug in paste lint where PATH_INFO would become unicode.

1.6¶

  • Make the import of socket.sslerror conditional in paste.exceptions.reporter (needed for Python interpreters compiled without SSL support).
  • In paste.proxy.TransparentProxy, don’t overwrite X-Forwarded-For header if it is already in the environment.
  • Added 226 IM Used status code to paste.wsgiwrappers
  • In paste.fixture.TestApp treat <image type="image"> the same as a submit button.
  • Use OpenSSL.tsafe.Connection for https with paste.httpserver, avoiding some possible errors (sslv3 alert bad record mac).
  • Fix small issue with paste.cgiapp and mod_wsgi.
  • Use BaseCookie instead of SimpleCookie for storing cookies (avoids quoting cookie values).

1.5.1¶

  • Make paste.cascade more tolerant of a missing or invalid Content-Length.

1.5¶

  • Fixed memory leak with paste.registry not properly removing all references to registered objects should register be called multiple times during a single context for a StackedObjectProxy.
  • paste.httpheaders.CONTENT_RANGE returns bytes START-END/LENGTH instead of just START-END/LENGTH
  • In paste.fixture.TestApp set CONTENT_TYPE to 'application/x-www-form-urlencoded' whenever there are parameters (and no other content type was provided).
  • In paste.session, when cleaning files ignore files that aren’t session files.
  • paste.httpexceptions.HTTPExceptionHandler will no longer catch exceptions raised during the app_iter iteration.
  • paste.cascade.Cascade copies wsgi.input when cascading, so that one of the applications cannot read the input and leave a later application blocked when it tries to read the input.
  • Fix assigning to WSGIResponse.charset breaking the content-type.
  • SMTP authentication is supported for the exception handler. You may now set smtp_username, smtp_password and smtp_use_tls to control this behavior. From pthy.

1.4.2¶

  • Remove FreeBSD threadpool condition in paste.httpserver (which was also breaking code for Windows users).
  • Fix problem with paste.wsgilib.intercept_output and passing up exceptions.

1.4.1¶

  • Allow customization of the paste.config.ConfigMiddleware environ key.
  • Added a current method (an alias of current_conf) to paste.config.DispatchingConfig.
  • Make test response .form attribute work when you have a single named form.
  • Try to encode any unicode input to paste.auth.auth_tkt
  • paste.wsgiwrappers.WSGIResponse now has a .content_type attribute (that does not include parameters), and a .charset attribute (that gets the charset parameter).
  • Inherit inherit show_exceptions_in_wsgi_errors from global configuration. Inherit debug more properly.

1.4¶

  • In paste.httpserver added lots of functionality to the threadpool. See the paste.httpserver threadpool documentation for details. This catches worker threads (and WSGI apps) that take too long to complete their task; killing them eventually, adding more worker threads when the pool is exhausted and it doesn’t look good that it’ll clear soon, and optionally killing the process when there are too many lost/zombie threads (you must be using some kind supervisor process for this last response to make sense).
  • Save host and scheme information during real HTTP proxy requests to paste.httpserver, into the keys paste.httpserver.proxy.scheme and paste.httpserver.proxy.host
  • In paste.exceptions always call start_response; may help problems when there is an exception in start_response itself.
  • Added method to paste.registry.StackedObjectProxy, _object_stack(), which returns a list of all the registered objects. Useful if you want to search through the effective history of a stacked object.
  • Fixed infinite recursion problem with paste.request.EnvironHeaders.keys().
  • Fix paste.wsgiwrappers.WSGIRequest.urlvars to use wsgiorg.routing_args
  • Remove port from paste.request.construct_url if it’s the default port (e.g., port 80 for http).
  • paste.proxy works with headers with continuations in the response (i.e., a header that spans multiple lines). Also, treat a missing Content-Length as 0, not unlimited (may have previously caused freeze ups for some kinds of requests).
  • StackedObjectProxy supports __call__ (i.e., you can use StackedObjectProxy with callable objects).
  • Fixed ProfileMiddleware not calling close() on consumed app_iters.
  • httpheaders.AcceptLanguage now won’t give an exception when there is a malformed parameter in the header.
  • Fix paste.auth.form.auth_form Paste Deploy entry point.
  • Added REST methods to paste.fixture.TestApp, so you can more easily do requests like PUT and DELETE. From Anders Pearson.
  • Added {{default var=default_value}} command to paste.util.template. Make {{# comment}} work.

1.3¶

  • In paste.httpserver remove the reverse DNS lookup to set REMOTE_HOST
  • In paste.fileapp, if the client sends both If-None-Match and If-Modified-Since, prefer If-None-Match. Make ETags include the size as well as last modified timestamp. Make it possible to override how mimetypes are guessed.
  • HTTPException objects now have a exc.response(environ) method that returns a WSGIResponse object.
  • egg:Paste#watch_threads will show tracebacks of each thread under Python 2.5.
  • Made paste.util.template trim whitespace around statements that are on their own line.
  • paste.fileapp.DataApp now accepts allowed_headers=[...] to specify the allowed headers. By default only GET and HEAD are allowed.
  • Added paste.util.import_string.try_import_module, which imports modules and catches ImportError, but only if it’s an error importing the specific module, not an uncaught ImportError in the module being imported.

1.2.1¶

  • paste.httpserver didn’t implement the readline that the cgi module wants (regression in 1.2).

1.2¶

  • Backward incompatible change: paste.fileapp.FileApp properly supports request methods, including HEAD. If you were subclassing FileApp or DataApp and overriding __call__() you may have to subclass get() instead.
  • paste.httpheaders now parses the HTTP Accept-Language header and returns a list of languages the browser supports in the order it prefers them.
  • paste.mimeparse module added that handles parsing HTTP Accept headers for quality and mime-types.
  • paste.request.construct_url was adding SERVER_PORT to HTTP_HOST; but HTTP_HOST (from the Host header) generally contains a port when necessary, and SERVER_PORT should only be used with SERVER_NAME.
  • Added entry point for paste.registry.RegistryManager (egg:Paste#registry).
  • paste.request.HeaderDict fixed to know that Content-Length maps to CONTENT_LENGTH.
  • Can use paste.urlparser.StaticURLParser with sub-instances other than paste.fileapp.FileApp (if you subclass and override make_app)
  • paste.fixture.TestApp.get(status=X) takes a list of allowed status codes for X.
  • Added a small templating system for internal use (paste.util.template)
  • Removed a bunch of long-deprecated modules (generally modules that have been moved to other names).

In paste.wsgiwrappers¶

  • paste.wsgiwrappers.WSGIRequest has match_accept() function to screen incoming HTPT Accept values against a list of mime-types.

  • paste.wsgiwrappers.WSGIRequest.defaults now accepts a new key:

    language:

    The i18n language that should be used as the fallback should a translation not occur in a language file. See docs for details.

  • paste.wsgiwrappers.WSGIRequest can now optionally decode form parameters to unicode when it has a charset value set.

  • Deprecated the paste.wsgiwrappers.settings StackedObjectProxy dictionary for paste.wsgiwrappers.WSGIResponse.defaults.

In paste.httpserver¶

  • Regression in 1.1 fixed, where Paste’s HTTP server would drop trailing slashes from paths.
  • paste.httpserver now puts a key in the environment when using a thread pool that allows you to track the thread pool and see any wedged threads. egg:Paste#watch_threads is an application that can display this information.
  • paste.httpserver now accepts all request methods, not just GET, PUT, etc. (Methods like MKCOL were previously rejected.)
  • paste.httpserver has a wsgi.input that now does not block if you try to read past the end (it is limited to returning the number of bytes given in Content-Length). Double-reading from wsgi.input won’t give you the same data, but it won’t cause blocking.

1.1.1¶

  • Fixed major issue with serving static files on Windows (a regression in Paste 1.1 where most static files would return 404 Not Found).
  • Fixed parse_dict_querystring returning empty dicts instead of MultiDicts.
  • Added paste.config, a rewrite of paste.deploy.config using paste.registry. This version of ConfigMiddleware will enable use of paste.config.CONFIG within the EvalException interactive debugger.
  • Fixed problem where paste.recursive would leave wsgi.input and CONTENT_LENGTH set for recursive requests.
  • Changed the static file servers to give 404 Not Found responses when you have extra parts after a static file, instead of 400 Bad Request (like when you request /file.html/extra/path)

1.1¶

  • Security fix for paste.urlparser.StaticURLParser. The problem allowed escaping the root (and reading files) when used with paste.httpserver (this does not effect other servers, and does not apply when proxying requests from Apache to paste.httpserver).
  • paste.httpserver and paste.fixture.TestApp url-unquote SCRIPT_NAME and PATH_INFO, as specified in the CGI spec. Thanks to Jon Nelson for pointing out both these issues.
  • paste.registry now works within the EvalException interactive debugger.
  • Fixed paste.auth.open_id failures not returning a correct response.
  • Changed paste.httpexceptions.HTTPUnauthorized so that the WWW-Authenticate header is not required. 401 responses don’t have to have that header.
  • In paste.fixture.TestApp: <form> tags that have to action will preserve the existing query string. (Generally relative links that are completely empty should but were not preserving the query string)
  • Made paste.* compatible with py2exe by adding a modulefinder call in __init__.py
  • The paste.gzipper gzipping middleware wasn’t changing the Content-Length header properly; thanks to Brad Clements for the fix.
  • Fixed paste.proxy to not use anything based on the dict form of httplib..HTTPMessage. This form folds headers together in a way that breaks Set-Cookie headers (two Set-Cookie headers would be merged into one).
  • paste.request.parse_formvars didn’t accept parameters in CONTENT_TYPE. prototype.js sets a charset parameter, which caused a problem.
  • Added a __traceback_decorator__ magic local variable, to allow arbitrary manipulation of the output of paste.exceptions.collector before formatting.
  • Added unicorn power to paste.pony (from Chad Whitacre)
  • For paste.httpserver SSL support: add support loading an explicit certificate context, and using ssl_pem='*' create an unsigned SSL certificate (from Jason Kirtland).
  • Fix some cases where paste.httpserver can have an orphaned thread pool (causing the process to not shut down properly). Patch from jek.

1.0¶

  • Fixed parsed_formvars potentially locking up on wsgi.input after modification of QUERY_STRING.
  • Fixed problem where paste.exceptions.errormiddleware didn’t expose the .close() method of the app_iter that it wraps (to catch exceptions). This is a problem if the server about the errormiddleware aborts the request; it should then call .close(), but won’t necessarily exhaust the iterator to do so.
  • Added entry point for paste.translogger (egg:Paste#translogger)
  • Fixed some cases where long data (e.g., a file upload) would show up in the error report, creating a very very large report. Also, put in a monkeypatch for the cgi module so that repr(uploaded_field) won’t load the entire field into memory (from its temporary file location).
  • Added a force_host option to paste.proxy.TransparentProxy, which will force all incoming requests to the same host, but leave the Host header intact.
  • Added automatic cleanup of old sessions for paste.session, from Amir Salihefendic.
  • Quote the function name in tracebacks in the exception formatter; Genshi has function names that use <>.

0.9.9¶

  • Fixed paste.response.HeaderDict get and setdefault methods to be case insensitive
  • Fix use of TestApp().post(params={'key': ['list of', 'values']}) as reported by Syver Enstad.
  • paste.fileapp.DataApp is now directly usable (was previously only usable as an abstract base class).

0.9.8¶

  • Fixed wsgiwrappers.WSGIResponse.delete_cookie. It also now takes optional path and domain arguments
  • wsgiwrappers.WSGIResponse now handles generator/iterator content more cleanly, and properly encodes unicode content according to its specified charset
  • Fixed wsgiwrappers.WSGIResponse mishandling multiple headers of the same name
  • Added a Paste Deploy entry point for paste.auth.cookie
  • Moved Paste Deploy dependencies out of top-level modules and into Paste-Deploy-specific entry point functions. This should make Paste more-or-less Paste Deploy independent. paste.urlparser and paste.exceptions.errormiddleware still have some leftover bits.
  • Added another redirector type to paste.recursive, environ['paste.recursive.include_app_iter'] which gives access to the original app_iter.
  • Bug with wsgilib.catch_errors and app_iters with no close() method.
  • Long words in tracebacks weren’t being wrapped correctly at all. Also, large data would cause the wrapping routine to give a recursion error. Now large data is truncated (at 1000 characters), and recursion won’t be a problem. Also, wrapping shouldn’t lose characters.
  • Better exception if you try to put a non-str into environ when using paste.auth.cookie
  • paste.exceptions.collector produces an exc_data.exception_type that is a class, not a string. This helps it get formatted better in Python 2.5.
  • All the tests pass on Python 2.5!
  • Added paste.proxy.TransparentProxy, which just sends the request described in the WSGI environ on without any modification. More useful for WSGI clients than servers, it effectively allows any WSGI-based request mechanism to act like an httplib-based request mechanism.
  • Added a cache_max_age argument to paste.urlparser.StaticURLParser, which allows you to encourage the caching of static files. Patch from Brad Clements.
  • Added suppress_http_headers to paste.proxy.Proxy, which will filter out HTTP headers from the request before passing it on. Patch from Brad Clements.

0.9.7¶

  • The EvalException ‘full traceback’ button is now only displayed when the full traceback differs from the regular (includes hidden frames).
  • Fixed EvalException returning a Content-type of ‘text-html’ instead of ‘text/html’ in some cases.

0.9.6¶

  • Renamed the paste.util.multidict.multidict class to paste.util.multidict.MultiDict

0.9.5¶

  • Fixed a security vulnerability in paste.urlparser‘s StaticURLParser and PkgResourcesParser where, with some servers, you could escape the document root.

  • Significantly improved paste.httpserver‘s (egg:Paste#http) performance. It now uses a thread pool: previously it created a new thread for every request. To revert back to the old, slower behavior, set:

    use_threadpool = false
    

    in the [server:main] section of the config file.

  • More control of where the output of paste.debug.prints goes.

  • Added a warning to paste.wsgilib.add_close if the upstream app_iter consumer doesn’t call the app_iter.close() method.

  • Fixed testapp.post(params={})

  • Fixed paste.translogger.TransLogger to log to the Apache combined log format as advertised.

  • Fixed paste.urlparser classes to handle quoted characters (e.g. %20) in URL paths.

  • Changed paste.session to allow manipulating a session for the first time after start_response is called.

  • Added paste.wsgilib.add_start_close which calls a function just before returning the first chunk of the app_iter.

  • Changed paste.urlmap so that it matches domain-specific mappings before domain-neutral mappings.

  • Fixed IE 6 potentially receiving the following "400 Bad Request" error on file downloads:

    Please check your system clock.
    According to this server, the time provided in the
    If-Modified-Since header is in the future.
  • Added a ‘no’ keyword argument to TestResponse.mustcontain, so you can assert that a response does contain some strings at the same time that you assert that a response does not contain other strings, like:

    res = app.get('/')
    res.mustcontain('this must be there',
                    no=['error', 'unexpected'])
    
  • Fixed fileapp.FileApp to pay attention to the If-None-Match header, which does ETag matching; before only If-Modified-Since was supported, even though an ETag header was being sent; in particular Firefox would then only send If-None-Match and so conditional requests never worked.

  • Changed usage of paste.request.MultiDict to paste.util.multidict, particularly in paste.wsgiwrappers where request.GET returns a new style of dictionary interface.

  • Be more careful in paste.request.parse_formvars not to let the cgi module read from wsgi.input when there are no parsable variables in the input (based on CONTENT_TYPE).

0.9.4¶

  • This released was lost in a tragic clerical accident.

0.9.3¶

  • 0.9.2 Included a version of MochiKit that was no longer compatible with evalexception; 0.9.3 reverts to a previous version.
  • Change wsgi.run_once=False for paste.httpserver
  • Added entry points for debug apps

0.9.2¶

  • Fix in paste.urlmap when connecting with host:port.
  • Added /_debug/summary to evalexception, which gives a JSON-formatted list of all the exceptions in memory.

0.9.1¶

  • A fix for paste.errordocument, when doing an internal redirect from a POST request (the request is rewritten as a GET request)

0.9¶

  • Added paste.request.WSGIRequest, a request object that wraps the WSGI environment.
  • Added paste.registry, which is middleware for registering threadlocal objects in a request.
  • Avoid annoying warning from paste.recursive
  • paste.httpserver now removes HTTPServer’s transaction logging, which was doing a reverse DNS lookup.
  • Added has_session to paste.session
  • Allow for conditional paste.wsgilib.intercept_output which should be slightly faster (and streamable) compared to doing the condition manually.
  • Added entry point for paste.proxy, plus improvements from Brad Clements (support path in target, filter request methods)
  • Added paste.pony so pony power can be added to any WSGI application.
  • Added port matching to paste.urlmap.

0.4.1¶

0.4¶

  • Fixed up paste documentation (especially for new packages/modules)
  • Added paste.auth package for authentication related WSGI middle-ware components:
    • basic and digest HTTP authentication as described by RFC 2617
    • support for Yale’s Central Authentication System (cas)
    • open_id supports single sign-on originally developed for LiveJournal (see http://openid.net)
    • cookie digitally signs cookies to record the current authenticated user (REMOTE_USER), session identifier (REMOTE_SESSION), and other WSGI entries in the environ.
    • a form module (to be used with cookie or an equivalent) provides a simple HTML based form authentication.
    • the multi module is an experimental mechanism for choosing an authentication mechanism based on the WSGI environ
  • Added paste.httpserver module which provides a very simple WSGI server built upon python’s BaseHTTPServer; this server has support for several features:
    • support for SSL connections via OpenSSL
    • support for HTTP/1.1 100 Continue messages as required by the WSGI specification (many HTTP server implementations don’t do this)
    • implemented as a Mix-In so that it can be used with other more enchanted versions of BaseHTTPServer
    • support for ‘Keep-Alive’ (standard in HTTP/1.1) by either providing a content-length or closing a connection if one is not available
  • Improved the paste.httpexceptions module:
    • added missing exception objects, and better descriptions
    • fixed several bugs in how exceptions are caught and propagated
    • usage as a wsgi_application() enables exceptions to be returned without throwing or catching the error
    • support for plain/text messages for text-only clients such as curl, python’s urllib, or Microsoft Excel
    • allows customization of the HTML template for higher-level frameworks
  • Added paste.httpheaders module to provide a uniform mechanism to access/update standard HTTP headers in a WSGI environ and response_headers collection; it includes specific support for:
    • providing “common” header names and sorting them as suggested by RFC 2616
    • validated support for Cache-Control header construction
    • validated support for Content-Disposition header construction
    • parsing of If-Modified-Since and other date oriented headers
    • parsing of Range header for partial-content delivery
    • composition of HTTP/1.1 digest Authorization responses
  • Improved paste.fileapp to support:
    • static in-memory resources
    • incremental downloading of files from disk
    • responding to ‘Range’ requests to handle partial downloads
    • allowing cache settings to be easily provided; including support for HTTP/1.0 ‘Expires’ and HTTP/1.1 ‘Cache-Control’
  • Added an experimental paste.transaction module for handling commit/rollback of standard DBAPI database connections
  • Added a paste.util.datetimeutil module for parsing standard date/time user-generated text values
  • Added a debug package, which includes:
    • previous top-level modules prints, profile, wdg_validate and doctest_webapp
    • a testserver module suitable to test HTTP socket connections via py.test
  • Re-factored paste.wsgilib into several other modules:
  • Significant improvements to wsgiutils.wsgilib module:
    • added a dump_environ application to help debugging
    • fixes to raw_interactive to comply with WSGI specifications
    • raw_interactive now logs all 5xx exceptions and sets HTTP_HOST
  • Added an argument no_profile to paste.debug.profile.profile_decorator; if that option is false, then don’t profile the function at all.
  • Changed paste.lint to check that the status contains a message (e.g., "404 Not Found" instead of just "404"). Check that environmental variables HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH are no present. Made unknown REQUEST_METHOD a warning (not an error).
  • Added parameter cwd to TestFileEnvironment.run
  • paste.fixture.TestApp:
    • Form filling code (use response.forms[0] to get a form object)
    • Added click method.
    • Better attribute errors.
    • You can force set hidden fields using form.fields[name].force_value(value) (normally setting the value of a hidden field is an error).
    • Frameworks can now add custom attributes to the response object.
  • paste.wsgilib.capture_output is deprecated in favor of paste.wsgilib.intercept_output
  • Remove use of exceptions in paste.cascade.Cascade, which causes weird effects in some cases. Generally we aren’t using exceptions internally now, only return status codes. Also in cascade, be careful to keep cascaded requests from sharing the same environment.
  • paste.wsgilib.error_response is deprecated (paste.httpexceptions replaces this with exception’s .wsgi_application method).
  • Moved paste.login to the attic, since paste.auth pretty much replaces it.
  • paste.urlparser improvements:
    • Added an application urlparser.StaticURLParser for serving static files.
    • Added an application urlparser.PkgResourcesParser for serving static files found with pkg_resources (e.g., out of zipped Eggs).
    • Be less picky about ambiguous filenames when using URLParser; if an exact file match exists, use that. (file.gif.bak would cause a request for file.gif to be ambiguous before)
    • Now looks for a .wsgi_application attribute when serving Python files/modules, as a general hook for returning a WSGI application version of an object.
  • The ErrorMiddleware:
    • Returns trimmed-down exceptions if there is a _ GET variable in the request (which is meant to signal an XMLHttpRequest). Exceptions displayed in this context are best when they are smaller and easier to display.
    • Includes a text version of the traceback, for easier copy-and-paste.
    • Avoid printing exceptions to wsgi.errors if they are already displayed elsewhere (at least by default).
    • Highlight Python code.
  • Use pkg_resources.declare_namespace so that there are less problems about confusing the paste package that is provided by Paste, Paste Script, Paste Deploy, and Paste WebKit. Before you could get one of these at random if you didn’t use pkg_resources.require first.
  • Cleaned up use of exc_info argument in start_response calls (both accepting and producing), in a variety of places.
Paste-1.7.5.1/docs/_build/modindex.html0000664000175000017500000003365711410507054016243 0ustar ianbianb Global Module Index — Paste v1.7.4 documentation Paste-1.7.5.1/docs/_build/index.html0000664000175000017500000001775211443716126015551 0ustar ianbianb Python Paste — Paste v1.7.5 documentation Paste-1.7.5.1/docs/_build/genindex.html0000664000175000017500000011701611443716131016231 0ustar ianbianb Index — Paste v1.7.5 documentation

Index

A | B | C | D | E | F | G | H | I | L | M | N | P | R | S | T | U | V | W

A

add_close() (in module paste.wsgilib)
add_start_close() (in module paste.wsgilib)
apply() (paste.httpheaders.HTTPHeader method)
ArchiveStore() (in module paste.fileapp)
AuthBasicAuthenticator (class in paste.auth.basic)
AuthBasicHandler (class in paste.auth.basic)
AuthCASHandler (class in paste.auth.cas)
AuthCookieEnviron (class in paste.auth.cookie)
AuthCookieHandler (class in paste.auth.cookie)
AuthCookieSigner (class in paste.auth.cookie)
AuthDigestAuthenticator (class in paste.auth.digest)
AuthDigestHandler (class in paste.auth.digest)
AuthFormHandler (class in paste.auth.form)
AuthTicket (class in paste.auth.auth_tkt)
AuthTKTMiddleware (class in paste.auth.auth_tkt)

B

BadTicket
BasicTransactionHandler() (in module paste.transaction)

C

Cascade (class in paste.cascade)
catch_errors() (in module paste.wsgilib)
catch_errors_app() (in module paste.wsgilib)
CGIApplication (class in paste.cgiapp)
CGIError
CgitbMiddleware (class in paste.cgitb_catcher)
chained_app_iters() (in module paste.wsgilib)
Checkbox (class in paste.fixture)
clear() (paste.fixture.TestFileEnvironment method)
collect_exception() (in module paste.exceptions.collector)
compose() (paste.httpheaders.HTTPHeader method)
ConnectionFactory (class in paste.transaction)
construct_url() (in module paste.request)

D

DataApp() (in module paste.fileapp)
delete() (paste.fixture.TestApp method)
(paste.httpheaders.HTTPHeader method)
Diff (class in paste.debug.fsdiff)
digest_password() (in module paste.auth.digest)
Dir (class in paste.debug.fsdiff)
DirectoryApp (class in paste.fileapp)
do_request() (paste.fixture.TestApp method)
dump_environ() (in module paste.wsgilib)

E

EmailReporter (class in paste.exceptions.reporter)
encode_multipart() (paste.fixture.TestApp method)
encode_unicode_app_iter (class in paste.wsgilib)
EnvironHeaders (class in paste.request)
EnvironVariable (class in paste.httpheaders)
ErrorMiddleware (class in paste.exceptions.errormiddleware)
eval_import() (in module paste.util.import_string)
EvalException (class in paste.evalexception)
ExceptionCollector (class in paste.exceptions.collector)

F

Field (class in paste.fixture)
File (class in paste.debug.fsdiff)
FileApp (class in paste.fileapp)
FileReporter (class in paste.exceptions.reporter)
force_value() (paste.fixture.Field method)
Form (class in paste.fixture)
format_html() (in module paste.exceptions.formatter)
format_text() (in module paste.exceptions.formatter)
ForwardRequestException() (in module paste.recursive)
FoundDir (class in paste.fixture)
FoundFile (class in paste.fixture)

G

get() (paste.fixture.Form method)
(paste.fixture.TestApp method)
get_cookie_dict() (in module paste.request)
get_cookies() (in module paste.request)
get_header() (in module paste.httpheaders)
GrantIPMiddleware (class in paste.auth.grantip)

H

handle_exception() (in module paste.exceptions.errormiddleware)
has_header() (in module paste.response)
header_value() (in module paste.response)
HeaderDict (class in paste.response)
Hidden (class in paste.fixture)
HTMLFormatter (class in paste.exceptions.formatter)
HTTPBadGateway
HTTPBadRequest
HTTPClientError
HTTPConflict
HTTPError
HTTPException
HTTPExceptionHandler (class in paste.httpexceptions)
HTTPExpectationFailed
HTTPForbidden
HTTPFound
HTTPGatewayTimeout
HTTPGone
HTTPHeader (class in paste.httpheaders)
HTTPInternalServerError
HTTPLengthRequired
HTTPMethodNotAllowed
HTTPMovedPermanently
HTTPMultipleChoices
HTTPNotAcceptable
HTTPNotFound
HTTPNotImplemented
HTTPNotModified
HTTPPaymentRequired
HTTPPreconditionFailed
HTTPProxyAuthenticationRequired
HTTPRedirection
HTTPRequestEntityTooLarge
HTTPRequestRangeNotSatisfiable
HTTPRequestTimeout
HTTPRequestURITooLong
HTTPServerError
HTTPServiceUnavailable
HTTPTemporaryRedirect
HTTPUnauthorized
HTTPUnsupportedMediaType
HTTPUseProxy
HTTPVersionNotSupported

I

Image (class in paste.url)
import_module() (in module paste.util.import_string)
install() (in module paste.reloader)
interactive() (in module paste.wsgilib)
intercept_output() (in module paste.wsgilib)

L

list_headers() (in module paste.httpheaders)
LogReporter (class in paste.exceptions.reporter)

M

make_auth_cookie() (in module paste.auth.cookie)
make_auth_tkt_middleware() (in module paste.auth.auth_tkt)
make_bad_app() (in module paste.debug.watchthreads)
make_basic() (in module paste.auth.basic)
make_cascade() (in module paste.cascade)
make_cgi_application() (in module paste.cgiapp)
make_cgitb_middleware() (in module paste.cgitb_catcher)
make_digest() (in module paste.auth.digest)
make_error_middleware() (in module paste.exceptions.errormiddleware)
make_errordocument() (in module paste.errordocument)
make_filter() (in module paste.translogger)
make_form() (in module paste.auth.form)
make_grantip() (in module paste.auth.grantip)
make_gzip_middleware() (in module paste.gzipper)
make_middleware() (in module paste.httpexceptions)
make_pkg_resources() (in module paste.urlparser)
make_pony() (in module paste.pony)
make_profile_middleware() (in module paste.debug.profile)
make_proxy() (in module paste.proxy)
make_registry_manager() (in module paste.registry)
make_session_middleware() (in module paste.session)
make_slow_app() (in module paste.debug.debugapp)
make_static() (in module paste.urlparser)
make_test_app() (in module paste.debug.debugapp)
make_transparent_proxy() (in module paste.proxy)
make_url_parser() (in module paste.urlparser)
make_watch_threads() (in module paste.debug.watchthreads)
make_wdg_validate_middleware() (in module paste.debug.wdg_validate)
middleware (class in paste.gzipper)
middleware() (in module paste.lint)
Monitor (class in paste.reloader)
MultiDict (class in paste.util.multidict)
MultiHandler (class in paste.auth.multi)

N

normalize_headers() (in module paste.httpheaders)

P

parse() (paste.httpheaders.HTTPHeader method)
parse_formvars() (in module paste.request)
parse_querystring() (in module paste.request)
paste.auth.auth_tkt (module)
paste.auth.basic (module)
paste.auth.cas (module)
paste.auth.cookie (module)
paste.auth.digest (module)
paste.auth.form (module)
paste.auth.grantip (module)
paste.auth.multi (module)
paste.cascade (module)
paste.cgiapp (module)
paste.cgitb_catcher (module)
paste.debug.debugapp (module)
paste.debug.fsdiff (module)
paste.debug.prints (module)
paste.debug.profile (module)
paste.debug.watchthreads (module)
paste.debug.wdg_validate (module)
paste.errordocument (module)
paste.evalexception (module)
paste.exceptions.collector (module)
paste.exceptions.errormiddleware (module)
paste.exceptions.formatter (module)
paste.exceptions.reporter (module)
paste.fileapp (module)
paste.fixture (module)
paste.gzipper (module)
paste.httpexceptions (module)
paste.httpheaders (module)
paste.httpserver (module)
paste.lint (module)
paste.pony (module)
paste.progress (module)
paste.proxy (module)
paste.recursive (module)
paste.registry (module)
paste.reloader (module)
paste.request (module)
paste.response (module)
paste.session (module)
paste.transaction (module)
paste.translogger (module)
paste.url (module)
paste.urlmap (module)
paste.urlparser (module)
paste.util.import_string (module)
paste.util.multidict (module)
paste.wsgilib (module)
paste.wsgiwrappers (module)
path_info_pop() (in module paste.request)
path_info_split() (in module paste.request)
PathProxyURLMap (class in paste.urlmap)
PkgResourcesParser (class in paste.urlparser)
PonyMiddleware (class in paste.pony)
post() (paste.fixture.TestApp method)
PrintDebugMiddleware (class in paste.debug.prints)
ProcResult (class in paste.fixture)
profile_decorator() (in module paste.debug.profile)
ProfileMiddleware (class in paste.debug.profile)
Proxy (class in paste.proxy)
put() (paste.fixture.TestApp method)

R

Radio (class in paste.fixture)
raw_interactive() (in module paste.wsgilib)
RecursiveMiddleware (class in paste.recursive)
Registry (class in paste.registry)
RegistryManager (class in paste.registry)
remove_header() (in module paste.response)
replace_header() (in module paste.response)
report_expected_diffs() (in module paste.debug.fsdiff)
reset() (paste.fixture.TestApp method)
resolve_relative_url() (in module paste.request)
run() (paste.fixture.TestFileEnvironment method)

S

Select (class in paste.fixture)
select() (paste.fixture.Form method)
serve() (in module paste.httpserver)
server_runner() (in module paste.httpserver)
SessionMiddleware (class in paste.session)
set() (paste.fixture.Form method)
show_diff() (in module paste.debug.fsdiff)
simple_import() (in module paste.util.import_string)
SimpleApplication (class in paste.debug.debugapp)
SlowConsumer (class in paste.debug.debugapp)
Snapshot (class in paste.debug.fsdiff)
StackedObjectProxy (class in paste.registry)
StackedObjectRestorer (class in paste.registry)
StaticURLParser (class in paste.urlparser)
StatusBasedForward (class in paste.errordocument)
Submit (class in paste.fixture)
submit() (paste.fixture.Form method)
submit_fields() (paste.fixture.Form method)

T

TestApp (class in paste.fixture)
TestFileEnvironment (class in paste.fixture)
TestRequest (class in paste.fixture)
Text (class in paste.fixture)
Textarea (class in paste.fixture)
TextFormatter (class in paste.exceptions.formatter)
TransactionManagerMiddleware (class in paste.transaction)
TransLogger (class in paste.translogger)
TransparentProxy (class in paste.proxy)
try_import_module() (in module paste.util.import_string)

U

UnicodeMultiDict (class in paste.util.multidict)
update() (paste.httpheaders.HTTPHeader method)
UploadProgressMonitor (class in paste.progress)
UploadProgressReporter (class in paste.progress)
URL (class in paste.url)
URLMap (class in paste.urlmap)
urlmap_factory() (in module paste.urlmap)
URLParser (class in paste.urlparser)

V

values() (paste.httpheaders.HTTPHeader method)

W

watch_file() (in module paste.reloader)
WatchThreads (class in paste.debug.watchthreads)
WDGValidateMiddleware (class in paste.debug.wdg_validate)
writefile() (paste.fixture.TestFileEnvironment method)
WSGIAppReporter (class in paste.exceptions.reporter)
WSGIRequest (class in paste.wsgiwrappers)
WSGIResponse (class in paste.wsgiwrappers)
WSGIWarning

Python Paste

Paste-1.7.5.1/docs/_build/do-it-yourself-framework.html0000664000175000017500000015617111443716126021316 0ustar ianbianb A Do-It-Yourself Framework — Paste v1.7.5 documentation

A Do-It-Yourself Framework¶

author:Ian Bicking <ianb@colorstudy.com>
revision:$Rev$
date:$LastChangedDate$

This tutorial has been translated into Portuguese.

A newer version of this article is available using WebOb.

Introduction and Audience¶

This short tutorial is meant to teach you a little about WSGI, and as an example a bit about the architecture that Paste has enabled and encourages.

This isn’t an introduction to all the parts of Paste – in fact, we’ll only use a few, and explain each part. This isn’t to encourage everyone to go off and make their own framework (though honestly I wouldn’t mind). The goal is that when you have finished reading this you feel more comfortable with some of the frameworks built using this architecture, and a little more secure that you will understand the internals if you look under the hood.

What is WSGI?¶

At its simplest WSGI is an interface between web servers and web applications. We’ll explain the mechanics of WSGI below, but a higher level view is to say that WSGI lets code pass around web requests in a fairly formal way. But there’s more! WSGI is more than just HTTP. It might seem like it is just barely more than HTTP, but that little bit is important:

  • You pass around a CGI-like environment, which means data like REMOTE_USER (the logged-in username) can be securely passed about.
  • A CGI-like environment can be passed around with more context – specifically instead of just one path you two: SCRIPT_NAME (how we got here) and PATH_INFO (what we have left).
  • You can – and often should – put your own extensions into the WSGI environment. This allows for callbacks, extra information, arbitrary Python objects, or whatever you want. These are things you can’t put in custom HTTP headers.

This means that WSGI can be used not just between a web server an an application, but can be used at all levels for communication. This allows web applications to become more like libraries – well encapsulated and reusable, but still with rich reusable functionality.

Writing a WSGI Application¶

The first part is about how to use WSGI at its most basic. You can read the spec, but I’ll do a very brief summary:

  • You will be writing a WSGI application. That’s an object that responds to requests. An application is just a callable object (like a function) that takes two arguments: environ and start_response.
  • The environment looks a lot like a CGI environment, with keys like REQUEST_METHOD, HTTP_HOST, etc.
  • The environment also has some special keys like wsgi.input (the input stream, like the body of a POST request).
  • start_response is a function that starts the response – you give the status and headers here.
  • Lastly the application returns an iterator with the body response (commonly this is just a list of strings, or just a list containing one string that is the entire body.)

So, here’s a simple application:

def app(environ, start_response):
    start_response('200 OK', [('content-type', 'text/html')])
    return ['Hello world!']

Well... that’s unsatisfying. Sure, you can imagine what it does, but you can’t exactly point your web browser at it.

There’s other cleaner ways to do this, but this tutorial isn’t about clean it’s about easy-to-understand. So just add this to the bottom of your file:

if __name__ == '__main__':
    from paste import httpserver
    httpserver.serve(app, host='127.0.0.1', port='8080')

Now visit http://localhost:8080 and you should see your new app. If you want to understand how a WSGI server works, I’d recommend looking at the CGI WSGI server in the WSGI spec.

An Interactive App¶

That last app wasn’t very interesting. Let’s at least make it interactive. To do that we’ll give a form, and then parse the form fields:

from paste.request import parse_formvars

def app(environ, start_response):
    fields = parse_formvars(environ)
    if environ['REQUEST_METHOD'] == 'POST':
        start_response('200 OK', [('content-type', 'text/html')])
        return ['Hello, ', fields['name'], '!']
    else:
        start_response('200 OK', [('content-type', 'text/html')])
        return ['<form method="POST">Name: <input type="text" '
                'name="name"><input type="submit"></form>']

The parse_formvars function just takes the WSGI environment and calls the cgi module (the FieldStorage class) and turns that into a MultiDict.

Now For a Framework¶

Now, this probably feels a bit crude. After all, we’re testing for things like REQUEST_METHOD to handle more than one thing, and it’s unclear how you can have more than one page.

We want to build a framework, which is just a kind of generic application. In this tutorial we’ll implement an object publisher, which is something you may have seen in Zope, Quixote, or CherryPy.

Object Publishing¶

In a typical Python object publisher you translate / to .. So /articles/view?id=5 turns into root.articles.view(id=5). We have to start with some root object, of course, which we’ll pass in...

class ObjectPublisher(object):

    def __init__(self, root):
        self.root = root

    def __call__(self, environ, start_response):
        ...

app = ObjectPublisher(my_root_object)

We override __call__ to make instances of ObjectPublisher callable objects, just like a function, and just like WSGI applications. Now all we have to do is translate that environ into the thing we are publishing, then call that thing, then turn the response into what WSGI wants.

The Path¶

WSGI puts the requested path into two variables: SCRIPT_NAME and PATH_INFO. SCRIPT_NAME is everything that was used up getting here. PATH_INFO is everything left over – it’s the part the framework should be using to find the object. If you put the two back together, you get the full path used to get to where we are right now; this is very useful for generating correct URLs, and we’ll make sure we preserve this.

So here’s how we might implement __call__:

def __call__(self, environ, start_response):
    fields = parse_formvars(environ)
    obj = self.find_object(self.root, environ)
    response_body = obj(**fields.mixed())
    start_response('200 OK', [('content-type', 'text/html')])
    return [response_body]

def find_object(self, obj, environ):
    path_info = environ.get('PATH_INFO', '')
    if not path_info or path_info == '/':
        # We've arrived!
        return obj
    # PATH_INFO always starts with a /, so we'll get rid of it:
    path_info = path_info.lstrip('/')
    # Then split the path into the "next" chunk, and everything
    # after it ("rest"):
    parts = path_info.split('/', 1)
    next = parts[0]
    if len(parts) == 1:
        rest = ''
    else:
        rest = '/' + parts[1]
    # Hide private methods/attributes:
    assert not next.startswith('_')
    # Now we get the attribute; getattr(a, 'b') is equivalent
    # to a.b...
    next_obj = getattr(obj, next)
    # Now fix up SCRIPT_NAME and PATH_INFO...
    environ['SCRIPT_NAME'] += '/' + next
    environ['PATH_INFO'] = rest
    # and now parse the remaining part of the URL...
    return self.find_object(next_obj, environ)

And that’s it, we’ve got a framework.

Taking It For a Ride¶

Now, let’s write a little application. Put that ObjectPublisher class into a module objectpub:

from objectpub import ObjectPublisher

class Root(object):

    # The "index" method:
    def __call__(self):
        return '''
        <form action="welcome">
        Name: <input type="text" name="name">
        <input type="submit">
        </form>
        '''

    def welcome(self, name):
        return 'Hello %s!' % name

app = ObjectPublisher(Root())

if __name__ == '__main__':
    from paste import httpserver
    httpserver.serve(app, host='127.0.0.1', port='8080')

Alright, done! Oh, wait. There’s still some big missing features, like how do you set headers? And instead of giving 404 Not Found responses in some places, you’ll just get an attribute error. We’ll fix those up in a later installment...

Give Me More!¶

You’ll notice some things are missing here. Most specifically, there’s no way to set the output headers, and the information on the request is a little slim.

# This is just a dictionary-like object that has case-
# insensitive keys:
from paste.response import HeaderDict

class Request(object):
    def __init__(self, environ):
        self.environ = environ
        self.fields = parse_formvars(environ)

class Response(object):
    def __init__(self):
        self.headers = HeaderDict(
            {'content-type': 'text/html'})

Now I’ll teach you a little trick. We don’t want to change the signature of the methods. But we can’t put the request and response objects in normal global variables, because we want to be thread-friendly, and all threads see the same global variables (even if they are processing different requests).

But Python 2.4 introduced a concept of “thread-local values”. That’s a value that just this one thread can see. This is in the threading.local object. When you create an instance of local any attributes you set on that object can only be seen by the thread you set them in. So we’ll attach the request and response objects here.

So, let’s remind ourselves of what the __call__ function looked like:

class ObjectPublisher(object):
    ...

    def __call__(self, environ, start_response):
        fields = parse_formvars(environ)
        obj = self.find_object(self.root, environ)
        response_body = obj(**fields.mixed())
        start_response('200 OK', [('content-type', 'text/html')])
        return [response_body]

Lets’s update that:

import threading
webinfo = threading.local()

class ObjectPublisher(object):
    ...

    def __call__(self, environ, start_response):
        webinfo.request = Request(environ)
        webinfo.response = Response()
        obj = self.find_object(self.root, environ)
        response_body = obj(**dict(webinfo.request.fields))
        start_response('200 OK', webinfo.response.headers.items())
        return [response_body]

Now in our method we might do:

class Root:
    def rss(self):
        webinfo.response.headers['content-type'] = 'text/xml'
        ...

If we were being fancier we would do things like handle cookies in these objects. But we aren’t going to do that now. You have a framework, be happy!

WSGI Middleware¶

Middleware is where people get a little intimidated by WSGI and Paste.

What is middleware? Middleware is software that serves as an intermediary.

So lets write one. We’ll write an authentication middleware, so that you can keep your greeting from being seen by just anyone.

Let’s use HTTP authentication, which also can mystify people a bit. HTTP authentication is fairly simple:

  • When authentication is requires, we give a 401 Authentication Required status with a WWW-Authenticate: Basic realm="This Realm" header
  • The client then sends back a header Authorization: Basic encoded_info
  • The “encoded_info” is a base-64 encoded version of username:password

So how does this work? Well, we’re writing “middleware”, which means we’ll typically pass the request on to another application. We could change the request, or change the response, but in this case sometimes we won’t pass the request on (like, when we need to give that 401 response).

To give an example of a really really simple middleware, here’s one that capitalizes the response:

class Capitalizer(object):

    # We generally pass in the application to be wrapped to
    # the middleware constructor:
    def __init__(self, wrap_app):
        self.wrap_app = wrap_app

    def __call__(self, environ, start_response):
        # We call the application we are wrapping with the
        # same arguments we get...
        response_iter = self.wrap_app(environ, start_response)
        # then change the response...
        response_string = ''.join(response_iter)
        return [response_string.upper()]

Techically this isn’t quite right, because there there’s two ways to return the response body, but we’re skimming bits. paste.wsgilib.intercept_output is a somewhat more thorough implementation of this.

Note

This, like a lot of parts of this (now fairly old) tutorial is better, more thorough, and easier using WebOb. This particular example looks like:

from webob import Request

class Capitalizer(object):
    def __init__(self, app):
        self.app = app
    def __call__(self, environ, start_response):
        req = Request(environ)
        resp = req.get_response(self.app)
        resp.body = resp.body.upper()
        return resp(environ, start_response)

So here’s some code that does something useful, authentication:

class AuthMiddleware(object):

    def __init__(self, wrap_app):
        self.wrap_app = wrap_app

    def __call__(self, environ, start_response):
        if not self.authorized(environ.get('HTTP_AUTHORIZATION')):
            # Essentially self.auth_required is a WSGI application
            # that only knows how to respond with 401...
            return self.auth_required(environ, start_response)
        # But if everything is okay, then pass everything through
        # to the application we are wrapping...
        return self.wrap_app(environ, start_response)

    def authorized(self, auth_header):
        if not auth_header:
            # If they didn't give a header, they better login...
            return False
        # .split(None, 1) means split in two parts on whitespace:
        auth_type, encoded_info = auth_header.split(None, 1)
        assert auth_type.lower() == 'basic'
        unencoded_info = encoded_info.decode('base64')
        username, password = unencoded_info.split(':', 1)
        return self.check_password(username, password)

    def check_password(self, username, password):
        # Not very high security authentication...
        return username == password

    def auth_required(self, environ, start_response):
        start_response('401 Authentication Required',
            [('Content-type', 'text/html'),
             ('WWW-Authenticate', 'Basic realm="this realm"')])
        return ["""
        <html>
         <head><title>Authentication Required</title></head>
         <body>
          <h1>Authentication Required</h1>
          If you can't get in, then stay out.
         </body>
        </html>"""]

Note

Again, here’s the same thing with WebOb:

from webob import Request, Response

class AuthMiddleware(object):
    def __init__(self, app):
        self.app = app
    def __call__(self, environ, start_response):
        req = Request(environ)
        if not self.authorized(req.headers['authorization']):
            resp = self.auth_required(req)
        else:
            resp = self.app
        return resp(environ, start_response)
    def authorized(self, header):
        if not header:
            return False
        auth_type, encoded = header.split(None, 1)
        if not auth_type.lower() == 'basic':
            return False
        username, password = encoded.decode('base64').split(':', 1)
        return self.check_password(username, password)
 def check_password(self, username, password):
     return username == password
 def auth_required(self, req):
     return Response(status=401, headers={'WWW-Authenticate': 'Basic realm="this realm"'},
                     body="""\
     <html>
      <head><title>Authentication Required</title></head>
      <body>
       <h1>Authentication Required</h1>
       If you can't get in, then stay out.
      </body>
     </html>""")

So, how do we use this?

app = ObjectPublisher(Root())
wrapped_app = AuthMiddleware(app)

if __name__ == '__main__':
    from paste import httpserver
    httpserver.serve(wrapped_app, host='127.0.0.1', port='8080')

Now you have middleware! Hurrah!

Give Me More Middleware!¶

It’s even easier to use other people’s middleware than to make your own, because then you don’t have to program. If you’ve been following along, you’ve probably encountered a few exceptions, and have to look at the console to see the exception reports. Let’s make that a little easier, and show the exceptions in the browser...

app = ObjectPublisher(Root())
wrapped_app = AuthMiddleware(app)
from paste.exceptions.errormiddleware import ErrorMiddleware
exc_wrapped_app = ErrorMiddleware(wrapped_app)

Easy! But let’s make it more fancy...

app = ObjectPublisher(Root())
wrapped_app = AuthMiddleware(app)
from paste.evalexception import EvalException
exc_wrapped_app = EvalException(wrapped_app)

So go make an error now. And hit the little +’s. And type stuff in to the boxes.

Conclusion¶

Now that you’ve created your framework and application (I’m sure it’s much nicer than the one I’ve given so far). You might keep writing it (many people have so far), but even if you don’t you should be able to recognize these components in other frameworks now, and you’ll have a better understanding how they probably work under the covers.

Also check out the version of this tutorial written using WebOb. That tutorial includes things like testing and pattern-matching dispatch (instead of object publishing).

Paste-1.7.5.1/docs/developer-features.txt0000664000175000017500000001062411354703364016650 0ustar ianbianbFeatures ======== Testing ------- * A fixture for testing WSGI applications conveniently and in-process, in :class:`paste.fixture.TestApp` * A fixture for testing command-line applications, also in :class:`paste.fixture.TestFileEnvironment` * Check components for WSGI-compliance in :mod:`paste.lint` * Check filesystem changes, with :mod:`paste.debug.fsdiff` Server ------ * A threaded HTTP server in :mod:`paste.httpserver` * A tool for seeing and killing errant threads in the HTTP server, in :mod:`paste.debug.watchthreads` Dispatching ----------- * Chain and cascade WSGI applications (returning the first non-error response) in :mod:`paste.cascade` * Dispatch to several WSGI applications based on URL prefixes, in :mod:`paste.urlmap` * Allow applications to make subrequests and forward requests internally, in :mod:`paste.recursive` * Redirect error pages (e.g., 404 Not Found) to custom error pages, in :mod:`paste.errordocument`. Web Application --------------- * Easily deal with incoming requests and sending a response in :mod:`paste.wsgiwrappers` * Work directly with the WSGI environment in :mod:`paste.request` * Run CGI programs as WSGI applications in :mod:`paste.cgiapp` * Traverse files and load WSGI applications from ``.py`` files (or static files), in :mod:`paste.urlparser` * Serve static directories of files, also in :mod:`paste.urlparser`; also serve using the Setuptools ``pkg_resources`` resource API. * Proxy to other servers, treating external HTTP servers as WSGI applications, in :mod:`paste.proxy`. * Serve files (with support for ``If-Modified-Since``, etc) in :mod:`paste.fileapp` Tools ----- * Catch HTTP-related exceptions (e.g., ``HTTPNotFound``) and turn them into proper responses in :mod:`paste.httpexceptions` * Manage HTTP header fields with :mod:`paste.httpheaders` * Handle authentication/identification of requests in :mod:`paste.auth` * Create sessions in :mod:`paste.session` and :mod:`paste.flup_session` * Gzip responses in :mod:`paste.gzipper` * A wide variety of routines for manipulating WSGI requests and producing responses, in :mod:`paste.request`, :mod:`paste.response` and :mod:`paste.wsgilib`. * Create Apache-style logs in :mod:`paste.translogger` * Handy request and response wrappers in :mod:`paste.wsgiwrappers` * Handling of request-local module globals sanely in :mod:`paste.registry` Authentication -------------- * Authentication using cookies in :mod:`paste.auth.cookie` and :mod:`paste.auth.auth_tkt`; login form in :mod:`paste.auth.form` * Authentication using `OpenID `_ in :mod:`paste.auth.open_id`, using `CAS `_ in :mod:`paste.auth.cas` * HTTP authentication in :mod:`paste.auth.basic` and :mod:`paste.auth.digest` * Dispatch to different authentication methods based on User-Agent, in :mod:`paste.auth.multi` * Grant roles based on IP addresses, in :mod:`paste.auth.grantip` Debugging Filters ----------------- * Catch (optionally email) errors with extended tracebacks (using Zope/ZPT conventions) in :mod:`paste.exceptions` * During debugging, show tracebacks with information about each stack frame, including an interactive prompt that runs in the individual stack frames, in :mod:`paste.evalexception`. * Catch errors presenting a `cgitb `_-based output, in :mod:`paste.cgitb_catcher`. * Profile each request and append profiling information to the HTML, in :mod:`paste.debug.profile` * Capture ``print`` output and present it in the browser for debugging, in :mod:`paste.debug.prints` * Validate all HTML output from applications using the `WDG Validator `_, appending any errors or warnings to the page, in :mod:`paste.debug.wdg_validator` Other Tools ----------- * A file monitor to allow restarting the server when files have been updated (for automatic restarting when editing code) in :mod:`paste.reloader` * A class for generating and traversing URLs, and creating associated HTML code, in :mod:`paste.url` * A small templating language (for internal use) in :mod:`paste.util.template` * A class to help with loops in templates, in :mod:`paste.util.looper` * Import modules and objects given a string, in :mod:`paste.util.import_string` * Ordered dictionary that can have multiple values with the same key, in :mod:`paste.util.multidict` Paste-1.7.5.1/docs/do-it-yourself-framework.txt0000664000175000017500000004550111355170661017725 0ustar ianbianbA Do-It-Yourself Framework ++++++++++++++++++++++++++ :author: Ian Bicking :revision: $Rev$ :date: $LastChangedDate$ This tutorial has been translated `into Portuguese `_. A newer version of this article is available `using WebOb `_. .. contents:: .. comments: Explain SCRIPT_NAME/PATH_INFO better Introduction and Audience ========================= This short tutorial is meant to teach you a little about WSGI, and as an example a bit about the architecture that Paste has enabled and encourages. This isn't an introduction to all the parts of Paste -- in fact, we'll only use a few, and explain each part. This isn't to encourage everyone to go off and make their own framework (though honestly I wouldn't mind). The goal is that when you have finished reading this you feel more comfortable with some of the frameworks built using this architecture, and a little more secure that you will understand the internals if you look under the hood. What is WSGI? ============= At its simplest WSGI is an interface between web servers and web applications. We'll explain the mechanics of WSGI below, but a higher level view is to say that WSGI lets code pass around web requests in a fairly formal way. But there's more! WSGI is more than just HTTP. It might seem like it is just *barely* more than HTTP, but that little bit is important: * You pass around a CGI-like environment, which means data like ``REMOTE_USER`` (the logged-in username) can be securely passed about. * A CGI-like environment can be passed around with more context -- specifically instead of just one path you two: ``SCRIPT_NAME`` (how we got here) and ``PATH_INFO`` (what we have left). * You can -- and often should -- put your own extensions into the WSGI environment. This allows for callbacks, extra information, arbitrary Python objects, or whatever you want. These are things you can't put in custom HTTP headers. This means that WSGI can be used not just between a web server an an application, but can be used at all levels for communication. This allows web applications to become more like libraries -- well encapsulated and reusable, but still with rich reusable functionality. Writing a WSGI Application ========================== The first part is about how to use `WSGI `_ at its most basic. You can read the spec, but I'll do a very brief summary: * You will be writing a *WSGI application*. That's an object that responds to requests. An application is just a callable object (like a function) that takes two arguments: ``environ`` and ``start_response``. * The environment looks a lot like a CGI environment, with keys like ``REQUEST_METHOD``, ``HTTP_HOST``, etc. * The environment also has some special keys like ``wsgi.input`` (the input stream, like the body of a POST request). * ``start_response`` is a function that starts the response -- you give the status and headers here. * Lastly the application returns an iterator with the body response (commonly this is just a list of strings, or just a list containing one string that is the entire body.) So, here's a simple application:: def app(environ, start_response): start_response('200 OK', [('content-type', 'text/html')]) return ['Hello world!'] Well... that's unsatisfying. Sure, you can imagine what it does, but you can't exactly point your web browser at it. There's other cleaner ways to do this, but this tutorial isn't about *clean* it's about *easy-to-understand*. So just add this to the bottom of your file:: if __name__ == '__main__': from paste import httpserver httpserver.serve(app, host='127.0.0.1', port='8080') Now visit http://localhost:8080 and you should see your new app. If you want to understand how a WSGI server works, I'd recommend looking at the `CGI WSGI server `_ in the WSGI spec. An Interactive App ------------------ That last app wasn't very interesting. Let's at least make it interactive. To do that we'll give a form, and then parse the form fields:: from paste.request import parse_formvars def app(environ, start_response): fields = parse_formvars(environ) if environ['REQUEST_METHOD'] == 'POST': start_response('200 OK', [('content-type', 'text/html')]) return ['Hello, ', fields['name'], '!'] else: start_response('200 OK', [('content-type', 'text/html')]) return ['
Name:
'] The ``parse_formvars`` function just takes the WSGI environment and calls the `cgi `_ module (the ``FieldStorage`` class) and turns that into a MultiDict. Now For a Framework =================== Now, this probably feels a bit crude. After all, we're testing for things like REQUEST_METHOD to handle more than one thing, and it's unclear how you can have more than one page. We want to build a framework, which is just a kind of generic application. In this tutorial we'll implement an *object publisher*, which is something you may have seen in Zope, Quixote, or CherryPy. Object Publishing ----------------- In a typical Python object publisher you translate ``/`` to ``.``. So ``/articles/view?id=5`` turns into ``root.articles.view(id=5)``. We have to start with some root object, of course, which we'll pass in... :: class ObjectPublisher(object): def __init__(self, root): self.root = root def __call__(self, environ, start_response): ... app = ObjectPublisher(my_root_object) We override ``__call__`` to make instances of ``ObjectPublisher`` callable objects, just like a function, and just like WSGI applications. Now all we have to do is translate that ``environ`` into the thing we are publishing, then call that thing, then turn the response into what WSGI wants. The Path -------- WSGI puts the requested path into two variables: ``SCRIPT_NAME`` and ``PATH_INFO``. ``SCRIPT_NAME`` is everything that was used up *getting here*. ``PATH_INFO`` is everything left over -- it's the part the framework should be using to find the object. If you put the two back together, you get the full path used to get to where we are right now; this is very useful for generating correct URLs, and we'll make sure we preserve this. So here's how we might implement ``__call__``:: def __call__(self, environ, start_response): fields = parse_formvars(environ) obj = self.find_object(self.root, environ) response_body = obj(**fields.mixed()) start_response('200 OK', [('content-type', 'text/html')]) return [response_body] def find_object(self, obj, environ): path_info = environ.get('PATH_INFO', '') if not path_info or path_info == '/': # We've arrived! return obj # PATH_INFO always starts with a /, so we'll get rid of it: path_info = path_info.lstrip('/') # Then split the path into the "next" chunk, and everything # after it ("rest"): parts = path_info.split('/', 1) next = parts[0] if len(parts) == 1: rest = '' else: rest = '/' + parts[1] # Hide private methods/attributes: assert not next.startswith('_') # Now we get the attribute; getattr(a, 'b') is equivalent # to a.b... next_obj = getattr(obj, next) # Now fix up SCRIPT_NAME and PATH_INFO... environ['SCRIPT_NAME'] += '/' + next environ['PATH_INFO'] = rest # and now parse the remaining part of the URL... return self.find_object(next_obj, environ) And that's it, we've got a framework. Taking It For a Ride -------------------- Now, let's write a little application. Put that ``ObjectPublisher`` class into a module ``objectpub``:: from objectpub import ObjectPublisher class Root(object): # The "index" method: def __call__(self): return '''
Name:
''' def welcome(self, name): return 'Hello %s!' % name app = ObjectPublisher(Root()) if __name__ == '__main__': from paste import httpserver httpserver.serve(app, host='127.0.0.1', port='8080') Alright, done! Oh, wait. There's still some big missing features, like how do you set headers? And instead of giving ``404 Not Found`` responses in some places, you'll just get an attribute error. We'll fix those up in a later installment... Give Me More! ------------- You'll notice some things are missing here. Most specifically, there's no way to set the output headers, and the information on the request is a little slim. :: # This is just a dictionary-like object that has case- # insensitive keys: from paste.response import HeaderDict class Request(object): def __init__(self, environ): self.environ = environ self.fields = parse_formvars(environ) class Response(object): def __init__(self): self.headers = HeaderDict( {'content-type': 'text/html'}) Now I'll teach you a little trick. We don't want to change the signature of the methods. But we can't put the request and response objects in normal global variables, because we want to be thread-friendly, and all threads see the same global variables (even if they are processing different requests). But Python 2.4 introduced a concept of "thread-local values". That's a value that just this one thread can see. This is in the `threading.local `_ object. When you create an instance of ``local`` any attributes you set on that object can only be seen by the thread you set them in. So we'll attach the request and response objects here. So, let's remind ourselves of what the ``__call__`` function looked like:: class ObjectPublisher(object): ... def __call__(self, environ, start_response): fields = parse_formvars(environ) obj = self.find_object(self.root, environ) response_body = obj(**fields.mixed()) start_response('200 OK', [('content-type', 'text/html')]) return [response_body] Lets's update that:: import threading webinfo = threading.local() class ObjectPublisher(object): ... def __call__(self, environ, start_response): webinfo.request = Request(environ) webinfo.response = Response() obj = self.find_object(self.root, environ) response_body = obj(**dict(webinfo.request.fields)) start_response('200 OK', webinfo.response.headers.items()) return [response_body] Now in our method we might do:: class Root: def rss(self): webinfo.response.headers['content-type'] = 'text/xml' ... If we were being fancier we would do things like handle `cookies `_ in these objects. But we aren't going to do that now. You have a framework, be happy! WSGI Middleware =============== `Middleware `_ is where people get a little intimidated by WSGI and Paste. What is middleware? Middleware is software that serves as an intermediary. So lets write one. We'll write an authentication middleware, so that you can keep your greeting from being seen by just anyone. Let's use HTTP authentication, which also can mystify people a bit. HTTP authentication is fairly simple: * When authentication is requires, we give a ``401 Authentication Required`` status with a ``WWW-Authenticate: Basic realm="This Realm"`` header * The client then sends back a header ``Authorization: Basic encoded_info`` * The "encoded_info" is a base-64 encoded version of ``username:password`` So how does this work? Well, we're writing "middleware", which means we'll typically pass the request on to another application. We could change the request, or change the response, but in this case sometimes we *won't* pass the request on (like, when we need to give that 401 response). To give an example of a really really simple middleware, here's one that capitalizes the response:: class Capitalizer(object): # We generally pass in the application to be wrapped to # the middleware constructor: def __init__(self, wrap_app): self.wrap_app = wrap_app def __call__(self, environ, start_response): # We call the application we are wrapping with the # same arguments we get... response_iter = self.wrap_app(environ, start_response) # then change the response... response_string = ''.join(response_iter) return [response_string.upper()] Techically this isn't quite right, because there there's two ways to return the response body, but we're skimming bits. `paste.wsgilib.intercept_output `_ is a somewhat more thorough implementation of this. .. note:: This, like a lot of parts of this (now fairly old) tutorial is better, more thorough, and easier using `WebOb `_. This particular example looks like:: from webob import Request class Capitalizer(object): def __init__(self, app): self.app = app def __call__(self, environ, start_response): req = Request(environ) resp = req.get_response(self.app) resp.body = resp.body.upper() return resp(environ, start_response) So here's some code that does something useful, authentication:: class AuthMiddleware(object): def __init__(self, wrap_app): self.wrap_app = wrap_app def __call__(self, environ, start_response): if not self.authorized(environ.get('HTTP_AUTHORIZATION')): # Essentially self.auth_required is a WSGI application # that only knows how to respond with 401... return self.auth_required(environ, start_response) # But if everything is okay, then pass everything through # to the application we are wrapping... return self.wrap_app(environ, start_response) def authorized(self, auth_header): if not auth_header: # If they didn't give a header, they better login... return False # .split(None, 1) means split in two parts on whitespace: auth_type, encoded_info = auth_header.split(None, 1) assert auth_type.lower() == 'basic' unencoded_info = encoded_info.decode('base64') username, password = unencoded_info.split(':', 1) return self.check_password(username, password) def check_password(self, username, password): # Not very high security authentication... return username == password def auth_required(self, environ, start_response): start_response('401 Authentication Required', [('Content-type', 'text/html'), ('WWW-Authenticate', 'Basic realm="this realm"')]) return [""" Authentication Required

Authentication Required

If you can't get in, then stay out. """] .. note:: Again, here's the same thing with WebOb:: from webob import Request, Response class AuthMiddleware(object): def __init__(self, app): self.app = app def __call__(self, environ, start_response): req = Request(environ) if not self.authorized(req.headers['authorization']): resp = self.auth_required(req) else: resp = self.app return resp(environ, start_response) def authorized(self, header): if not header: return False auth_type, encoded = header.split(None, 1) if not auth_type.lower() == 'basic': return False username, password = encoded.decode('base64').split(':', 1) return self.check_password(username, password) def check_password(self, username, password): return username == password def auth_required(self, req): return Response(status=401, headers={'WWW-Authenticate': 'Basic realm="this realm"'}, body="""\ Authentication Required

Authentication Required

If you can't get in, then stay out. """) So, how do we use this? :: app = ObjectPublisher(Root()) wrapped_app = AuthMiddleware(app) if __name__ == '__main__': from paste import httpserver httpserver.serve(wrapped_app, host='127.0.0.1', port='8080') Now you have middleware! Hurrah! Give Me More Middleware! ------------------------ It's even easier to use other people's middleware than to make your own, because then you don't have to program. If you've been following along, you've probably encountered a few exceptions, and have to look at the console to see the exception reports. Let's make that a little easier, and show the exceptions in the browser... :: app = ObjectPublisher(Root()) wrapped_app = AuthMiddleware(app) from paste.exceptions.errormiddleware import ErrorMiddleware exc_wrapped_app = ErrorMiddleware(wrapped_app) Easy! But let's make it *more* fancy... :: app = ObjectPublisher(Root()) wrapped_app = AuthMiddleware(app) from paste.evalexception import EvalException exc_wrapped_app = EvalException(wrapped_app) So go make an error now. And hit the little +'s. And type stuff in to the boxes. Conclusion ========== Now that you've created your framework and application (I'm sure it's much nicer than the one I've given so far). You might keep writing it (many people have so far), but even if you don't you should be able to recognize these components in other frameworks now, and you'll have a better understanding how they probably work under the covers. Also check out the version of this tutorial written `using WebOb `_. That tutorial includes things like **testing** and **pattern-matching dispatch** (instead of object publishing). Paste-1.7.5.1/docs/future.txt0000664000175000017500000000617311355172150014357 0ustar ianbianbThe Future Of Paste =================== Introduction ------------ 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. The future of these pieces is to split them into independent packages, and refactor the internal Paste dependencies to rely instead on WebOb. Already Extracted ----------------- paste.fixture: WebTest ScriptTest paste.lint: wsgiref.validate paste.exceptions and paste.evalexception: WebError paste.util.template: Tempita To Be Separated --------------- paste.httpserver and paste.debug.watchthreads: Not sure what to call this. paste.cascade and paste.errordocuments: Not sure; Ben has an implementation of errordocuments in ``pylons.middleware.StatusCodeRedirect`` paste.urlmap, paste.deploy.config.PrefixMiddleware: In... some routing thing? Together with the previous package? paste.proxy: WSGIProxy (needs lots of cleanup though) paste.fileapp, paste.urlparser.StaticURLParser, paste.urlparser.PkgResourcesParser: In some new file-serving package. paste.cgiapp, wphp.fcgi_app: Some proxyish app... maybe WSGIProxy? paste.translogger, paste.debug.prints, paste.util.threadedprint, wsgifilter.proxyapp.DebugHeaders: Some... other place. Something loggy. paste.registry, paste.config: Not sure. Alberto Valverde expressed interest in splitting out paste.registry. paste.cgitb_catcher: Move to WebError? Not sure if it matters. For some reason people use this, though. To Deprecate ------------ (In that, I won't extract these anywhere; I'm not going to do any big deletes anytime soon, though) paste.recursive Better to do it manually (with webob.Request.get_response) paste.wsgiwrappers, paste.request, paste.response, paste.wsgilib, paste.httpheaders, paste.httpexceptions: All the functionality is already in WebOb. paste.urlparser.URLParser: Really this is tied to paste.webkit more than anything. paste.auth.*: Well, these all need to be refactored, and replacements exist in AuthKit and repoze.who. Some pieces might still have utility. paste.debug.profile: I think repoze.profile supersedes this. paste.debug.wdg_validator: It could get reimplemented with more options for validators, but I'm not really that interested at the moment. The code is nothing fancy. paste.transaction: More general in repoze.tm paste.url: No one uses this Undecided --------- paste.debug.fsdiff: Maybe ScriptTest? paste.session: 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. paste.gzipper: I'm a little uncomfortable with this in concept. It's largely in WebOb right now, but not as middleware. paste.reloader: Maybe this should be moved to paste.script (i.e., paster serve) paste.debug.debugapp, paste.script.testapp: Alongside other debugging tools, I guess paste.progress: Not sure this works. Paste-1.7.5.1/paste/0000775000175000017500000000000011354703364012467 5ustar ianbianbPaste-1.7.5.1/paste/lint.py0000664000175000017500000003522711354703364014020 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php # Also licenced under the Apache License, 2.0: http://opensource.org/licenses/apache2.0.php # Licensed to PSF under a Contributor Agreement """ Middleware to check for obedience to the WSGI specification. Some of the things this checks: * Signature of the application and start_response (including that keyword arguments are not used). * Environment checks: - Environment is a dictionary (and not a subclass). - That all the required keys are in the environment: REQUEST_METHOD, SERVER_NAME, SERVER_PORT, wsgi.version, wsgi.input, wsgi.errors, wsgi.multithread, wsgi.multiprocess, wsgi.run_once - That HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH are not in the environment (these headers should appear as CONTENT_LENGTH and CONTENT_TYPE). - Warns if QUERY_STRING is missing, as the cgi module acts unpredictably in that case. - That CGI-style variables (that don't contain a .) have (non-unicode) string values - That wsgi.version is a tuple - That wsgi.url_scheme is 'http' or 'https' (@@: is this too restrictive?) - Warns if the REQUEST_METHOD is not known (@@: probably too restrictive). - That SCRIPT_NAME and PATH_INFO are empty or start with / - That at least one of SCRIPT_NAME or PATH_INFO are set. - That CONTENT_LENGTH is a positive integer. - That SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should be '/'). - That wsgi.input has the methods read, readline, readlines, and __iter__ - That wsgi.errors has the methods flush, write, writelines * The status is a string, contains a space, starts with an integer, and that integer is in range (> 100). * That the headers is a list (not a subclass, not another kind of sequence). * That the items of the headers are tuples of strings. * That there is no 'status' header (that is used in CGI, but not in WSGI). * That the headers don't contain newlines or colons, end in _ or -, or contain characters codes below 037. * That Content-Type is given if there is content (CGI often has a default content type, but WSGI does not). * That no Content-Type is given when there is no content (@@: is this too restrictive?) * That the exc_info argument to start_response is a tuple or None. * That all calls to the writer are with strings, and no other methods on the writer are accessed. * That wsgi.input is used properly: - .read() is called with zero or one argument - That it returns a string - That readline, readlines, and __iter__ return strings - That .close() is not called - No other methods are provided * That wsgi.errors is used properly: - .write() and .writelines() is called with a string - That .close() is not called, and no other methods are provided. * The response iterator: - That it is not a string (it should be a list of a single string; a string will work, but perform horribly). - That .next() returns a string - That the iterator is not iterated over until start_response has been called (that can signal either a server or application error). - That .close() is called (doesn't raise exception, only prints to sys.stderr, because we only know it isn't called when the object is garbage collected). """ import re import sys from types import DictType, StringType, TupleType, ListType import warnings header_re = re.compile(r'^[a-zA-Z][a-zA-Z0-9\-_]*$') bad_header_value_re = re.compile(r'[\000-\037]') class WSGIWarning(Warning): """ Raised in response to WSGI-spec-related warnings """ def middleware(application, global_conf=None): """ When applied between a WSGI server and a WSGI application, this middleware will check for WSGI compliancy on a number of levels. This middleware does not modify the request or response in any way, but will throw an AssertionError if anything seems off (except for a failure to close the application iterator, which will be printed to stderr -- there's no way to throw an exception at that point). """ def lint_app(*args, **kw): assert len(args) == 2, "Two arguments required" assert not kw, "No keyword arguments allowed" environ, start_response = args check_environ(environ) # We use this to check if the application returns without # calling start_response: start_response_started = [] def start_response_wrapper(*args, **kw): assert len(args) == 2 or len(args) == 3, ( "Invalid number of arguments: %s" % args) assert not kw, "No keyword arguments allowed" status = args[0] headers = args[1] if len(args) == 3: exc_info = args[2] else: exc_info = None check_status(status) check_headers(headers) check_content_type(status, headers) check_exc_info(exc_info) start_response_started.append(None) return WriteWrapper(start_response(*args)) environ['wsgi.input'] = InputWrapper(environ['wsgi.input']) environ['wsgi.errors'] = ErrorWrapper(environ['wsgi.errors']) iterator = application(environ, start_response_wrapper) assert iterator is not None and iterator != False, ( "The application must return an iterator, if only an empty list") check_iterator(iterator) return IteratorWrapper(iterator, start_response_started) return lint_app class InputWrapper(object): def __init__(self, wsgi_input): self.input = wsgi_input def read(self, *args): assert len(args) <= 1 v = self.input.read(*args) assert type(v) is type("") return v def readline(self, *args): v = self.input.readline(*args) assert type(v) is type("") return v def readlines(self, *args): assert len(args) <= 1 lines = self.input.readlines(*args) assert type(lines) is type([]) for line in lines: assert type(line) is type("") return lines def __iter__(self): while 1: line = self.readline() if not line: return yield line def close(self): assert 0, "input.close() must not be called" class ErrorWrapper(object): def __init__(self, wsgi_errors): self.errors = wsgi_errors def write(self, s): assert type(s) is type("") self.errors.write(s) def flush(self): self.errors.flush() def writelines(self, seq): for line in seq: self.write(line) def close(self): assert 0, "errors.close() must not be called" class WriteWrapper(object): def __init__(self, wsgi_writer): self.writer = wsgi_writer def __call__(self, s): assert type(s) is type("") self.writer(s) class PartialIteratorWrapper(object): def __init__(self, wsgi_iterator): self.iterator = wsgi_iterator def __iter__(self): # We want to make sure __iter__ is called return IteratorWrapper(self.iterator) class IteratorWrapper(object): def __init__(self, wsgi_iterator, check_start_response): self.original_iterator = wsgi_iterator self.iterator = iter(wsgi_iterator) self.closed = False self.check_start_response = check_start_response def __iter__(self): return self def next(self): assert not self.closed, ( "Iterator read after closed") v = self.iterator.next() if self.check_start_response is not None: assert self.check_start_response, ( "The application returns and we started iterating over its body, but start_response has not yet been called") self.check_start_response = None return v def close(self): self.closed = True if hasattr(self.original_iterator, 'close'): self.original_iterator.close() def __del__(self): if not self.closed: sys.stderr.write( "Iterator garbage collected without being closed") assert self.closed, ( "Iterator garbage collected without being closed") def check_environ(environ): assert type(environ) is DictType, ( "Environment is not of the right type: %r (environment: %r)" % (type(environ), environ)) for key in ['REQUEST_METHOD', 'SERVER_NAME', 'SERVER_PORT', 'wsgi.version', 'wsgi.input', 'wsgi.errors', 'wsgi.multithread', 'wsgi.multiprocess', 'wsgi.run_once']: assert key in environ, ( "Environment missing required key: %r" % key) for key in ['HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH']: assert key not in environ, ( "Environment should not have the key: %s " "(use %s instead)" % (key, key[5:])) if 'QUERY_STRING' not in environ: warnings.warn( 'QUERY_STRING is not in the WSGI environment; the cgi ' 'module will use sys.argv when this variable is missing, ' 'so application errors are more likely', WSGIWarning) for key in environ.keys(): if '.' in key: # Extension, we don't care about its type continue assert type(environ[key]) is StringType, ( "Environmental variable %s is not a string: %r (value: %r)" % (key, type(environ[key]), environ[key])) assert type(environ['wsgi.version']) is TupleType, ( "wsgi.version should be a tuple (%r)" % environ['wsgi.version']) assert environ['wsgi.url_scheme'] in ('http', 'https'), ( "wsgi.url_scheme unknown: %r" % environ['wsgi.url_scheme']) check_input(environ['wsgi.input']) check_errors(environ['wsgi.errors']) # @@: these need filling out: if environ['REQUEST_METHOD'] not in ( 'GET', 'HEAD', 'POST', 'OPTIONS','PUT','DELETE','TRACE'): warnings.warn( "Unknown REQUEST_METHOD: %r" % environ['REQUEST_METHOD'], WSGIWarning) assert (not environ.get('SCRIPT_NAME') or environ['SCRIPT_NAME'].startswith('/')), ( "SCRIPT_NAME doesn't start with /: %r" % environ['SCRIPT_NAME']) assert (not environ.get('PATH_INFO') or environ['PATH_INFO'].startswith('/')), ( "PATH_INFO doesn't start with /: %r" % environ['PATH_INFO']) if environ.get('CONTENT_LENGTH'): assert int(environ['CONTENT_LENGTH']) >= 0, ( "Invalid CONTENT_LENGTH: %r" % environ['CONTENT_LENGTH']) if not environ.get('SCRIPT_NAME'): assert environ.has_key('PATH_INFO'), ( "One of SCRIPT_NAME or PATH_INFO are required (PATH_INFO " "should at least be '/' if SCRIPT_NAME is empty)") assert environ.get('SCRIPT_NAME') != '/', ( "SCRIPT_NAME cannot be '/'; it should instead be '', and " "PATH_INFO should be '/'") def check_input(wsgi_input): for attr in ['read', 'readline', 'readlines', '__iter__']: assert hasattr(wsgi_input, attr), ( "wsgi.input (%r) doesn't have the attribute %s" % (wsgi_input, attr)) def check_errors(wsgi_errors): for attr in ['flush', 'write', 'writelines']: assert hasattr(wsgi_errors, attr), ( "wsgi.errors (%r) doesn't have the attribute %s" % (wsgi_errors, attr)) def check_status(status): assert type(status) is StringType, ( "Status must be a string (not %r)" % status) # Implicitly check that we can turn it into an integer: status_code = status.split(None, 1)[0] assert len(status_code) == 3, ( "Status codes must be three characters: %r" % status_code) status_int = int(status_code) assert status_int >= 100, "Status code is invalid: %r" % status_int if len(status) < 4 or status[3] != ' ': warnings.warn( "The status string (%r) should be a three-digit integer " "followed by a single space and a status explanation" % status, WSGIWarning) def check_headers(headers): assert type(headers) is ListType, ( "Headers (%r) must be of type list: %r" % (headers, type(headers))) header_names = {} for item in headers: assert type(item) is TupleType, ( "Individual headers (%r) must be of type tuple: %r" % (item, type(item))) assert len(item) == 2 name, value = item assert name.lower() != 'status', ( "The Status header cannot be used; it conflicts with CGI " "script, and HTTP status is not given through headers " "(value: %r)." % value) header_names[name.lower()] = None assert '\n' not in name and ':' not in name, ( "Header names may not contain ':' or '\\n': %r" % name) assert header_re.search(name), "Bad header name: %r" % name assert not name.endswith('-') and not name.endswith('_'), ( "Names may not end in '-' or '_': %r" % name) assert not bad_header_value_re.search(value), ( "Bad header value: %r (bad char: %r)" % (value, bad_header_value_re.search(value).group(0))) def check_content_type(status, headers): code = int(status.split(None, 1)[0]) # @@: need one more person to verify this interpretation of RFC 2616 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html NO_MESSAGE_BODY = (204, 304) NO_MESSAGE_TYPE = (204, 304) for name, value in headers: if name.lower() == 'content-type': if code not in NO_MESSAGE_TYPE: return assert 0, (("Content-Type header found in a %s response, " "which must not return content.") % code) if code not in NO_MESSAGE_BODY: assert 0, "No Content-Type header found in headers (%s)" % headers def check_exc_info(exc_info): assert exc_info is None or type(exc_info) is type(()), ( "exc_info (%r) is not a tuple: %r" % (exc_info, type(exc_info))) # More exc_info checks? def check_iterator(iterator): # Technically a string is legal, which is why it's a really bad # idea, because it may cause the response to be returned # character-by-character assert not isinstance(iterator, str), ( "You should not return a string as your application iterator, " "instead return a single-item list containing that string.") def make_middleware(application, global_conf): # @@: global_conf should be taken out of the middleware function, # and isolated here return middleware(application) make_middleware.__doc__ = __doc__ __all__ = ['middleware', 'make_middleware'] Paste-1.7.5.1/paste/response.py0000664000175000017500000001675311437572246014720 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """Routines to generate WSGI responses""" ############################################################ ## Headers ############################################################ import warnings class HeaderDict(dict): """ This represents response headers. It handles the headers as a dictionary, with case-insensitive keys. Also there is an ``.add(key, value)`` method, which sets the key, or adds the value to the current value (turning it into a list if necessary). For passing to WSGI there is a ``.headeritems()`` method which is like ``.items()`` but unpacks value that are lists. It also handles encoding -- all headers are encoded in ASCII (if they are unicode). @@: Should that encoding be ISO-8859-1 or UTF-8? I'm not sure what the spec says. """ def __getitem__(self, key): return dict.__getitem__(self, self.normalize(key)) def __setitem__(self, key, value): dict.__setitem__(self, self.normalize(key), value) def __delitem__(self, key): dict.__delitem__(self, self.normalize(key)) def __contains__(self, key): return dict.__contains__(self, self.normalize(key)) has_key = __contains__ def get(self, key, failobj=None): return dict.get(self, self.normalize(key), failobj) def setdefault(self, key, failobj=None): return dict.setdefault(self, self.normalize(key), failobj) def pop(self, key, *args): return dict.pop(self, self.normalize(key), *args) def update(self, other): for key in other: self[self.normalize(key)] = other[key] def normalize(self, key): return str(key).lower().strip() def add(self, key, value): key = self.normalize(key) if key in self: if isinstance(self[key], list): self[key].append(value) else: self[key] = [self[key], value] else: self[key] = value def headeritems(self): result = [] for key, value in self.items(): if isinstance(value, list): for v in value: result.append((key, str(v))) else: result.append((key, str(value))) return result #@classmethod def fromlist(cls, seq): self = cls() for name, value in seq: self.add(name, value) return self fromlist = classmethod(fromlist) def has_header(headers, name): """ Is header named ``name`` present in headers? """ name = name.lower() for header, value in headers: if header.lower() == name: return True return False def header_value(headers, name): """ Returns the header's value, or None if no such header. If a header appears more than once, all the values of the headers are joined with ','. Note that this is consistent /w RFC 2616 section 4.2 which states: It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma. However, note that the original netscape usage of 'Set-Cookie', especially in MSIE which contains an 'expires' date will is not compatible with this particular concatination method. """ name = name.lower() result = [value for header, value in headers if header.lower() == name] if result: return ','.join(result) else: return None def remove_header(headers, name): """ Removes the named header from the list of headers. Returns the value of that header, or None if no header found. If multiple headers are found, only the last one is returned. """ name = name.lower() i = 0 result = None while i < len(headers): if headers[i][0].lower() == name: result = headers[i][1] del headers[i] continue i += 1 return result def replace_header(headers, name, value): """ Updates the headers replacing the first occurance of the given name with the value provided; asserting that no further occurances happen. Note that this is _not_ the same as remove_header and then append, as two distinct operations (del followed by an append) are not atomic in a threaded environment. Returns the previous header value for the provided name, if any. Clearly one should not use this function with ``set-cookie`` or other names that may have more than one occurance in the headers. """ name = name.lower() i = 0 result = None while i < len(headers): if headers[i][0].lower() == name: assert not result, "two values for the header '%s' found" % name result = headers[i][1] headers[i] = (name, value) i += 1 if not result: headers.append((name, value)) return result ############################################################ ## Deprecated methods ############################################################ def error_body_response(error_code, message, __warn=True): """ Returns a standard HTML response page for an HTTP error. **Note:** Deprecated """ if __warn: warnings.warn( 'wsgilib.error_body_response is deprecated; use the ' 'wsgi_application method on an HTTPException object ' 'instead', DeprecationWarning, 2) return '''\ %(error_code)s

%(error_code)s

%(message)s ''' % { 'error_code': error_code, 'message': message, } def error_response(environ, error_code, message, debug_message=None, __warn=True): """ Returns the status, headers, and body of an error response. Use like: .. code-block:: python status, headers, body = wsgilib.error_response( '301 Moved Permanently', 'Moved to %s' % (url, url)) start_response(status, headers) return [body] **Note:** Deprecated """ if __warn: warnings.warn( 'wsgilib.error_response is deprecated; use the ' 'wsgi_application method on an HTTPException object ' 'instead', DeprecationWarning, 2) if debug_message and environ.get('paste.config', {}).get('debug'): message += '\n\n' % debug_message body = error_body_response(error_code, message, __warn=False) headers = [('content-type', 'text/html'), ('content-length', str(len(body)))] return error_code, headers, body def error_response_app(error_code, message, debug_message=None, __warn=True): """ An application that emits the given error response. **Note:** Deprecated """ if __warn: warnings.warn( 'wsgilib.error_response_app is deprecated; use the ' 'wsgi_application method on an HTTPException object ' 'instead', DeprecationWarning, 2) def application(environ, start_response): status, headers, body = error_response( environ, error_code, message, debug_message=debug_message, __warn=False) start_response(status, headers) return [body] return application Paste-1.7.5.1/paste/cgitb_catcher.py0000664000175000017500000000725011354703364015626 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ WSGI middleware Captures any exceptions and prints a pretty report. See the `cgitb documentation `_ for more. """ import cgitb from cStringIO import StringIO import sys from paste.util import converters class NoDefault(object): pass class CgitbMiddleware(object): def __init__(self, app, global_conf=None, display=NoDefault, logdir=None, context=5, format="html"): self.app = app if global_conf is None: global_conf = {} if display is NoDefault: display = global_conf.get('debug') if isinstance(display, basestring): display = converters.asbool(display) self.display = display self.logdir = logdir self.context = int(context) self.format = format def __call__(self, environ, start_response): try: app_iter = self.app(environ, start_response) return self.catching_iter(app_iter, environ) except: exc_info = sys.exc_info() start_response('500 Internal Server Error', [('content-type', 'text/html')], exc_info) response = self.exception_handler(exc_info, environ) return [response] def catching_iter(self, app_iter, environ): if not app_iter: raise StopIteration error_on_close = False try: for v in app_iter: yield v if hasattr(app_iter, 'close'): error_on_close = True app_iter.close() except: response = self.exception_handler(sys.exc_info(), environ) if not error_on_close and hasattr(app_iter, 'close'): try: app_iter.close() except: close_response = self.exception_handler( sys.exc_info(), environ) response += ( '
Error in .close():
%s' % close_response) yield response def exception_handler(self, exc_info, environ): dummy_file = StringIO() hook = cgitb.Hook(file=dummy_file, display=self.display, logdir=self.logdir, context=self.context, format=self.format) hook(*exc_info) return dummy_file.getvalue() def make_cgitb_middleware(app, global_conf, display=NoDefault, logdir=None, context=5, format='html'): """ Wraps the application in the ``cgitb`` (standard library) error catcher. display: If true (or debug is set in the global configuration) then the traceback will be displayed in the browser logdir: Writes logs of all errors in that directory context: Number of lines of context to show around each line of source code """ from paste.deploy.converters import asbool if display is not NoDefault: display = asbool(display) if 'debug' in global_conf: global_conf['debug'] = asbool(global_conf['debug']) return CgitbMiddleware( app, global_conf=global_conf, display=display, logdir=logdir, context=context, format=format) Paste-1.7.5.1/paste/fileapp.py0000664000175000017500000003245011437653443014471 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php # (c) 2005 Ian Bicking, Clark C. Evans and contributors # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """ This module handles sending static content such as in-memory data or files. At this time it has cache helpers and understands the if-modified-since request header. """ import os, time, mimetypes, zipfile, tarfile from paste.httpexceptions import * from paste.httpheaders import * CACHE_SIZE = 4096 BLOCK_SIZE = 4096 * 16 __all__ = ['DataApp', 'FileApp', 'DirectoryApp', 'ArchiveStore'] class DataApp(object): """ Returns an application that will send content in a single chunk, this application has support for setting cache-control and for responding to conditional (or HEAD) requests. Constructor Arguments: ``content`` the content being sent to the client ``headers`` the headers to send /w the response The remaining ``kwargs`` correspond to headers, where the underscore is replaced with a dash. These values are only added to the headers if they are not already provided; thus, they can be used for default values. Examples include, but are not limited to: ``content_type`` ``content_encoding`` ``content_location`` ``cache_control()`` This method provides validated construction of the ``Cache-Control`` header as well as providing for automated filling out of the ``EXPIRES`` header for HTTP/1.0 clients. ``set_content()`` This method provides a mechanism to set the content after the application has been constructed. This method does things like changing ``Last-Modified`` and ``Content-Length`` headers. """ allowed_methods = ('GET', 'HEAD') def __init__(self, content, headers=None, allowed_methods=None, **kwargs): assert isinstance(headers, (type(None), list)) self.expires = None self.content = None self.content_length = None self.last_modified = 0 if allowed_methods is not None: self.allowed_methods = allowed_methods self.headers = headers or [] for (k, v) in kwargs.items(): header = get_header(k) header.update(self.headers, v) ACCEPT_RANGES.update(self.headers, bytes=True) if not CONTENT_TYPE(self.headers): CONTENT_TYPE.update(self.headers) if content is not None: self.set_content(content) def cache_control(self, **kwargs): self.expires = CACHE_CONTROL.apply(self.headers, **kwargs) or None return self def set_content(self, content, last_modified=None): assert content is not None if last_modified is None: self.last_modified = time.time() else: self.last_modified = last_modified self.content = content self.content_length = len(content) LAST_MODIFIED.update(self.headers, time=self.last_modified) return self def content_disposition(self, **kwargs): CONTENT_DISPOSITION.apply(self.headers, **kwargs) return self def __call__(self, environ, start_response): method = environ['REQUEST_METHOD'].upper() if method not in self.allowed_methods: exc = HTTPMethodNotAllowed( 'You cannot %s a file' % method, headers=[('Allow', ','.join(self.allowed_methods))]) return exc(environ, start_response) return self.get(environ, start_response) def calculate_etag(self): return '"%s-%s"' % (self.last_modified, self.content_length) def get(self, environ, start_response): headers = self.headers[:] current_etag = self.calculate_etag() ETAG.update(headers, current_etag) if self.expires is not None: EXPIRES.update(headers, delta=self.expires) try: client_etags = IF_NONE_MATCH.parse(environ) if client_etags: for etag in client_etags: if etag == current_etag or etag == '*': # horribly inefficient, n^2 performance, yuck! for head in list_headers(entity=True): head.delete(headers) start_response('304 Not Modified', headers) return [''] except HTTPBadRequest, exce: return exce.wsgi_application(environ, start_response) # If we get If-None-Match and If-Modified-Since, and # If-None-Match doesn't match, then we should not try to # figure out If-Modified-Since (which has 1-second granularity # and just isn't as accurate) if not client_etags: try: client_clock = IF_MODIFIED_SINCE.parse(environ) if client_clock >= int(self.last_modified): # horribly inefficient, n^2 performance, yuck! for head in list_headers(entity=True): head.delete(headers) start_response('304 Not Modified', headers) return [''] # empty body except HTTPBadRequest, exce: return exce.wsgi_application(environ, start_response) (lower, upper) = (0, self.content_length - 1) range = RANGE.parse(environ) if range and 'bytes' == range[0] and 1 == len(range[1]): (lower, upper) = range[1][0] upper = upper or (self.content_length - 1) if upper >= self.content_length or lower > upper: return HTTPRequestRangeNotSatisfiable(( "Range request was made beyond the end of the content,\r\n" "which is %s long.\r\n Range: %s\r\n") % ( self.content_length, RANGE(environ)) ).wsgi_application(environ, start_response) content_length = upper - lower + 1 CONTENT_RANGE.update(headers, first_byte=lower, last_byte=upper, total_length = self.content_length) CONTENT_LENGTH.update(headers, content_length) if content_length == self.content_length: start_response('200 OK', headers) else: start_response('206 Partial Content', headers) if self.content is not None: return [self.content[lower:upper+1]] return (lower, content_length) class FileApp(DataApp): """ Returns an application that will send the file at the given filename. Adds a mime type based on ``mimetypes.guess_type()``. See DataApp for the arguments beyond ``filename``. """ def __init__(self, filename, headers=None, **kwargs): self.filename = filename content_type, content_encoding = self.guess_type() if content_type and 'content_type' not in kwargs: kwargs['content_type'] = content_type if content_encoding and 'content_encoding' not in kwargs: kwargs['content_encoding'] = content_encoding DataApp.__init__(self, None, headers, **kwargs) def guess_type(self): return mimetypes.guess_type(self.filename) def update(self, force=False): stat = os.stat(self.filename) if not force and stat.st_mtime == self.last_modified: return self.last_modified = stat.st_mtime if stat.st_size < CACHE_SIZE: fh = open(self.filename,"rb") self.set_content(fh.read(), stat.st_mtime) fh.close() else: self.content = None self.content_length = stat.st_size # This is updated automatically if self.set_content() is # called LAST_MODIFIED.update(self.headers, time=self.last_modified) def get(self, environ, start_response): is_head = environ['REQUEST_METHOD'].upper() == 'HEAD' if 'max-age=0' in CACHE_CONTROL(environ).lower(): self.update(force=True) # RFC 2616 13.2.6 else: self.update() if not self.content: if not os.path.exists(self.filename): exc = HTTPNotFound( 'The resource does not exist', comment="No file at %r" % self.filename) return exc(environ, start_response) try: file = open(self.filename, 'rb') except (IOError, OSError), e: exc = HTTPForbidden( 'You are not permitted to view this file (%s)' % e) return exc.wsgi_application( environ, start_response) retval = DataApp.get(self, environ, start_response) if isinstance(retval, list): # cached content, exception, or not-modified if is_head: return [''] return retval (lower, content_length) = retval if is_head: return [''] file.seek(lower) file_wrapper = environ.get('wsgi.file_wrapper', None) if file_wrapper: return file_wrapper(file, BLOCK_SIZE) else: return _FileIter(file, size=content_length) class _FileIter(object): def __init__(self, file, block_size=None, size=None): self.file = file self.size = size self.block_size = block_size or BLOCK_SIZE def __iter__(self): return self def next(self): chunk_size = self.block_size if self.size is not None: if chunk_size > self.size: chunk_size = self.size self.size -= chunk_size data = self.file.read(chunk_size) if not data: raise StopIteration return data def close(self): self.file.close() class DirectoryApp(object): """ Returns an application that dispatches requests to corresponding FileApps based on PATH_INFO. FileApp instances are cached. This app makes sure not to serve any files that are not in a subdirectory. To customize FileApp creation override ``DirectoryApp.make_fileapp`` """ def __init__(self, path): self.path = os.path.abspath(path) if not self.path.endswith(os.path.sep): self.path += os.path.sep assert os.path.isdir(self.path) self.cached_apps = {} make_fileapp = FileApp def __call__(self, environ, start_response): path_info = environ['PATH_INFO'] app = self.cached_apps.get(path_info) if app is None: path = os.path.join(self.path, path_info.lstrip('/')) if not os.path.normpath(path).startswith(self.path): app = HTTPForbidden() elif os.path.isfile(path): app = self.make_fileapp(path) self.cached_apps[path_info] = app else: app = HTTPNotFound(comment=path) return app(environ, start_response) class ArchiveStore(object): """ Returns an application that serves up a DataApp for items requested in a given zip or tar archive. Constructor Arguments: ``filepath`` the path to the archive being served ``cache_control()`` This method provides validated construction of the ``Cache-Control`` header as well as providing for automated filling out of the ``EXPIRES`` header for HTTP/1.0 clients. """ def __init__(self, filepath): if zipfile.is_zipfile(filepath): self.archive = zipfile.ZipFile(filepath,"r") elif tarfile.is_tarfile(filepath): self.archive = tarfile.TarFileCompat(filepath,"r") else: raise AssertionError("filepath '%s' is not a zip or tar " % filepath) self.expires = None self.last_modified = time.time() self.cache = {} def cache_control(self, **kwargs): self.expires = CACHE_CONTROL.apply(self.headers, **kwargs) or None return self def __call__(self, environ, start_response): path = environ.get("PATH_INFO","") if path.startswith("/"): path = path[1:] application = self.cache.get(path) if application: return application(environ, start_response) try: info = self.archive.getinfo(path) except KeyError: exc = HTTPNotFound("The file requested, '%s', was not found." % path) return exc.wsgi_application(environ, start_response) if info.filename.endswith("/"): exc = HTTPNotFound("Path requested, '%s', is not a file." % path) return exc.wsgi_application(environ, start_response) content_type, content_encoding = mimetypes.guess_type(info.filename) # 'None' is not a valid content-encoding, so don't set the header if # mimetypes.guess_type returns None if content_encoding is not None: app = DataApp(None, content_type = content_type, content_encoding = content_encoding) else: app = DataApp(None, content_type = content_type) app.set_content(self.archive.read(path), time.mktime(info.date_time + (0,0,0))) self.cache[path] = app app.expires = self.expires return app(environ, start_response) Paste-1.7.5.1/paste/flup_session.py0000664000175000017500000000754511354703364015565 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Creates a session object. In your application, use:: environ['paste.flup_session_service'].session This will return a dictionary. The contents of this dictionary will be saved to disk when the request is completed. The session will be created when you first fetch the session dictionary, and a cookie will be sent in that case. There's current no way to use sessions without cookies, and there's no way to delete a session except to clear its data. """ from paste import httpexceptions from paste import wsgilib import flup.middleware.session flup_session = flup.middleware.session # This is a dictionary of existing stores, keyed by a tuple of # store type and parameters store_cache = {} class NoDefault(object): pass class SessionMiddleware(object): session_classes = { 'memory': (flup_session.MemorySessionStore, [('session_timeout', 'timeout', int, 60)]), 'disk': (flup_session.DiskSessionStore, [('session_timeout', 'timeout', int, 60), ('session_dir', 'storeDir', str, '/tmp/sessions')]), 'shelve': (flup_session.ShelveSessionStore, [('session_timeout', 'timeout', int, 60), ('session_file', 'storeFile', str, '/tmp/session.shelve')]), } def __init__(self, app, global_conf=None, session_type=NoDefault, cookie_name=NoDefault, **store_config ): self.application = app if session_type is NoDefault: session_type = global_conf.get('session_type', 'disk') self.session_type = session_type try: self.store_class, self.store_args = self.session_classes[self.session_type] except KeyError: raise KeyError( "The session_type %s is unknown (I know about %s)" % (self.session_type, ', '.join(self.session_classes.keys()))) kw = {} for config_name, kw_name, coercer, default in self.store_args: value = coercer(store_config.get(config_name, default)) kw[kw_name] = value self.store = self.store_class(**kw) if cookie_name is NoDefault: cookie_name = global_conf.get('session_cookie', '_SID_') self.cookie_name = cookie_name def __call__(self, environ, start_response): service = flup_session.SessionService( self.store, environ, cookieName=self.cookie_name, fieldName=self.cookie_name) environ['paste.flup_session_service'] = service def cookie_start_response(status, headers, exc_info=None): service.addCookie(headers) return start_response(status, headers, exc_info) try: app_iter = self.application(environ, cookie_start_response) except httpexceptions.HTTPException, e: headers = (e.headers or {}).items() service.addCookie(headers) e.headers = dict(headers) service.close() raise except: service.close() raise return wsgilib.add_close(app_iter, service.close) def make_session_middleware(app, global_conf, session_type=NoDefault, cookie_name=NoDefault, **store_config): """ Wraps the application in a session-managing middleware. The session service can then be found in ``environ['paste.flup_session_service']`` """ return SessionMiddleware( app, global_conf=global_conf, session_type=session_type, cookie_name=cookie_name, **store_config) Paste-1.7.5.1/paste/url.py0000664000175000017500000003444011354703364013650 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ This module implements a class for handling URLs. """ import urllib import cgi from paste import request # Imported lazily from FormEncode: variabledecode = None __all__ = ["URL", "Image"] def html_quote(v): if v is None: return '' return cgi.escape(str(v), 1) def url_quote(v): if v is None: return '' return urllib.quote(str(v)) url_unquote = urllib.unquote def js_repr(v): if v is None: return 'null' elif v is False: return 'false' elif v is True: return 'true' elif isinstance(v, list): return '[%s]' % ', '.join(map(js_repr, v)) elif isinstance(v, dict): return '{%s}' % ', '.join( ['%s: %s' % (js_repr(key), js_repr(value)) for key, value in v]) elif isinstance(v, str): return repr(v) elif isinstance(v, unicode): # @@: how do you do Unicode literals in Javascript? return repr(v.encode('UTF-8')) elif isinstance(v, (float, int)): return repr(v) elif isinstance(v, long): return repr(v).lstrip('L') elif hasattr(v, '__js_repr__'): return v.__js_repr__() else: raise ValueError( "I don't know how to turn %r into a Javascript representation" % v) class URLResource(object): """ This is an abstract superclass for different kinds of URLs """ default_params = {} def __init__(self, url, vars=None, attrs=None, params=None): self.url = url or '/' self.vars = vars or [] self.attrs = attrs or {} self.params = self.default_params.copy() self.original_params = params or {} if params: self.params.update(params) #@classmethod def from_environ(cls, environ, with_query_string=True, with_path_info=True, script_name=None, path_info=None, querystring=None): url = request.construct_url( environ, with_query_string=False, with_path_info=with_path_info, script_name=script_name, path_info=path_info) if with_query_string: if querystring is None: vars = request.parse_querystring(environ) else: vars = cgi.parse_qsl( querystring, keep_blank_values=True, strict_parsing=False) else: vars = None v = cls(url, vars=vars) return v from_environ = classmethod(from_environ) def __call__(self, *args, **kw): res = self._add_positional(args) res = res._add_vars(kw) return res def __getitem__(self, item): if '=' in item: name, value = item.split('=', 1) return self._add_vars({url_unquote(name): url_unquote(value)}) return self._add_positional((item,)) def attr(self, **kw): for key in kw.keys(): if key.endswith('_'): kw[key[:-1]] = kw[key] del kw[key] new_attrs = self.attrs.copy() new_attrs.update(kw) return self.__class__(self.url, vars=self.vars, attrs=new_attrs, params=self.original_params) def param(self, **kw): new_params = self.original_params.copy() new_params.update(kw) return self.__class__(self.url, vars=self.vars, attrs=self.attrs, params=new_params) def coerce_vars(self, vars): global variabledecode need_variable_encode = False for key, value in vars.items(): if isinstance(value, dict): need_variable_encode = True if key.endswith('_'): vars[key[:-1]] = vars[key] del vars[key] if need_variable_encode: if variabledecode is None: from formencode import variabledecode vars = variabledecode.variable_encode(vars) return vars def var(self, **kw): kw = self.coerce_vars(kw) new_vars = self.vars + kw.items() return self.__class__(self.url, vars=new_vars, attrs=self.attrs, params=self.original_params) def setvar(self, **kw): """ Like ``.var(...)``, except overwrites keys, where .var simply extends the keys. Setting a variable to None here will effectively delete it. """ kw = self.coerce_vars(kw) new_vars = [] for name, values in self.vars: if name in kw: continue new_vars.append((name, values)) new_vars.extend(kw.items()) return self.__class__(self.url, vars=new_vars, attrs=self.attrs, params=self.original_params) def setvars(self, **kw): """ Creates a copy of this URL, but with all the variables set/reset (like .setvar(), except clears past variables at the same time) """ return self.__class__(self.url, vars=kw.items(), attrs=self.attrs, params=self.original_params) def addpath(self, *paths): u = self for path in paths: path = str(path).lstrip('/') new_url = u.url if not new_url.endswith('/'): new_url += '/' u = u.__class__(new_url+path, vars=u.vars, attrs=u.attrs, params=u.original_params) return u __div__ = addpath def become(self, OtherClass): return OtherClass(self.url, vars=self.vars, attrs=self.attrs, params=self.original_params) def href__get(self): s = self.url if self.vars: s += '?' vars = [] for name, val in self.vars: if isinstance(val, (list, tuple)): val = [v for v in val if v is not None] elif val is None: continue vars.append((name, val)) s += urllib.urlencode(vars, True) return s href = property(href__get) def __repr__(self): base = '<%s %s' % (self.__class__.__name__, self.href or "''") if self.attrs: base += ' attrs(%s)' % ( ' '.join(['%s="%s"' % (html_quote(n), html_quote(v)) for n, v in self.attrs.items()])) if self.original_params: base += ' params(%s)' % ( ', '.join(['%s=%r' % (n, v) for n, v in self.attrs.items()])) return base + '>' def html__get(self): if not self.params.get('tag'): raise ValueError( "You cannot get the HTML of %r until you set the " "'tag' param'" % self) content = self._get_content() tag = '<%s' % self.params.get('tag') attrs = ' '.join([ '%s="%s"' % (html_quote(n), html_quote(v)) for n, v in self._html_attrs()]) if attrs: tag += ' ' + attrs tag += self._html_extra() if content is None: return tag + ' />' else: return '%s>%s' % (tag, content, self.params.get('tag')) html = property(html__get) def _html_attrs(self): return self.attrs.items() def _html_extra(self): return '' def _get_content(self): """ Return the content for a tag (for self.html); return None for an empty tag (like ````) """ raise NotImplementedError def _add_vars(self, vars): raise NotImplementedError def _add_positional(self, args): raise NotImplementedError class URL(URLResource): r""" >>> u = URL('http://localhost') >>> u >>> u = u['view'] >>> str(u) 'http://localhost/view' >>> u['//foo'].param(content='view').html 'view' >>> u.param(confirm='Really?', content='goto').html 'goto' >>> u(title='See "it"', content='goto').html 'goto' >>> u('another', var='fuggetaboutit', content='goto').html 'goto' >>> u.attr(content='goto').html Traceback (most recent call last): .... ValueError: You must give a content param to generate anchor tags >>> str(u['foo=bar%20stuff']) 'http://localhost/view?foo=bar+stuff' """ default_params = {'tag': 'a'} def __str__(self): return self.href def _get_content(self): if not self.params.get('content'): raise ValueError( "You must give a content param to %r generate anchor tags" % self) return self.params['content'] def _add_vars(self, vars): url = self for name in ('confirm', 'content'): if name in vars: url = url.param(**{name: vars.pop(name)}) if 'target' in vars: url = url.attr(target=vars.pop('target')) return url.var(**vars) def _add_positional(self, args): return self.addpath(*args) def _html_attrs(self): attrs = self.attrs.items() attrs.insert(0, ('href', self.href)) if self.params.get('confirm'): attrs.append(('onclick', 'return confirm(%s)' % js_repr(self.params['confirm']))) return attrs def onclick_goto__get(self): return 'location.href=%s; return false' % js_repr(self.href) onclick_goto = property(onclick_goto__get) def button__get(self): return self.become(Button) button = property(button__get) def js_popup__get(self): return self.become(JSPopup) js_popup = property(js_popup__get) class Image(URLResource): r""" >>> i = Image('/images') >>> i = i / '/foo.png' >>> i.html '' >>> str(i['alt=foo']) 'foo' >>> i.href '/images/foo.png' """ default_params = {'tag': 'img'} def __str__(self): return self.html def _get_content(self): return None def _add_vars(self, vars): return self.attr(**vars) def _add_positional(self, args): return self.addpath(*args) def _html_attrs(self): attrs = self.attrs.items() attrs.insert(0, ('src', self.href)) return attrs class Button(URLResource): r""" >>> u = URL('/') >>> u = u / 'delete' >>> b = u.button['confirm=Sure?'](id=5, content='del') >>> str(b) '' """ default_params = {'tag': 'button'} def __str__(self): return self.html def _get_content(self): if self.params.get('content'): return self.params['content'] if self.attrs.get('value'): return self.attrs['content'] # @@: Error? return None def _add_vars(self, vars): button = self if 'confirm' in vars: button = button.param(confirm=vars.pop('confirm')) if 'content' in vars: button = button.param(content=vars.pop('content')) return button.var(**vars) def _add_positional(self, args): return self.addpath(*args) def _html_attrs(self): attrs = self.attrs.items() onclick = 'location.href=%s' % js_repr(self.href) if self.params.get('confirm'): onclick = 'if (confirm(%s)) {%s}' % ( js_repr(self.params['confirm']), onclick) onclick += '; return false' attrs.insert(0, ('onclick', onclick)) return attrs class JSPopup(URLResource): r""" >>> u = URL('/') >>> u = u / 'view' >>> j = u.js_popup(content='view') >>> j.html 'view' """ default_params = {'tag': 'a', 'target': '_blank'} def _add_vars(self, vars): button = self for var in ('width', 'height', 'stripped', 'content'): if var in vars: button = button.param(**{var: vars.pop(var)}) return button.var(**vars) def _window_args(self): p = self.params features = [] if p.get('stripped'): p['location'] = p['status'] = p['toolbar'] = '0' for param in 'channelmode directories fullscreen location menubar resizable scrollbars status titlebar'.split(): if param not in p: continue v = p[param] if v not in ('yes', 'no', '1', '0'): if v: v = '1' else: v = '0' features.append('%s=%s' % (param, v)) for param in 'height left top width': if not p.get(param): continue features.append('%s=%s' % (param, p[param])) args = [self.href, p['target']] if features: args.append(','.join(features)) return ', '.join(map(js_repr, args)) def _html_attrs(self): attrs = self.attrs.items() onclick = ('window.open(%s); return false' % self._window_args()) attrs.insert(0, ('target', self.params['target'])) attrs.insert(0, ('onclick', onclick)) attrs.insert(0, ('href', self.href)) return attrs def _get_content(self): if not self.params.get('content'): raise ValueError( "You must give a content param to %r generate anchor tags" % self) return self.params['content'] def _add_positional(self, args): return self.addpath(*args) if __name__ == '__main__': import doctest doctest.testmod() Paste-1.7.5.1/paste/urlparser.py0000664000175000017500000006347011437653004015067 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ WSGI applications that parse the URL and dispatch to on-disk resources """ import os import sys import imp import mimetypes try: import pkg_resources except ImportError: pkg_resources = None from paste import request from paste import fileapp from paste.util import import_string from paste import httpexceptions from httpheaders import ETAG from paste.util import converters class NoDefault(object): pass __all__ = ['URLParser', 'StaticURLParser', 'PkgResourcesParser'] class URLParser(object): """ WSGI middleware Application dispatching, based on URL. An instance of `URLParser` is an application that loads and delegates to other applications. It looks for files in its directory that match the first part of PATH_INFO; these may have an extension, but are not required to have one, in which case the available files are searched to find the appropriate file. If it is ambiguous, a 404 is returned and an error logged. By default there is a constructor for .py files that loads the module, and looks for an attribute ``application``, which is a ready application object, or an attribute that matches the module name, which is a factory for building applications, and is called with no arguments. URLParser will also look in __init__.py for special overrides. These overrides are: ``urlparser_hook(environ)`` This can modify the environment. Its return value is ignored, and it cannot be used to change the response in any way. You *can* use this, for example, to manipulate SCRIPT_NAME/PATH_INFO (try to keep them consistent with the original URL -- but consuming PATH_INFO and moving that to SCRIPT_NAME is ok). ``urlparser_wrap(environ, start_response, app)``: After URLParser finds the application, it calls this function (if present). If this function doesn't call ``app(environ, start_response)`` then the application won't be called at all! This can be used to allocate resources (with ``try:finally:``) or otherwise filter the output of the application. ``not_found_hook(environ, start_response)``: If no file can be found (*in this directory*) to match the request, then this WSGI application will be called. You can use this to change the URL and pass the request back to URLParser again, or on to some other application. This doesn't catch all ``404 Not Found`` responses, just missing files. ``application(environ, start_response)``: This basically overrides URLParser completely, and the given application is used for all requests. ``urlparser_wrap`` and ``urlparser_hook`` are still called, but the filesystem isn't searched in any way. """ parsers_by_directory = {} # This is lazily initialized init_module = NoDefault global_constructors = {} def __init__(self, global_conf, directory, base_python_name, index_names=NoDefault, hide_extensions=NoDefault, ignore_extensions=NoDefault, constructors=None, **constructor_conf): """ Create a URLParser object that looks at `directory`. `base_python_name` is the package that this directory represents, thus any Python modules in this directory will be given names under this package. """ if global_conf: import warnings warnings.warn( 'The global_conf argument to URLParser is deprecated; ' 'either pass in None or {}, or use make_url_parser', DeprecationWarning) else: global_conf = {} if os.path.sep != '/': directory = directory.replace(os.path.sep, '/') self.directory = directory self.base_python_name = base_python_name # This logic here should be deprecated since it is in # make_url_parser if index_names is NoDefault: index_names = global_conf.get( 'index_names', ('index', 'Index', 'main', 'Main')) self.index_names = converters.aslist(index_names) if hide_extensions is NoDefault: hide_extensions = global_conf.get( 'hide_extensions', ('.pyc', '.bak', '.py~', '.pyo')) self.hide_extensions = converters.aslist(hide_extensions) if ignore_extensions is NoDefault: ignore_extensions = global_conf.get( 'ignore_extensions', ()) self.ignore_extensions = converters.aslist(ignore_extensions) self.constructors = self.global_constructors.copy() if constructors: self.constructors.update(constructors) # @@: Should we also check the global options for constructors? for name, value in constructor_conf.items(): if not name.startswith('constructor '): raise ValueError( "Only extra configuration keys allowed are " "'constructor .ext = import_expr'; you gave %r " "(=%r)" % (name, value)) ext = name[len('constructor '):].strip() if isinstance(value, (str, unicode)): value = import_string.eval_import(value) self.constructors[ext] = value def __call__(self, environ, start_response): environ['paste.urlparser.base_python_name'] = self.base_python_name if self.init_module is NoDefault: self.init_module = self.find_init_module(environ) path_info = environ.get('PATH_INFO', '') if not path_info: return self.add_slash(environ, start_response) if (self.init_module and getattr(self.init_module, 'urlparser_hook', None)): self.init_module.urlparser_hook(environ) orig_path_info = environ['PATH_INFO'] orig_script_name = environ['SCRIPT_NAME'] application, filename = self.find_application(environ) if not application: if (self.init_module and getattr(self.init_module, 'not_found_hook', None) and environ.get('paste.urlparser.not_found_parser') is not self): not_found_hook = self.init_module.not_found_hook environ['paste.urlparser.not_found_parser'] = self environ['PATH_INFO'] = orig_path_info environ['SCRIPT_NAME'] = orig_script_name return not_found_hook(environ, start_response) if filename is None: name, rest_of_path = request.path_info_split(environ['PATH_INFO']) if not name: name = 'one of %s' % ', '.join( self.index_names or ['(no index_names defined)']) return self.not_found( environ, start_response, 'Tried to load %s from directory %s' % (name, self.directory)) else: environ['wsgi.errors'].write( 'Found resource %s, but could not construct application\n' % filename) return self.not_found( environ, start_response, 'Tried to load %s from directory %s' % (filename, self.directory)) if (self.init_module and getattr(self.init_module, 'urlparser_wrap', None)): return self.init_module.urlparser_wrap( environ, start_response, application) else: return application(environ, start_response) def find_application(self, environ): if (self.init_module and getattr(self.init_module, 'application', None) and not environ.get('paste.urlparser.init_application') == environ['SCRIPT_NAME']): environ['paste.urlparser.init_application'] = environ['SCRIPT_NAME'] return self.init_module.application, None name, rest_of_path = request.path_info_split(environ['PATH_INFO']) environ['PATH_INFO'] = rest_of_path if name is not None: environ['SCRIPT_NAME'] = environ.get('SCRIPT_NAME', '') + '/' + name if not name: names = self.index_names for index_name in names: filename = self.find_file(environ, index_name) if filename: break else: # None of the index files found filename = None else: filename = self.find_file(environ, name) if filename is None: return None, filename else: return self.get_application(environ, filename), filename def not_found(self, environ, start_response, debug_message=None): exc = httpexceptions.HTTPNotFound( 'The resource at %s could not be found' % request.construct_url(environ), comment=debug_message) return exc.wsgi_application(environ, start_response) def add_slash(self, environ, start_response): """ This happens when you try to get to a directory without a trailing / """ url = request.construct_url(environ, with_query_string=False) url += '/' if environ.get('QUERY_STRING'): url += '?' + environ['QUERY_STRING'] exc = httpexceptions.HTTPMovedPermanently( 'The resource has moved to %s - you should be redirected ' 'automatically.' % url, headers=[('location', url)]) return exc.wsgi_application(environ, start_response) def find_file(self, environ, base_filename): possible = [] """Cache a few values to reduce function call overhead""" for filename in os.listdir(self.directory): base, ext = os.path.splitext(filename) full_filename = os.path.join(self.directory, filename) if (ext in self.hide_extensions or not base): continue if filename == base_filename: possible.append(full_filename) continue if ext in self.ignore_extensions: continue if base == base_filename: possible.append(full_filename) if not possible: #environ['wsgi.errors'].write( # 'No file found matching %r in %s\n' # % (base_filename, self.directory)) return None if len(possible) > 1: # If there is an exact match, this isn't 'ambiguous' # per se; it might mean foo.gif and foo.gif.back for # instance if full_filename in possible: return full_filename else: environ['wsgi.errors'].write( 'Ambiguous URL: %s; matches files %s\n' % (request.construct_url(environ), ', '.join(possible))) return None return possible[0] def get_application(self, environ, filename): if os.path.isdir(filename): t = 'dir' else: t = os.path.splitext(filename)[1] constructor = self.constructors.get(t, self.constructors.get('*')) if constructor is None: #environ['wsgi.errors'].write( # 'No constructor found for %s\n' % t) return constructor app = constructor(self, environ, filename) if app is None: #environ['wsgi.errors'].write( # 'Constructor %s return None for %s\n' % # (constructor, filename)) pass return app def register_constructor(cls, extension, constructor): """ Register a function as a constructor. Registered constructors apply to all instances of `URLParser`. The extension should have a leading ``.``, or the special extensions ``dir`` (for directories) and ``*`` (a catch-all). `constructor` must be a callable that takes two arguments: ``environ`` and ``filename``, and returns a WSGI application. """ d = cls.global_constructors assert not d.has_key(extension), ( "A constructor already exists for the extension %r (%r) " "when attemption to register constructor %r" % (extension, d[extension], constructor)) d[extension] = constructor register_constructor = classmethod(register_constructor) def get_parser(self, directory, base_python_name): """ Get a parser for the given directory, or create one if necessary. This way parsers can be cached and reused. # @@: settings are inherited from the first caller """ try: return self.parsers_by_directory[(directory, base_python_name)] except KeyError: parser = self.__class__( {}, directory, base_python_name, index_names=self.index_names, hide_extensions=self.hide_extensions, ignore_extensions=self.ignore_extensions, constructors=self.constructors) self.parsers_by_directory[(directory, base_python_name)] = parser return parser def find_init_module(self, environ): filename = os.path.join(self.directory, '__init__.py') if not os.path.exists(filename): return None return load_module(environ, filename) def __repr__(self): return '<%s directory=%r; module=%s at %s>' % ( self.__class__.__name__, self.directory, self.base_python_name, hex(abs(id(self)))) def make_directory(parser, environ, filename): base_python_name = environ['paste.urlparser.base_python_name'] if base_python_name: base_python_name += "." + os.path.basename(filename) else: base_python_name = os.path.basename(filename) return parser.get_parser(filename, base_python_name) URLParser.register_constructor('dir', make_directory) def make_unknown(parser, environ, filename): return fileapp.FileApp(filename) URLParser.register_constructor('*', make_unknown) def load_module(environ, filename): base_python_name = environ['paste.urlparser.base_python_name'] module_name = os.path.splitext(os.path.basename(filename))[0] if base_python_name: module_name = base_python_name + '.' + module_name return load_module_from_name(environ, filename, module_name, environ['wsgi.errors']) def load_module_from_name(environ, filename, module_name, errors): if sys.modules.has_key(module_name): return sys.modules[module_name] init_filename = os.path.join(os.path.dirname(filename), '__init__.py') if not os.path.exists(init_filename): try: f = open(init_filename, 'w') except (OSError, IOError), e: errors.write( 'Cannot write __init__.py file into directory %s (%s)\n' % (os.path.dirname(filename), e)) return None f.write('#\n') f.close() fp = None if sys.modules.has_key(module_name): return sys.modules[module_name] if '.' in module_name: parent_name = '.'.join(module_name.split('.')[:-1]) base_name = module_name.split('.')[-1] parent = load_module_from_name(environ, os.path.dirname(filename), parent_name, errors) else: base_name = module_name fp = None try: fp, pathname, stuff = imp.find_module( base_name, [os.path.dirname(filename)]) module = imp.load_module(module_name, fp, pathname, stuff) finally: if fp is not None: fp.close() return module def make_py(parser, environ, filename): module = load_module(environ, filename) if not module: return None if hasattr(module, 'application') and module.application: return getattr(module.application, 'wsgi_application', module.application) base_name = module.__name__.split('.')[-1] if hasattr(module, base_name): obj = getattr(module, base_name) if hasattr(obj, 'wsgi_application'): return obj.wsgi_application else: # @@: Old behavior; should probably be deprecated eventually: return getattr(module, base_name)() environ['wsgi.errors'].write( "Cound not find application or %s in %s\n" % (base_name, module)) return None URLParser.register_constructor('.py', make_py) class StaticURLParser(object): """ Like ``URLParser`` but only serves static files. ``cache_max_age``: integer specifies Cache-Control max_age in seconds """ # @@: Should URLParser subclass from this? def __init__(self, directory, root_directory=None, cache_max_age=None): self.directory = self.normpath(directory) self.root_directory = self.normpath(root_directory or directory) self.cache_max_age = cache_max_age def normpath(path): return os.path.normcase(os.path.abspath(path)) normpath = staticmethod(normpath) def __call__(self, environ, start_response): path_info = environ.get('PATH_INFO', '') if not path_info: return self.add_slash(environ, start_response) if path_info == '/': # @@: This should obviously be configurable filename = 'index.html' else: filename = request.path_info_pop(environ) full = self.normpath(os.path.join(self.directory, filename)) if not full.startswith(self.root_directory): # Out of bounds return self.not_found(environ, start_response) if not os.path.exists(full): return self.not_found(environ, start_response) if os.path.isdir(full): # @@: Cache? return self.__class__(full, root_directory=self.root_directory, cache_max_age=self.cache_max_age)(environ, start_response) if environ.get('PATH_INFO') and environ.get('PATH_INFO') != '/': return self.error_extra_path(environ, start_response) if_none_match = environ.get('HTTP_IF_NONE_MATCH') if if_none_match: mytime = os.stat(full).st_mtime if str(mytime) == if_none_match: headers = [] ## FIXME: probably should be ## ETAG.update(headers, '"%s"' % mytime) ETAG.update(headers, mytime) start_response('304 Not Modified', headers) return [''] # empty body fa = self.make_app(full) if self.cache_max_age: fa.cache_control(max_age=self.cache_max_age) return fa(environ, start_response) def make_app(self, filename): return fileapp.FileApp(filename) def add_slash(self, environ, start_response): """ This happens when you try to get to a directory without a trailing / """ url = request.construct_url(environ, with_query_string=False) url += '/' if environ.get('QUERY_STRING'): url += '?' + environ['QUERY_STRING'] exc = httpexceptions.HTTPMovedPermanently( 'The resource has moved to %s - you should be redirected ' 'automatically.' % url, headers=[('location', url)]) return exc.wsgi_application(environ, start_response) def not_found(self, environ, start_response, debug_message=None): exc = httpexceptions.HTTPNotFound( 'The resource at %s could not be found' % request.construct_url(environ), comment='SCRIPT_NAME=%r; PATH_INFO=%r; looking in %r; debug: %s' % (environ.get('SCRIPT_NAME'), environ.get('PATH_INFO'), self.directory, debug_message or '(none)')) return exc.wsgi_application(environ, start_response) def error_extra_path(self, environ, start_response): exc = httpexceptions.HTTPNotFound( 'The trailing path %r is not allowed' % environ['PATH_INFO']) return exc.wsgi_application(environ, start_response) def __repr__(self): return '<%s %r>' % (self.__class__.__name__, self.directory) def make_static(global_conf, document_root, cache_max_age=None): """ Return a WSGI application that serves a directory (configured with document_root) cache_max_age - integer specifies CACHE_CONTROL max_age in seconds """ if cache_max_age is not None: cache_max_age = int(cache_max_age) return StaticURLParser( document_root, cache_max_age=cache_max_age) class PkgResourcesParser(StaticURLParser): def __init__(self, egg_or_spec, resource_name, manager=None, root_resource=None): if pkg_resources is None: raise NotImplementedError("This class requires pkg_resources.") if isinstance(egg_or_spec, (str, unicode)): self.egg = pkg_resources.get_distribution(egg_or_spec) else: self.egg = egg_or_spec self.resource_name = resource_name if manager is None: manager = pkg_resources.ResourceManager() self.manager = manager if root_resource is None: root_resource = resource_name self.root_resource = os.path.normpath(root_resource) def __repr__(self): return '<%s for %s:%r>' % ( self.__class__.__name__, self.egg.project_name, self.resource_name) def __call__(self, environ, start_response): path_info = environ.get('PATH_INFO', '') if not path_info: return self.add_slash(environ, start_response) if path_info == '/': # @@: This should obviously be configurable filename = 'index.html' else: filename = request.path_info_pop(environ) resource = os.path.normcase(os.path.normpath( self.resource_name + '/' + filename)) if self.root_resource is not None and not resource.startswith(self.root_resource): # Out of bounds return self.not_found(environ, start_response) if not self.egg.has_resource(resource): return self.not_found(environ, start_response) if self.egg.resource_isdir(resource): # @@: Cache? child_root = self.root_resource is not None and self.root_resource or \ self.resource_name return self.__class__(self.egg, resource, self.manager, root_resource=child_root)(environ, start_response) if environ.get('PATH_INFO') and environ.get('PATH_INFO') != '/': return self.error_extra_path(environ, start_response) type, encoding = mimetypes.guess_type(resource) if not type: type = 'application/octet-stream' # @@: I don't know what to do with the encoding. try: file = self.egg.get_resource_stream(self.manager, resource) except (IOError, OSError), e: exc = httpexceptions.HTTPForbidden( 'You are not permitted to view this file (%s)' % e) return exc.wsgi_application(environ, start_response) start_response('200 OK', [('content-type', type)]) return fileapp._FileIter(file) def not_found(self, environ, start_response, debug_message=None): exc = httpexceptions.HTTPNotFound( 'The resource at %s could not be found' % request.construct_url(environ), comment='SCRIPT_NAME=%r; PATH_INFO=%r; looking in egg:%s#%r; debug: %s' % (environ.get('SCRIPT_NAME'), environ.get('PATH_INFO'), self.egg, self.resource_name, debug_message or '(none)')) return exc.wsgi_application(environ, start_response) def make_pkg_resources(global_conf, egg, resource_name=''): """ A static file parser that loads data from an egg using ``pkg_resources``. Takes a configuration value ``egg``, which is an egg spec, and a base ``resource_name`` (default empty string) which is the path in the egg that this starts at. """ if pkg_resources is None: raise NotImplementedError("This function requires pkg_resources.") return PkgResourcesParser(egg, resource_name) def make_url_parser(global_conf, directory, base_python_name, index_names=None, hide_extensions=None, ignore_extensions=None, **constructor_conf): """ Create a URLParser application that looks in ``directory``, which should be the directory for the Python package named in ``base_python_name``. ``index_names`` are used when viewing the directory (like ``'index'`` for ``'index.html'``). ``hide_extensions`` are extensions that are not viewable (like ``'.pyc'``) and ``ignore_extensions`` are viewable but only if an explicit extension is given. """ if index_names is None: index_names = global_conf.get( 'index_names', ('index', 'Index', 'main', 'Main')) index_names = converters.aslist(index_names) if hide_extensions is None: hide_extensions = global_conf.get( 'hide_extensions', ('.pyc', 'bak', 'py~')) hide_extensions = converters.aslist(hide_extensions) if ignore_extensions is None: ignore_extensions = global_conf.get( 'ignore_extensions', ()) ignore_extensions = converters.aslist(ignore_extensions) # There's no real way to set constructors currently... return URLParser({}, directory, base_python_name, index_names=index_names, hide_extensions=hide_extensions, ignore_extensions=ignore_extensions, **constructor_conf) Paste-1.7.5.1/paste/reloader.py0000664000175000017500000001357211354703364014646 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ A file monitor and server restarter. Use this like: ..code-block:: Python import reloader reloader.install() Then make sure your server is installed with a shell script like:: err=3 while test "$err" -eq 3 ; do python server.py err="$?" done or is run from this .bat file (if you use Windows):: @echo off :repeat python server.py if %errorlevel% == 3 goto repeat or run a monitoring process in Python (``paster serve --reload`` does this). Use the ``watch_file(filename)`` function to cause a reload/restart for other other non-Python files (e.g., configuration files). If you have a dynamic set of files that grows over time you can use something like:: def watch_config_files(): return CONFIG_FILE_CACHE.keys() paste.reloader.add_file_callback(watch_config_files) Then every time the reloader polls files it will call ``watch_config_files`` and check all the filenames it returns. """ import os import sys import time import threading import traceback from paste.util.classinstance import classinstancemethod def install(poll_interval=1): """ Install the reloading monitor. On some platforms server threads may not terminate when the main thread does, causing ports to remain open/locked. The ``raise_keyboard_interrupt`` option creates a unignorable signal which causes the whole application to shut-down (rudely). """ mon = Monitor(poll_interval=poll_interval) t = threading.Thread(target=mon.periodic_reload) t.setDaemon(True) t.start() class Monitor(object): instances = [] global_extra_files = [] global_file_callbacks = [] def __init__(self, poll_interval): self.module_mtimes = {} self.keep_running = True self.poll_interval = poll_interval self.extra_files = list(self.global_extra_files) self.instances.append(self) self.file_callbacks = list(self.global_file_callbacks) def periodic_reload(self): while True: if not self.check_reload(): # use os._exit() here and not sys.exit() since within a # thread sys.exit() just closes the given thread and # won't kill the process; note os._exit does not call # any atexit callbacks, nor does it do finally blocks, # flush open files, etc. In otherwords, it is rude. os._exit(3) break time.sleep(self.poll_interval) def check_reload(self): filenames = list(self.extra_files) for file_callback in self.file_callbacks: try: filenames.extend(file_callback()) except: print >> sys.stderr, "Error calling paste.reloader callback %r:" % file_callback traceback.print_exc() for module in sys.modules.values(): try: filename = module.__file__ except (AttributeError, ImportError), exc: continue if filename is not None: filenames.append(filename) for filename in filenames: try: stat = os.stat(filename) if stat: mtime = stat.st_mtime else: mtime = 0 except (OSError, IOError): continue if filename.endswith('.pyc') and os.path.exists(filename[:-1]): mtime = max(os.stat(filename[:-1]).st_mtime, mtime) elif filename.endswith('$py.class') and \ os.path.exists(filename[:-9] + '.py'): mtime = max(os.stat(filename[:-9] + '.py').st_mtime, mtime) if not self.module_mtimes.has_key(filename): self.module_mtimes[filename] = mtime elif self.module_mtimes[filename] < mtime: print >> sys.stderr, ( "%s changed; reloading..." % filename) return False return True def watch_file(self, cls, filename): """Watch the named file for changes""" filename = os.path.abspath(filename) if self is None: for instance in cls.instances: instance.watch_file(filename) cls.global_extra_files.append(filename) else: self.extra_files.append(filename) watch_file = classinstancemethod(watch_file) def add_file_callback(self, cls, callback): """Add a callback -- a function that takes no parameters -- that will return a list of filenames to watch for changes.""" if self is None: for instance in cls.instances: instance.add_file_callback(callback) cls.global_file_callbacks.append(callback) else: self.file_callbacks.append(callback) add_file_callback = classinstancemethod(add_file_callback) if sys.platform.startswith('java'): try: from _systemrestart import SystemRestart except ImportError: pass else: class JythonMonitor(Monitor): """ Monitor that utilizes Jython's special ``_systemrestart.SystemRestart`` exception. When raised from the main thread it causes Jython to reload the interpreter in the existing Java process (avoiding startup time). Note that this functionality of Jython is experimental and may change in the future. """ def periodic_reload(self): while True: if not self.check_reload(): raise SystemRestart() time.sleep(self.poll_interval) watch_file = Monitor.watch_file add_file_callback = Monitor.add_file_callback Paste-1.7.5.1/paste/proxy.py0000664000175000017500000002370511354703364014231 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ An application that proxies WSGI requests to a remote server. TODO: * Send ``Via`` header? It's not clear to me this is a Via in the style of a typical proxy. * Other headers or metadata? I put in X-Forwarded-For, but that's it. * Signed data of non-HTTP keys? This would be for things like REMOTE_USER. * Something to indicate what the original URL was? The original host, scheme, and base path. * Rewriting ``Location`` headers? mod_proxy does this. * Rewriting body? (Probably not on this one -- that can be done with a different middleware that wraps this middleware) * Example:: use = egg:Paste#proxy address = http://server3:8680/exist/rest/db/orgs/sch/config/ allowed_request_methods = GET """ import httplib import urlparse import urllib from paste import httpexceptions from paste.util.converters import aslist # Remove these headers from response (specify lower case header # names): filtered_headers = ( 'transfer-encoding', 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailers', 'upgrade', ) class Proxy(object): def __init__(self, address, allowed_request_methods=(), suppress_http_headers=()): self.address = address self.parsed = urlparse.urlsplit(address) self.scheme = self.parsed[0].lower() self.host = self.parsed[1] self.path = self.parsed[2] self.allowed_request_methods = [ x.lower() for x in allowed_request_methods if x] self.suppress_http_headers = [ x.lower() for x in suppress_http_headers if x] def __call__(self, environ, start_response): if (self.allowed_request_methods and environ['REQUEST_METHOD'].lower() not in self.allowed_request_methods): return httpexceptions.HTTPBadRequest("Disallowed")(environ, start_response) if self.scheme == 'http': ConnClass = httplib.HTTPConnection elif self.scheme == 'https': ConnClass = httplib.HTTPSConnection else: raise ValueError( "Unknown scheme for %r: %r" % (self.address, self.scheme)) conn = ConnClass(self.host) headers = {} for key, value in environ.items(): if key.startswith('HTTP_'): key = key[5:].lower().replace('_', '-') if key == 'host' or key in self.suppress_http_headers: continue headers[key] = value headers['host'] = self.host if 'REMOTE_ADDR' in environ: headers['x-forwarded-for'] = environ['REMOTE_ADDR'] if environ.get('CONTENT_TYPE'): headers['content-type'] = environ['CONTENT_TYPE'] if environ.get('CONTENT_LENGTH'): if environ['CONTENT_LENGTH'] == '-1': # This is a special case, where the content length is basically undetermined body = environ['wsgi.input'].read(-1) headers['content-length'] = str(len(body)) else: headers['content-length'] = environ['CONTENT_LENGTH'] length = int(environ['CONTENT_LENGTH']) body = environ['wsgi.input'].read(length) else: body = '' path_info = urllib.quote(environ['PATH_INFO']) if self.path: request_path = path_info if request_path and request_path[0] == '/': request_path = request_path[1:] path = urlparse.urljoin(self.path, request_path) else: path = path_info if environ.get('QUERY_STRING'): path += '?' + environ['QUERY_STRING'] conn.request(environ['REQUEST_METHOD'], path, body, headers) res = conn.getresponse() headers_out = parse_headers(res.msg) status = '%s %s' % (res.status, res.reason) start_response(status, headers_out) # @@: Default? length = res.getheader('content-length') if length is not None: body = res.read(int(length)) else: body = res.read() conn.close() return [body] def make_proxy(global_conf, address, allowed_request_methods="", suppress_http_headers=""): """ Make a WSGI application that proxies to another address: ``address`` the full URL ending with a trailing ``/`` ``allowed_request_methods``: a space seperated list of request methods (e.g., ``GET POST``) ``suppress_http_headers`` a space seperated list of http headers (lower case, without the leading ``http_``) that should not be passed on to target host """ allowed_request_methods = aslist(allowed_request_methods) suppress_http_headers = aslist(suppress_http_headers) return Proxy( address, allowed_request_methods=allowed_request_methods, suppress_http_headers=suppress_http_headers) class TransparentProxy(object): """ A proxy that sends the request just as it was given, including respecting HTTP_HOST, wsgi.url_scheme, etc. This is a way of translating WSGI requests directly to real HTTP requests. All information goes in the environment; modify it to modify the way the request is made. If you specify ``force_host`` (and optionally ``force_scheme``) then HTTP_HOST won't be used to determine where to connect to; instead a specific host will be connected to, but the ``Host`` header in the request will remain intact. """ def __init__(self, force_host=None, force_scheme='http'): self.force_host = force_host self.force_scheme = force_scheme def __repr__(self): return '<%s %s force_host=%r force_scheme=%r>' % ( self.__class__.__name__, hex(id(self)), self.force_host, self.force_scheme) def __call__(self, environ, start_response): scheme = environ['wsgi.url_scheme'] if self.force_host is None: conn_scheme = scheme else: conn_scheme = self.force_scheme if conn_scheme == 'http': ConnClass = httplib.HTTPConnection elif conn_scheme == 'https': ConnClass = httplib.HTTPSConnection else: raise ValueError( "Unknown scheme %r" % scheme) if 'HTTP_HOST' not in environ: raise ValueError( "WSGI environ must contain an HTTP_HOST key") host = environ['HTTP_HOST'] if self.force_host is None: conn_host = host else: conn_host = self.force_host conn = ConnClass(conn_host) headers = {} for key, value in environ.items(): if key.startswith('HTTP_'): key = key[5:].lower().replace('_', '-') headers[key] = value headers['host'] = host if 'REMOTE_ADDR' in environ and 'HTTP_X_FORWARDED_FOR' not in environ: headers['x-forwarded-for'] = environ['REMOTE_ADDR'] if environ.get('CONTENT_TYPE'): headers['content-type'] = environ['CONTENT_TYPE'] if environ.get('CONTENT_LENGTH'): length = int(environ['CONTENT_LENGTH']) body = environ['wsgi.input'].read(length) if length == -1: environ['CONTENT_LENGTH'] = str(len(body)) elif 'CONTENT_LENGTH' not in environ: body = '' length = 0 else: body = '' length = 0 path = (environ.get('SCRIPT_NAME', '') + environ.get('PATH_INFO', '')) path = urllib.quote(path) if 'QUERY_STRING' in environ: path += '?' + environ['QUERY_STRING'] conn.request(environ['REQUEST_METHOD'], path, body, headers) res = conn.getresponse() headers_out = parse_headers(res.msg) status = '%s %s' % (res.status, res.reason) start_response(status, headers_out) # @@: Default? length = res.getheader('content-length') if length is not None: body = res.read(int(length)) else: body = res.read() conn.close() return [body] def parse_headers(message): """ Turn a Message object into a list of WSGI-style headers. """ headers_out = [] for full_header in message.headers: if not full_header: # Shouldn't happen, but we'll just ignore continue if full_header[0].isspace(): # Continuation line, add to the last header if not headers_out: raise ValueError( "First header starts with a space (%r)" % full_header) last_header, last_value = headers_out.pop() value = last_value + ' ' + full_header.strip() headers_out.append((last_header, value)) continue try: header, value = full_header.split(':', 1) except: raise ValueError("Invalid header: %r" % full_header) value = value.strip() if header.lower() not in filtered_headers: headers_out.append((header, value)) return headers_out def make_transparent_proxy( global_conf, force_host=None, force_scheme='http'): """ Create a proxy that connects to a specific host, but does absolutely no other filtering, including the Host header. """ return TransparentProxy(force_host=force_host, force_scheme=force_scheme) Paste-1.7.5.1/paste/debug/0000775000175000017500000000000011437657640013564 5ustar ianbianbPaste-1.7.5.1/paste/debug/watchthreads.py0000664000175000017500000002515711354703364016622 0ustar ianbianb""" Watches the key ``paste.httpserver.thread_pool`` to see how many threads there are and report on any wedged threads. """ import sys import cgi import time import traceback from cStringIO import StringIO from thread import get_ident from paste import httpexceptions from paste.request import construct_url, parse_formvars from paste.util.template import HTMLTemplate, bunch page_template = HTMLTemplate(''' {{title}}

{{title}}

{{if kill_thread_id}}
Thread {{kill_thread_id}} killed
{{endif}}
Pool size: {{nworkers}} {{if actual_workers > nworkers}} + {{actual_workers-nworkers}} extra {{endif}} ({{nworkers_used}} used including current request)
idle: {{len(track_threads["idle"])}}, busy: {{len(track_threads["busy"])}}, hung: {{len(track_threads["hung"])}}, dying: {{len(track_threads["dying"])}}, zombie: {{len(track_threads["zombie"])}}
{{for thread in threads}}
Thread {{if thread.thread_id == this_thread_id}} (this request) {{endif}} {{thread.thread_id}} {{if allow_kill}}
{{endif}}
Time processing request {{thread.time_html|html}}
URI {{if thread.uri == 'unknown'}} unknown {{else}}{{thread.uri_short}} {{endif}}
▸ Show environ {{if thread.traceback}} ▸ Show traceback {{endif}}
{{endfor}} ''', name='watchthreads.page_template') class WatchThreads(object): """ Application that watches the threads in ``paste.httpserver``, showing the length each thread has been working on a request. If allow_kill is true, then you can kill errant threads through this application. This application can expose private information (specifically in the environment, like cookies), so it should be protected. """ def __init__(self, allow_kill=False): self.allow_kill = allow_kill def __call__(self, environ, start_response): if 'paste.httpserver.thread_pool' not in environ: start_response('403 Forbidden', [('Content-type', 'text/plain')]) return ['You must use the threaded Paste HTTP server to use this application'] if environ.get('PATH_INFO') == '/kill': return self.kill(environ, start_response) else: return self.show(environ, start_response) def show(self, environ, start_response): start_response('200 OK', [('Content-type', 'text/html')]) form = parse_formvars(environ) if form.get('kill'): kill_thread_id = form['kill'] else: kill_thread_id = None thread_pool = environ['paste.httpserver.thread_pool'] nworkers = thread_pool.nworkers now = time.time() workers = thread_pool.worker_tracker.items() workers.sort(key=lambda v: v[1][0]) threads = [] for thread_id, (time_started, worker_environ) in workers: thread = bunch() threads.append(thread) if worker_environ: thread.uri = construct_url(worker_environ) else: thread.uri = 'unknown' thread.thread_id = thread_id thread.time_html = format_time(now-time_started) thread.uri_short = shorten(thread.uri) thread.environ = worker_environ thread.traceback = traceback_thread(thread_id) page = page_template.substitute( title="Thread Pool Worker Tracker", nworkers=nworkers, actual_workers=len(thread_pool.workers), nworkers_used=len(workers), script_name=environ['SCRIPT_NAME'], kill_thread_id=kill_thread_id, allow_kill=self.allow_kill, threads=threads, this_thread_id=get_ident(), track_threads=thread_pool.track_threads()) return [page] def kill(self, environ, start_response): if not self.allow_kill: exc = httpexceptions.HTTPForbidden( 'Killing threads has not been enabled. Shame on you ' 'for trying!') return exc(environ, start_response) vars = parse_formvars(environ) thread_id = int(vars['thread_id']) thread_pool = environ['paste.httpserver.thread_pool'] if thread_id not in thread_pool.worker_tracker: exc = httpexceptions.PreconditionFailed( 'You tried to kill thread %s, but it is not working on ' 'any requests' % thread_id) return exc(environ, start_response) thread_pool.kill_worker(thread_id) script_name = environ['SCRIPT_NAME'] or '/' exc = httpexceptions.HTTPFound( headers=[('Location', script_name+'?kill=%s' % thread_id)]) return exc(environ, start_response) def traceback_thread(thread_id): """ Returns a plain-text traceback of the given thread, or None if it can't get a traceback. """ if not hasattr(sys, '_current_frames'): # Only 2.5 has support for this, with this special function return None frames = sys._current_frames() if not thread_id in frames: return None frame = frames[thread_id] out = StringIO() traceback.print_stack(frame, file=out) return out.getvalue() hide_keys = ['paste.httpserver.thread_pool'] def format_environ(environ): if environ is None: return environ_template.substitute( key='---', value='No environment registered for this thread yet') environ_rows = [] for key, value in sorted(environ.items()): if key in hide_keys: continue try: if key.upper() != key: value = repr(value) environ_rows.append( environ_template.substitute( key=cgi.escape(str(key)), value=cgi.escape(str(value)))) except Exception, e: environ_rows.append( environ_template.substitute( key=cgi.escape(str(key)), value='Error in repr(): %s' % e)) return ''.join(environ_rows) def format_time(time_length): if time_length >= 60*60: # More than an hour time_string = '%i:%02i:%02i' % (int(time_length/60/60), int(time_length/60) % 60, time_length % 60) elif time_length >= 120: time_string = '%i:%02i' % (int(time_length/60), time_length % 60) elif time_length > 60: time_string = '%i sec' % time_length elif time_length > 1: time_string = '%0.1f sec' % time_length else: time_string = '%0.2f sec' % time_length if time_length < 5: return time_string elif time_length < 120: return '%s' % time_string else: return '%s' % time_string def shorten(s): if len(s) > 60: return s[:40]+'...'+s[-10:] else: return s def make_watch_threads(global_conf, allow_kill=False): from paste.deploy.converters import asbool return WatchThreads(allow_kill=asbool(allow_kill)) make_watch_threads.__doc__ = WatchThreads.__doc__ def make_bad_app(global_conf, pause=0): pause = int(pause) def bad_app(environ, start_response): import thread if pause: time.sleep(pause) else: count = 0 while 1: print "I'm alive %s (%s)" % (count, thread.get_ident()) time.sleep(10) count += 1 start_response('200 OK', [('content-type', 'text/plain')]) return ['OK, paused %s seconds' % pause] return bad_app Paste-1.7.5.1/paste/debug/debugapp.py0000775000175000017500000000541211354703364015723 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php # (c) 2005 Clark C. Evans # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # This code was written with funding by http://prometheusresearch.com """ Various Applications for Debugging/Testing Purposes """ import time __all__ = ['SimpleApplication', 'SlowConsumer'] class SimpleApplication(object): """ Produces a simple web page """ def __call__(self, environ, start_response): body = "simple" start_response("200 OK", [('Content-Type', 'text/html'), ('Content-Length', str(len(body)))]) return [body] class SlowConsumer(object): """ Consumes an upload slowly... NOTE: This should use the iterator form of ``wsgi.input``, but it isn't implemented in paste.httpserver. """ def __init__(self, chunk_size = 4096, delay = 1, progress = True): self.chunk_size = chunk_size self.delay = delay self.progress = True def __call__(self, environ, start_response): size = 0 total = environ.get('CONTENT_LENGTH') if total: remaining = int(total) while remaining > 0: if self.progress: print "%s of %s remaining" % (remaining, total) if remaining > 4096: chunk = environ['wsgi.input'].read(4096) else: chunk = environ['wsgi.input'].read(remaining) if not chunk: break size += len(chunk) remaining -= len(chunk) if self.delay: time.sleep(self.delay) body = "%d bytes" % size else: body = ('\n' '
\n' '\n' '\n' '
\n') print "bingles" start_response("200 OK", [('Content-Type', 'text/html'), ('Content-Length', len(body))]) return [body] def make_test_app(global_conf): return SimpleApplication() make_test_app.__doc__ = SimpleApplication.__doc__ def make_slow_app(global_conf, chunk_size=4096, delay=1, progress=True): from paste.deploy.converters import asbool return SlowConsumer( chunk_size=int(chunk_size), delay=int(delay), progress=asbool(progress)) make_slow_app.__doc__ = SlowConsumer.__doc__ Paste-1.7.5.1/paste/debug/wdg_validate.py0000664000175000017500000001037711437657640016600 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Middleware that tests the validity of all generated HTML using the `WDG HTML Validator `_ """ from cStringIO import StringIO try: import subprocess except ImportError: from paste.util import subprocess24 as subprocess from paste.response import header_value import re import cgi __all__ = ['WDGValidateMiddleware'] class WDGValidateMiddleware(object): """ Middleware that checks HTML and appends messages about the validity of the HTML. Uses: http://www.htmlhelp.com/tools/validator/ -- interacts with the command line client. Use the configuration ``wdg_path`` to override the path (default: looks for ``validate`` in $PATH). To install, in your web context's __init__.py:: def urlparser_wrap(environ, start_response, app): return wdg_validate.WDGValidateMiddleware(app)( environ, start_response) Or in your configuration:: middleware.append('paste.wdg_validate.WDGValidateMiddleware') """ _end_body_regex = re.compile(r'', re.I) def __init__(self, app, global_conf=None, wdg_path='validate'): self.app = app self.wdg_path = wdg_path def __call__(self, environ, start_response): output = StringIO() response = [] def writer_start_response(status, headers, exc_info=None): response.extend((status, headers)) start_response(status, headers, exc_info) return output.write app_iter = self.app(environ, writer_start_response) try: for s in app_iter: output.write(s) finally: if hasattr(app_iter, 'close'): app_iter.close() page = output.getvalue() status, headers = response v = header_value(headers, 'content-type') or '' if (not v.startswith('text/html') and not v.startswith('text/xhtml') and not v.startswith('application/xhtml')): # Can't validate # @@: Should validate CSS too... but using what? return [page] ops = [] if v.startswith('text/xhtml+xml'): ops.append('--xml') # @@: Should capture encoding too html_errors = self.call_wdg_validate( self.wdg_path, ops, page) if html_errors: page = self.add_error(page, html_errors)[0] headers.remove( ('Content-Length', str(header_value(headers, 'content-length')))) headers.append(('Content-Length', str(len(page)))) return [page] def call_wdg_validate(self, wdg_path, ops, page): if subprocess is None: raise ValueError( "This middleware requires the subprocess module from " "Python 2.4") proc = subprocess.Popen([wdg_path] + ops, shell=False, close_fds=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT) stdout = proc.communicate(page)[0] proc.wait() return stdout def add_error(self, html_page, html_errors): add_text = ('
%s
' % cgi.escape(html_errors)) match = self._end_body_regex.search(html_page) if match: return [html_page[:match.start()] + add_text + html_page[match.start():]] else: return [html_page + add_text] def make_wdg_validate_middleware( app, global_conf, wdg_path='validate'): """ Wraps the application in the WDG validator from http://www.htmlhelp.com/tools/validator/ Validation errors are appended to the text of each page. You can configure this by giving the path to the validate executable (by default picked up from $PATH) """ return WDGValidateMiddleware( app, global_conf, wdg_path=wdg_path) Paste-1.7.5.1/paste/debug/testserver.py0000775000175000017500000000647111354703364016350 0ustar ianbianb# (c) 2005 Clark C. Evans # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # This code was written with funding by http://prometheusresearch.com """ WSGI Test Server This builds upon paste.util.baseserver to customize it for regressions where using raw_interactive won't do. """ import time from paste.httpserver import * class WSGIRegressionServer(WSGIServer): """ A threaded WSGIServer for use in regression testing. To use this module, call serve(application, regression=True), and then call server.accept() to let it handle one request. When finished, use server.stop() to shutdown the server. Note that all pending requests are processed before the server shuts down. """ defaulttimeout = 10 def __init__ (self, *args, **kwargs): WSGIServer.__init__(self, *args, **kwargs) self.stopping = [] self.pending = [] self.timeout = self.defaulttimeout # this is a local connection, be quick self.socket.settimeout(2) def serve_forever(self): from threading import Thread thread = Thread(target=self.serve_pending) thread.start() def reset_expires(self): if self.timeout: self.expires = time.time() + self.timeout def close_request(self, *args, **kwargs): WSGIServer.close_request(self, *args, **kwargs) self.pending.pop() self.reset_expires() def serve_pending(self): self.reset_expires() while not self.stopping or self.pending: now = time.time() if now > self.expires and self.timeout: # note regression test doesn't handle exceptions in # threads very well; so we just print and exit print "\nWARNING: WSGIRegressionServer timeout exceeded\n" break if self.pending: self.handle_request() time.sleep(.1) def stop(self): """ stop the server (called from tester's thread) """ self.stopping.append(True) def accept(self, count = 1): """ accept another request (called from tester's thread) """ assert not self.stopping [self.pending.append(True) for x in range(count)] def serve(application, host=None, port=None, handler=None): server = WSGIRegressionServer(application, host, port, handler) print "serving on %s:%s" % server.server_address server.serve_forever() return server if __name__ == '__main__': import urllib from paste.wsgilib import dump_environ server = serve(dump_environ) baseuri = ("http://%s:%s" % server.server_address) def fetch(path): # tell the server to humor exactly one more request server.accept(1) # not needed; but this is what you do if the server # may not respond in a resonable time period import socket socket.setdefaulttimeout(5) # build a uri, fetch and return return urllib.urlopen(baseuri + path).read() assert "PATH_INFO: /foo" in fetch("/foo") assert "PATH_INFO: /womble" in fetch("/womble") # ok, let's make one more final request... server.accept(1) # and then schedule a stop() server.stop() # and then... fetch it... urllib.urlopen(baseuri) Paste-1.7.5.1/paste/debug/profile.py0000664000175000017500000001671111354703364015575 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Middleware that profiles the request and displays profiling information at the bottom of each page. """ import sys import os import hotshot import hotshot.stats import threading import cgi import time from cStringIO import StringIO from paste import response __all__ = ['ProfileMiddleware', 'profile_decorator'] class ProfileMiddleware(object): """ Middleware that profiles all requests. All HTML pages will have profiling information appended to them. The data is isolated to that single request, and does not include data from previous requests. This uses the ``hotshot`` module, which affects performance of the application. It also runs in a single-threaded mode, so it is only usable in development environments. """ style = ('clear: both; background-color: #ff9; color: #000; ' 'border: 2px solid #000; padding: 5px;') def __init__(self, app, global_conf=None, log_filename='profile.log.tmp', limit=40): self.app = app self.lock = threading.Lock() self.log_filename = log_filename self.limit = limit def __call__(self, environ, start_response): catch_response = [] body = [] def replace_start_response(status, headers, exc_info=None): catch_response.extend([status, headers]) start_response(status, headers, exc_info) return body.append def run_app(): app_iter = self.app(environ, replace_start_response) try: body.extend(app_iter) finally: if hasattr(app_iter, 'close'): app_iter.close() self.lock.acquire() try: prof = hotshot.Profile(self.log_filename) prof.addinfo('URL', environ.get('PATH_INFO', '')) try: prof.runcall(run_app) finally: prof.close() body = ''.join(body) headers = catch_response[1] content_type = response.header_value(headers, 'content-type') if content_type is None or not content_type.startswith('text/html'): # We can't add info to non-HTML output return [body] stats = hotshot.stats.load(self.log_filename) stats.strip_dirs() stats.sort_stats('time', 'calls') output = capture_output(stats.print_stats, self.limit) output_callers = capture_output( stats.print_callers, self.limit) body += '
%s\n%s
' % ( self.style, cgi.escape(output), cgi.escape(output_callers)) return [body] finally: self.lock.release() def capture_output(func, *args, **kw): # Not threadsafe! (that's okay when ProfileMiddleware uses it, # though, since it synchronizes itself.) out = StringIO() old_stdout = sys.stdout sys.stdout = out try: func(*args, **kw) finally: sys.stdout = old_stdout return out.getvalue() def profile_decorator(**options): """ Profile a single function call. Used around a function, like:: @profile_decorator(options...) def ... All calls to the function will be profiled. The options are all keywords, and are: log_file: The filename to log to (or ``'stdout'`` or ``'stderr'``). Default: stderr. display_limit: Only show the top N items, default: 20. sort_stats: A list of string-attributes to sort on. Default ``('time', 'calls')``. strip_dirs: Strip directories/module names from files? Default True. add_info: If given, this info will be added to the report (for your own tracking). Default: none. log_filename: The temporary filename to log profiling data to. Default; ``./profile_data.log.tmp`` no_profile: If true, then don't actually profile anything. Useful for conditional profiling. """ if options.get('no_profile'): def decorator(func): return func return decorator def decorator(func): def replacement(*args, **kw): return DecoratedProfile(func, **options)(*args, **kw) return replacement return decorator class DecoratedProfile(object): lock = threading.Lock() def __init__(self, func, **options): self.func = func self.options = options def __call__(self, *args, **kw): self.lock.acquire() try: return self.profile(self.func, *args, **kw) finally: self.lock.release() def profile(self, func, *args, **kw): ops = self.options prof_filename = ops.get('log_filename', 'profile_data.log.tmp') prof = hotshot.Profile(prof_filename) prof.addinfo('Function Call', self.format_function(func, *args, **kw)) if ops.get('add_info'): prof.addinfo('Extra info', ops['add_info']) exc_info = None try: start_time = time.time() try: result = prof.runcall(func, *args, **kw) except: exc_info = sys.exc_info() end_time = time.time() finally: prof.close() stats = hotshot.stats.load(prof_filename) os.unlink(prof_filename) if ops.get('strip_dirs', True): stats.strip_dirs() stats.sort_stats(*ops.get('sort_stats', ('time', 'calls'))) display_limit = ops.get('display_limit', 20) output = capture_output(stats.print_stats, display_limit) output_callers = capture_output( stats.print_callers, display_limit) output_file = ops.get('log_file') if output_file in (None, 'stderr'): f = sys.stderr elif output_file in ('-', 'stdout'): f = sys.stdout else: f = open(output_file, 'a') f.write('\n%s\n' % ('-'*60)) f.write('Date: %s\n' % time.strftime('%c')) f.write('Function call: %s\n' % self.format_function(func, *args, **kw)) f.write('Wall time: %0.2f seconds\n' % (end_time - start_time)) f.write(output) f.write(output_callers) if output_file not in (None, '-', 'stdout', 'stderr'): f.close() if exc_info: # We captured an exception earlier, now we re-raise it raise exc_info[0], exc_info[1], exc_info[2] return result def format_function(self, func, *args, **kw): args = map(repr, args) args.extend( ['%s=%r' % (k, v) for k, v in kw.items()]) return '%s(%s)' % (func.__name__, ', '.join(args)) def make_profile_middleware( app, global_conf, log_filename='profile.log.tmp', limit=40): """ Wrap the application in a component that will profile each request. The profiling data is then appended to the output of each page. Note that this serializes all requests (i.e., removing concurrency). Therefore never use this in production. """ limit = int(limit) return ProfileMiddleware( app, log_filename=log_filename, limit=limit) Paste-1.7.5.1/paste/debug/fsdiff.py0000664000175000017500000003132111354703364015370 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Module to find differences over time in a filesystem Basically this takes a snapshot of a directory, then sees what changes were made. The contents of the files are not checked, so you can detect that the content was changed, but not what the old version of the file was. """ import os from fnmatch import fnmatch from datetime import datetime from paste.util.UserDict24 import IterableUserDict import operator import re __all__ = ['Diff', 'Snapshot', 'File', 'Dir', 'report_expected_diffs', 'show_diff'] class Diff(object): """ Represents the difference between two snapshots """ def __init__(self, before, after): self.before = before self.after = after self._calculate() def _calculate(self): before = self.before.data after = self.after.data self.deleted = {} self.updated = {} self.created = after.copy() for path, f in before.items(): if path not in after: self.deleted[path] = f continue del self.created[path] if f.mtime < after[path].mtime: self.updated[path] = after[path] def __str__(self): return self.report() def report(self, header=True, dates=False): s = [] if header: s.append('Difference in %s from %s to %s:' % (self.before.base_path, self.before.calculated, self.after.calculated)) for name, files, show_size in [ ('created', self.created, True), ('deleted', self.deleted, True), ('updated', self.updated, True)]: if files: s.append('-- %s: -------------------' % name) files = files.items() files.sort() last = '' for path, f in files: t = ' %s' % _space_prefix(last, path, indent=4, include_sep=False) last = path if show_size and f.size != 'N/A': t += ' (%s bytes)' % f.size if dates: parts = [] if self.before.get(path): parts.append(self.before[path].mtime) if self.after.get(path): parts.append(self.after[path].mtime) t += ' (mtime: %s)' % ('->'.join(map(repr, parts))) s.append(t) if len(s) == 1: s.append(' (no changes)') return '\n'.join(s) class Snapshot(IterableUserDict): """ Represents a snapshot of a set of files. Has a dictionary-like interface, keyed relative to ``base_path`` """ def __init__(self, base_path, files=None, ignore_wildcards=(), ignore_paths=(), ignore_hidden=True): self.base_path = base_path self.ignore_wildcards = ignore_wildcards self.ignore_hidden = ignore_hidden self.ignore_paths = ignore_paths self.calculated = None self.data = files or {} if files is None: self.find_files() ############################################################ ## File finding ############################################################ def find_files(self): """ Find all the files under the base path, and put them in ``self.data`` """ self._find_traverse('', self.data) self.calculated = datetime.now() def _ignore_file(self, fn): if fn in self.ignore_paths: return True if self.ignore_hidden and os.path.basename(fn).startswith('.'): return True for pat in self.ignore_wildcards: if fnmatch(fn, pat): return True return False def _ignore_file(self, fn): if fn in self.ignore_paths: return True if self.ignore_hidden and os.path.basename(fn).startswith('.'): return True return False def _find_traverse(self, path, result): full = os.path.join(self.base_path, path) if os.path.isdir(full): if path: # Don't actually include the base path result[path] = Dir(self.base_path, path) for fn in os.listdir(full): fn = os.path.join(path, fn) if self._ignore_file(fn): continue self._find_traverse(fn, result) else: result[path] = File(self.base_path, path) def __repr__(self): return '<%s in %r from %r>' % ( self.__class__.__name__, self.base_path, self.calculated or '(no calculation done)') def compare_expected(self, expected, comparison=operator.eq, differ=None, not_found=None, include_success=False): """ Compares a dictionary of ``path: content`` to the found files. Comparison is done by equality, or the ``comparison(actual_content, expected_content)`` function given. Returns dictionary of differences, keyed by path. Each difference is either noted, or the output of ``differ(actual_content, expected_content)`` is given. If a file does not exist and ``not_found`` is given, then ``not_found(path)`` is put in. """ result = {} for path in expected: orig_path = path path = path.strip('/') if path not in self.data: if not_found: msg = not_found(path) else: msg = 'not found' result[path] = msg continue expected_content = expected[orig_path] file = self.data[path] actual_content = file.bytes if not comparison(actual_content, expected_content): if differ: msg = differ(actual_content, expected_content) else: if len(actual_content) < len(expected_content): msg = 'differ (%i bytes smaller)' % ( len(expected_content) - len(actual_content)) elif len(actual_content) > len(expected_content): msg = 'differ (%i bytes larger)' % ( len(actual_content) - len(expected_content)) else: msg = 'diff (same size)' result[path] = msg elif include_success: result[path] = 'same!' return result def diff_to_now(self): return Diff(self, self.clone()) def clone(self): return self.__class__(base_path=self.base_path, ignore_wildcards=self.ignore_wildcards, ignore_paths=self.ignore_paths, ignore_hidden=self.ignore_hidden) class File(object): """ Represents a single file found as the result of a command. Has attributes: ``path``: The path of the file, relative to the ``base_path`` ``full``: The full path ``stat``: The results of ``os.stat``. Also ``mtime`` and ``size`` contain the ``.st_mtime`` and ``st_size`` of the stat. ``bytes``: The contents of the file. You may use the ``in`` operator with these objects (tested against the contents of the file), and the ``.mustcontain()`` method. """ file = True dir = False def __init__(self, base_path, path): self.base_path = base_path self.path = path self.full = os.path.join(base_path, path) self.stat = os.stat(self.full) self.mtime = self.stat.st_mtime self.size = self.stat.st_size self._bytes = None def bytes__get(self): if self._bytes is None: f = open(self.full, 'rb') self._bytes = f.read() f.close() return self._bytes bytes = property(bytes__get) def __contains__(self, s): return s in self.bytes def mustcontain(self, s): __tracebackhide__ = True bytes = self.bytes if s not in bytes: print 'Could not find %r in:' % s print bytes assert s in bytes def __repr__(self): return '<%s %s:%s>' % ( self.__class__.__name__, self.base_path, self.path) class Dir(File): """ Represents a directory created by a command. """ file = False dir = True def __init__(self, base_path, path): self.base_path = base_path self.path = path self.full = os.path.join(base_path, path) self.size = 'N/A' self.mtime = 'N/A' def __repr__(self): return '<%s %s:%s>' % ( self.__class__.__name__, self.base_path, self.path) def bytes__get(self): raise NotImplementedError( "Directory %r doesn't have content" % self) bytes = property(bytes__get) def _space_prefix(pref, full, sep=None, indent=None, include_sep=True): """ Anything shared by pref and full will be replaced with spaces in full, and full returned. Example:: >>> _space_prefix('/foo/bar', '/foo') ' /bar' """ if sep is None: sep = os.path.sep pref = pref.split(sep) full = full.split(sep) padding = [] while pref and full and pref[0] == full[0]: if indent is None: padding.append(' ' * (len(full[0]) + len(sep))) else: padding.append(' ' * indent) full.pop(0) pref.pop(0) if padding: if include_sep: return ''.join(padding) + sep + sep.join(full) else: return ''.join(padding) + sep.join(full) else: return sep.join(full) def report_expected_diffs(diffs, colorize=False): """ Takes the output of compare_expected, and returns a string description of the differences. """ if not diffs: return 'No differences' diffs = diffs.items() diffs.sort() s = [] last = '' for path, desc in diffs: t = _space_prefix(last, path, indent=4, include_sep=False) if colorize: t = color_line(t, 11) last = path if len(desc.splitlines()) > 1: cur_indent = len(re.search(r'^[ ]*', t).group(0)) desc = indent(cur_indent+2, desc) if colorize: t += '\n' for line in desc.splitlines(): if line.strip().startswith('+'): line = color_line(line, 10) elif line.strip().startswith('-'): line = color_line(line, 9) else: line = color_line(line, 14) t += line+'\n' else: t += '\n' + desc else: t += ' '+desc s.append(t) s.append('Files with differences: %s' % len(diffs)) return '\n'.join(s) def color_code(foreground=None, background=None): """ 0 black 1 red 2 green 3 yellow 4 blue 5 magenta (purple) 6 cyan 7 white (gray) Add 8 to get high-intensity """ if foreground is None and background is None: # Reset return '\x1b[0m' codes = [] if foreground is None: codes.append('[39m') elif foreground > 7: codes.append('[1m') codes.append('[%im' % (22+foreground)) else: codes.append('[%im' % (30+foreground)) if background is None: codes.append('[49m') else: codes.append('[%im' % (40+background)) return '\x1b' + '\x1b'.join(codes) def color_line(line, foreground=None, background=None): match = re.search(r'^(\s*)', line) return (match.group(1) + color_code(foreground, background) + line[match.end():] + color_code()) def indent(indent, text): return '\n'.join( [' '*indent + l for l in text.splitlines()]) def show_diff(actual_content, expected_content): actual_lines = [l.strip() for l in actual_content.splitlines() if l.strip()] expected_lines = [l.strip() for l in expected_content.splitlines() if l.strip()] if len(actual_lines) == len(expected_lines) == 1: return '%r not %r' % (actual_lines[0], expected_lines[0]) if not actual_lines: return 'Empty; should have:\n'+expected_content import difflib return '\n'.join(difflib.ndiff(actual_lines, expected_lines)) Paste-1.7.5.1/paste/debug/prints.py0000664000175000017500000001267111354703364015455 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Middleware that displays everything that is printed inline in application pages. Anything printed during the request will get captured and included on the page. It will usually be included as a floating element in the top right hand corner of the page. If you want to override this you can include a tag in your template where it will be placed::


You might want to include ``style="white-space: normal"``, as all the
whitespace will be quoted, and this allows the text to wrap if
necessary.

"""

from cStringIO import StringIO
import re
import cgi
from paste.util import threadedprint
from paste import wsgilib
from paste import response
import sys

_threadedprint_installed = False

__all__ = ['PrintDebugMiddleware']

class TeeFile(object):

    def __init__(self, files):
        self.files = files

    def write(self, v):
        if isinstance(v, unicode):
            # WSGI is picky in this case
            v = str(v)
        for file in self.files:
            file.write(v)

class PrintDebugMiddleware(object):

    """
    This middleware captures all the printed statements, and inlines
    them in HTML pages, so that you can see all the (debug-intended)
    print statements in the page itself.

    There are two keys added to the environment to control this:
    ``environ['paste.printdebug_listeners']`` is a list of functions
    that will be called everytime something is printed.

    ``environ['paste.remove_printdebug']`` is a function that, if
    called, will disable printing of output for that request.

    If you have ``replace_stdout=True`` then stdout is replaced, not
    captured.
    """

    log_template = (
        '
'
        'Log messages
' '%s
') def __init__(self, app, global_conf=None, force_content_type=False, print_wsgi_errors=True, replace_stdout=False): # @@: global_conf should be handled separately and only for # the entry point self.app = app self.force_content_type = force_content_type if isinstance(print_wsgi_errors, basestring): from paste.deploy.converters import asbool print_wsgi_errors = asbool(print_wsgi_errors) self.print_wsgi_errors = print_wsgi_errors self.replace_stdout = replace_stdout self._threaded_print_stdout = None def __call__(self, environ, start_response): global _threadedprint_installed if environ.get('paste.testing'): # In a testing environment this interception isn't # useful: return self.app(environ, start_response) if (not _threadedprint_installed or self._threaded_print_stdout is not sys.stdout): # @@: Not strictly threadsafe _threadedprint_installed = True threadedprint.install(leave_stdout=not self.replace_stdout) self._threaded_print_stdout = sys.stdout removed = [] def remove_printdebug(): removed.append(None) environ['paste.remove_printdebug'] = remove_printdebug logged = StringIO() listeners = [logged] environ['paste.printdebug_listeners'] = listeners if self.print_wsgi_errors: listeners.append(environ['wsgi.errors']) replacement_stdout = TeeFile(listeners) threadedprint.register(replacement_stdout) try: status, headers, body = wsgilib.intercept_output( environ, self.app) if status is None: # Some error occurred status = '500 Server Error' headers = [('Content-type', 'text/html')] start_response(status, headers) if not body: body = 'An error occurred' content_type = response.header_value(headers, 'content-type') if (removed or (not self.force_content_type and (not content_type or not content_type.startswith('text/html')))): if replacement_stdout == logged: # Then the prints will be lost, unless... environ['wsgi.errors'].write(logged.getvalue()) start_response(status, headers) return [body] response.remove_header(headers, 'content-length') body = self.add_log(body, logged.getvalue()) start_response(status, headers) return [body] finally: threadedprint.deregister() _body_re = re.compile(r']*>', re.I) _explicit_re = re.compile(r']*id="paste-debug-prints".*?>', re.I+re.S) def add_log(self, html, log): if not log: return html text = cgi.escape(log) text = text.replace('\n', '
') text = text.replace(' ', '  ') match = self._explicit_re.search(html) if not match: text = self.log_template % text match = self._body_re.search(html) if not match: return text + html else: return html[:match.end()] + text + html[match.end():] Paste-1.7.5.1/paste/debug/__init__.py0000664000175000017500000000033511354703364015667 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Package for debugging and development tools """ Paste-1.7.5.1/paste/debug/doctest_webapp.py0000775000175000017500000003524211354703364017143 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php #!/usr/bin/env python2.4 # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ These are functions for use when doctest-testing a document. """ try: import subprocess except ImportError: from paste.util import subprocess24 as subprocess import doctest import os import sys import shutil import re import cgi import rfc822 from cStringIO import StringIO from paste.util import PySourceColor here = os.path.abspath(__file__) paste_parent = os.path.dirname( os.path.dirname(os.path.dirname(here))) def run(command): data = run_raw(command) if data: print data def run_raw(command): """ Runs the string command, returns any output. """ proc = subprocess.Popen(command, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=_make_env()) data = proc.stdout.read() proc.wait() while data.endswith('\n') or data.endswith('\r'): data = data[:-1] if data: data = '\n'.join( [l for l in data.splitlines() if l]) return data else: return '' def run_command(command, name, and_print=False): output = run_raw(command) data = '$ %s\n%s' % (command, output) show_file('shell-command', name, description='shell transcript', data=data) if and_print and output: print output def _make_env(): env = os.environ.copy() env['PATH'] = (env.get('PATH', '') + ':' + os.path.join(paste_parent, 'scripts') + ':' + os.path.join(paste_parent, 'paste', '3rd-party', 'sqlobject-files', 'scripts')) env['PYTHONPATH'] = (env.get('PYTHONPATH', '') + ':' + paste_parent) return env def clear_dir(dir): """ Clears (deletes) the given directory """ shutil.rmtree(dir, True) def ls(dir=None, recurse=False, indent=0): """ Show a directory listing """ dir = dir or os.getcwd() fns = os.listdir(dir) fns.sort() for fn in fns: full = os.path.join(dir, fn) if os.path.isdir(full): fn = fn + '/' print ' '*indent + fn if os.path.isdir(full) and recurse: ls(dir=full, recurse=True, indent=indent+2) default_app = None default_url = None def set_default_app(app, url): global default_app global default_url default_app = app default_url = url def resource_filename(fn): """ Returns the filename of the resource -- generally in the directory resources/DocumentName/fn """ return os.path.join( os.path.dirname(sys.testing_document_filename), 'resources', os.path.splitext(os.path.basename(sys.testing_document_filename))[0], fn) def show(path_info, example_name): fn = resource_filename(example_name + '.html') out = StringIO() assert default_app is not None, ( "No default_app set") url = default_url + path_info out.write('%s
\n' % (url, url)) out.write('
\n') proc = subprocess.Popen( ['paster', 'serve' '--server=console', '--no-verbose', '--url=' + path_info], stderr=subprocess.PIPE, stdout=subprocess.PIPE, env=_make_env()) stdout, errors = proc.communicate() stdout = StringIO(stdout) headers = rfc822.Message(stdout) content = stdout.read() for header, value in headers.items(): if header.lower() == 'status' and int(value.split()[0]) == 200: continue if header.lower() in ('content-type', 'content-length'): continue if (header.lower() == 'set-cookie' and value.startswith('_SID_')): continue out.write('%s: %s
\n' % (header, value)) lines = [l for l in content.splitlines() if l.strip()] for line in lines: out.write(line + '\n') if errors: out.write('
%s
' % errors) out.write('
\n') result = out.getvalue() if not os.path.exists(fn): f = open(fn, 'wb') f.write(result) f.close() else: f = open(fn, 'rb') expected = f.read() f.close() if not html_matches(expected, result): print 'Pages did not match. Expected from %s:' % fn print '-'*60 print expected print '='*60 print 'Actual output:' print '-'*60 print result def html_matches(pattern, text): regex = re.escape(pattern) regex = regex.replace(r'\.\.\.', '.*') regex = re.sub(r'0x[0-9a-f]+', '.*', regex) regex = '^%s$' % regex return re.search(regex, text) def convert_docstring_string(data): if data.startswith('\n'): data = data[1:] lines = data.splitlines() new_lines = [] for line in lines: if line.rstrip() == '.': new_lines.append('') else: new_lines.append(line) data = '\n'.join(new_lines) + '\n' return data def create_file(path, version, data): data = convert_docstring_string(data) write_data(path, data) show_file(path, version) def append_to_file(path, version, data): data = convert_docstring_string(data) f = open(path, 'a') f.write(data) f.close() # I think these appends can happen so quickly (in less than a second) # that the .pyc file doesn't appear to be expired, even though it # is after we've made this change; so we have to get rid of the .pyc # file: if path.endswith('.py'): pyc_file = path + 'c' if os.path.exists(pyc_file): os.unlink(pyc_file) show_file(path, version, description='added to %s' % path, data=data) def show_file(path, version, description=None, data=None): ext = os.path.splitext(path)[1] if data is None: f = open(path, 'rb') data = f.read() f.close() if ext == '.py': html = ('
%s
' % PySourceColor.str2html(data, PySourceColor.dark)) else: html = '
%s
' % cgi.escape(data, 1) html = '%s
%s' % ( description or path, html) write_data(resource_filename('%s.%s.gen.html' % (path, version)), html) def call_source_highlight(input, format): proc = subprocess.Popen(['source-highlight', '--out-format=html', '--no-doc', '--css=none', '--src-lang=%s' % format], shell=False, stdout=subprocess.PIPE) stdout, stderr = proc.communicate(input) result = stdout proc.wait() return result def write_data(path, data): dir = os.path.dirname(os.path.abspath(path)) if not os.path.exists(dir): os.makedirs(dir) f = open(path, 'wb') f.write(data) f.close() def change_file(path, changes): f = open(os.path.abspath(path), 'rb') lines = f.readlines() f.close() for change_type, line, text in changes: if change_type == 'insert': lines[line:line] = [text] elif change_type == 'delete': lines[line:text] = [] else: assert 0, ( "Unknown change_type: %r" % change_type) f = open(path, 'wb') f.write(''.join(lines)) f.close() class LongFormDocTestParser(doctest.DocTestParser): """ This parser recognizes some reST comments as commands, without prompts or expected output, like: .. run: do_this(... ...) """ _EXAMPLE_RE = re.compile(r""" # Source consists of a PS1 line followed by zero or more PS2 lines. (?: (?P (?:^(?P [ ]*) >>> .*) # PS1 line (?:\n [ ]* \.\.\. .*)*) # PS2 lines \n? # Want consists of any non-blank lines that do not start with PS1. (?P (?:(?![ ]*$) # Not a blank line (?![ ]*>>>) # Not a line starting with PS1 .*$\n? # But any other line )*)) | (?: # This is for longer commands that are prefixed with a reST # comment like '.. run:' (two colons makes that a directive). # These commands cannot have any output. (?:^\.\.[ ]*(?Prun):[ ]*\n) # Leading command/command (?:[ ]*\n)? # Blank line following (?P (?:(?P [ ]+)[^ ].*$) (?:\n [ ]+ .*)*) ) | (?: # This is for shell commands (?P (?:^(P [ ]*) [$] .*) # Shell line (?:\n [ ]* [>] .*)*) # Continuation \n? # Want consists of any non-blank lines that do not start with $ (?P (?:(?![ ]*$) (?![ ]*[$]$) .*$\n? )*)) """, re.MULTILINE | re.VERBOSE) def _parse_example(self, m, name, lineno): r""" Given a regular expression match from `_EXAMPLE_RE` (`m`), return a pair `(source, want)`, where `source` is the matched example's source code (with prompts and indentation stripped); and `want` is the example's expected output (with indentation stripped). `name` is the string's name, and `lineno` is the line number where the example starts; both are used for error messages. >>> def parseit(s): ... p = LongFormDocTestParser() ... return p._parse_example(p._EXAMPLE_RE.search(s), '', 1) >>> parseit('>>> 1\n1') ('1', {}, '1', None) >>> parseit('>>> (1\n... +1)\n2') ('(1\n+1)', {}, '2', None) >>> parseit('.. run:\n\n test1\n test2\n') ('test1\ntest2', {}, '', None) """ # Get the example's indentation level. runner = m.group('run') or '' indent = len(m.group('%sindent' % runner)) # Divide source into lines; check that they're properly # indented; and then strip their indentation & prompts. source_lines = m.group('%ssource' % runner).split('\n') if runner: self._check_prefix(source_lines[1:], ' '*indent, name, lineno) else: self._check_prompt_blank(source_lines, indent, name, lineno) self._check_prefix(source_lines[2:], ' '*indent + '.', name, lineno) if runner: source = '\n'.join([sl[indent:] for sl in source_lines]) else: source = '\n'.join([sl[indent+4:] for sl in source_lines]) if runner: want = '' exc_msg = None else: # Divide want into lines; check that it's properly indented; and # then strip the indentation. Spaces before the last newline should # be preserved, so plain rstrip() isn't good enough. want = m.group('want') want_lines = want.split('\n') if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): del want_lines[-1] # forget final newline & spaces after it self._check_prefix(want_lines, ' '*indent, name, lineno + len(source_lines)) want = '\n'.join([wl[indent:] for wl in want_lines]) # If `want` contains a traceback message, then extract it. m = self._EXCEPTION_RE.match(want) if m: exc_msg = m.group('msg') else: exc_msg = None # Extract options from the source. options = self._find_options(source, name, lineno) return source, options, want, exc_msg def parse(self, string, name=''): """ Divide the given string into examples and intervening text, and return them as a list of alternating Examples and strings. Line numbers for the Examples are 0-based. The optional argument `name` is a name identifying this string, and is only used for error messages. """ string = string.expandtabs() # If all lines begin with the same indentation, then strip it. min_indent = self._min_indent(string) if min_indent > 0: string = '\n'.join([l[min_indent:] for l in string.split('\n')]) output = [] charno, lineno = 0, 0 # Find all doctest examples in the string: for m in self._EXAMPLE_RE.finditer(string): # Add the pre-example text to `output`. output.append(string[charno:m.start()]) # Update lineno (lines before this example) lineno += string.count('\n', charno, m.start()) # Extract info from the regexp match. (source, options, want, exc_msg) = \ self._parse_example(m, name, lineno) # Create an Example, and add it to the list. if not self._IS_BLANK_OR_COMMENT(source): # @@: Erg, this is the only line I need to change... output.append(doctest.Example( source, want, exc_msg, lineno=lineno, indent=min_indent+len(m.group('indent') or m.group('runindent')), options=options)) # Update lineno (lines inside this example) lineno += string.count('\n', m.start(), m.end()) # Update charno. charno = m.end() # Add any remaining post-example text to `output`. output.append(string[charno:]) return output if __name__ == '__main__': if sys.argv[1:] and sys.argv[1] == 'doctest': doctest.testmod() sys.exit() if not paste_parent in sys.path: sys.path.append(paste_parent) for fn in sys.argv[1:]: fn = os.path.abspath(fn) # @@: OK, ick; but this module gets loaded twice sys.testing_document_filename = fn doctest.testfile( fn, module_relative=False, optionflags=doctest.ELLIPSIS|doctest.REPORT_ONLY_FIRST_FAILURE, parser=LongFormDocTestParser()) new = os.path.splitext(fn)[0] + '.html' assert new != fn os.system('rst2html.py %s > %s' % (fn, new)) Paste-1.7.5.1/paste/session.py0000664000175000017500000002610711354703364014532 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Creates a session object in your WSGI environment. Use like: ..code-block:: Python environ['paste.session.factory']() This will return a dictionary. The contents of this dictionary will be saved to disk when the request is completed. The session will be created when you first fetch the session dictionary, and a cookie will be sent in that case. There's current no way to use sessions without cookies, and there's no way to delete a session except to clear its data. @@: This doesn't do any locking, and may cause problems when a single session is accessed concurrently. Also, it loads and saves the session for each request, with no caching. Also, sessions aren't expired. """ from Cookie import SimpleCookie import time import random import os import datetime import threading import tempfile try: import cPickle except ImportError: import pickle as cPickle try: from hashlib import md5 except ImportError: from md5 import md5 from paste import wsgilib from paste import request class SessionMiddleware(object): def __init__(self, application, global_conf=None, **factory_kw): self.application = application self.factory_kw = factory_kw def __call__(self, environ, start_response): session_factory = SessionFactory(environ, **self.factory_kw) environ['paste.session.factory'] = session_factory remember_headers = [] def session_start_response(status, headers, exc_info=None): if not session_factory.created: remember_headers[:] = [status, headers] return start_response(status, headers) headers.append(session_factory.set_cookie_header()) return start_response(status, headers, exc_info) app_iter = self.application(environ, session_start_response) def start(): if session_factory.created and remember_headers: # Tricky bastard used the session after start_response status, headers = remember_headers headers.append(session_factory.set_cookie_header()) exc = ValueError( "You cannot get the session after content from the " "app_iter has been returned") start_response(status, headers, (exc.__class__, exc, None)) def close(): if session_factory.used: session_factory.close() return wsgilib.add_start_close(app_iter, start, close) class SessionFactory(object): def __init__(self, environ, cookie_name='_SID_', session_class=None, session_expiration=60*12, # in minutes **session_class_kw): self.created = False self.used = False self.environ = environ self.cookie_name = cookie_name self.session = None self.session_class = session_class or FileSession self.session_class_kw = session_class_kw self.expiration = session_expiration def __call__(self): self.used = True if self.session is not None: return self.session.data() cookies = request.get_cookies(self.environ) session = None if cookies.has_key(self.cookie_name): self.sid = cookies[self.cookie_name].value try: session = self.session_class(self.sid, create=False, **self.session_class_kw) except KeyError: # Invalid SID pass if session is None: self.created = True self.sid = self.make_sid() session = self.session_class(self.sid, create=True, **self.session_class_kw) session.clean_up() self.session = session return session.data() def has_session(self): if self.session is not None: return True cookies = request.get_cookies(self.environ) if cookies.has_key(self.cookie_name): return True return False def make_sid(self): # @@: need better algorithm return (''.join(['%02d' % x for x in time.localtime(time.time())[:6]]) + '-' + self.unique_id()) def unique_id(self, for_object=None): """ Generates an opaque, identifier string that is practically guaranteed to be unique. If an object is passed, then its id() is incorporated into the generation. Relies on md5 and returns a 32 character long string. """ r = [time.time(), random.random()] if hasattr(os, 'times'): r.append(os.times()) if for_object is not None: r.append(id(for_object)) md5_hash = md5(str(r)) try: return md5_hash.hexdigest() except AttributeError: # Older versions of Python didn't have hexdigest, so we'll # do it manually hexdigest = [] for char in md5_hash.digest(): hexdigest.append('%02x' % ord(char)) return ''.join(hexdigest) def set_cookie_header(self): c = SimpleCookie() c[self.cookie_name] = self.sid c[self.cookie_name]['path'] = '/' gmt_expiration_time = time.gmtime(time.time() + (self.expiration * 60)) c[self.cookie_name]['expires'] = time.strftime("%a, %d-%b-%Y %H:%M:%S GMT", gmt_expiration_time) name, value = str(c).split(': ', 1) return (name, value) def close(self): if self.session is not None: self.session.close() last_cleanup = None cleaning_up = False cleanup_cycle = datetime.timedelta(seconds=15*60) #15 min class FileSession(object): def __init__(self, sid, create=False, session_file_path=tempfile.gettempdir(), chmod=None, expiration=2880, # in minutes: 48 hours ): if chmod and isinstance(chmod, basestring): chmod = int(chmod, 8) self.chmod = chmod if not sid: # Invalid... raise KeyError self.session_file_path = session_file_path self.sid = sid if not create: if not os.path.exists(self.filename()): raise KeyError self._data = None self.expiration = expiration def filename(self): return os.path.join(self.session_file_path, self.sid) def data(self): if self._data is not None: return self._data if os.path.exists(self.filename()): f = open(self.filename(), 'rb') self._data = cPickle.load(f) f.close() else: self._data = {} return self._data def close(self): if self._data is not None: filename = self.filename() exists = os.path.exists(filename) if not self._data: if exists: os.unlink(filename) else: f = open(filename, 'wb') cPickle.dump(self._data, f) f.close() if not exists and self.chmod: os.chmod(filename, self.chmod) def _clean_up(self): global cleaning_up try: exp_time = datetime.timedelta(seconds=self.expiration*60) now = datetime.datetime.now() #Open every session and check that it isn't too old for root, dirs, files in os.walk(self.session_file_path): for f in files: self._clean_up_file(f, exp_time=exp_time, now=now) finally: cleaning_up = False def _clean_up_file(self, f, exp_time, now): t = f.split("-") if len(t) != 2: return t = t[0] try: sess_time = datetime.datetime( int(t[0:4]), int(t[4:6]), int(t[6:8]), int(t[8:10]), int(t[10:12]), int(t[12:14])) except ValueError: # Probably not a session file at all return if sess_time + exp_time < now: os.remove(os.path.join(self.session_file_path, f)) def clean_up(self): global last_cleanup, cleanup_cycle, cleaning_up now = datetime.datetime.now() if cleaning_up: return if not last_cleanup or last_cleanup + cleanup_cycle < now: if not cleaning_up: cleaning_up = True try: last_cleanup = now t = threading.Thread(target=self._clean_up) t.start() except: # Normally _clean_up should set cleaning_up # to false, but if something goes wrong starting # it... cleaning_up = False raise class _NoDefault(object): def __repr__(self): return '' NoDefault = _NoDefault() def make_session_middleware( app, global_conf, session_expiration=NoDefault, expiration=NoDefault, cookie_name=NoDefault, session_file_path=NoDefault, chmod=NoDefault): """ Adds a middleware that handles sessions for your applications. The session is a peristent dictionary. To get this dictionary in your application, use ``environ['paste.session.factory']()`` which returns this persistent dictionary. Configuration: session_expiration: The time each session lives, in minutes. This controls the cookie expiration. Default 12 hours. expiration: The time each session lives on disk. Old sessions are culled from disk based on this. Default 48 hours. cookie_name: The cookie name used to track the session. Use different names to avoid session clashes. session_file_path: Sessions are put in this location, default /tmp. chmod: The octal chmod you want to apply to new sessions (e.g., 660 to make the sessions group readable/writable) Each of these also takes from the global configuration. cookie_name and chmod take from session_cookie_name and session_chmod """ if session_expiration is NoDefault: session_expiration = global_conf.get('session_expiration', 60*12) session_expiration = int(session_expiration) if expiration is NoDefault: expiration = global_conf.get('expiration', 60*48) expiration = int(expiration) if cookie_name is NoDefault: cookie_name = global_conf.get('session_cookie_name', '_SID_') if session_file_path is NoDefault: session_file_path = global_conf.get('session_file_path', '/tmp') if chmod is NoDefault: chmod = global_conf.get('session_chmod', None) return SessionMiddleware( app, session_expiration=session_expiration, expiration=expiration, cookie_name=cookie_name, session_file_path=session_file_path, chmod=chmod) Paste-1.7.5.1/paste/cgiapp.py0000664000175000017500000002255011437656441014315 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Application that runs a CGI script. """ import os import sys import subprocess import urllib try: import select except ImportError: select = None from paste.util import converters __all__ = ['CGIError', 'CGIApplication'] class CGIError(Exception): """ Raised when the CGI script can't be found or doesn't act like a proper CGI script. """ class CGIApplication(object): """ This object acts as a proxy to a CGI application. You pass in the script path (``script``), an optional path to search for the script (if the name isn't absolute) (``path``). If you don't give a path, then ``$PATH`` will be used. """ def __init__(self, global_conf, script, path=None, include_os_environ=True, query_string=None): if global_conf: raise NotImplemented( "global_conf is no longer supported for CGIApplication " "(use make_cgi_application); please pass None instead") self.script_filename = script if path is None: path = os.environ.get('PATH', '').split(':') self.path = path if '?' in script: assert query_string is None, ( "You cannot have '?' in your script name (%r) and also " "give a query_string (%r)" % (script, query_string)) script, query_string = script.split('?', 1) if os.path.abspath(script) != script: # relative path for path_dir in self.path: if os.path.exists(os.path.join(path_dir, script)): self.script = os.path.join(path_dir, script) break else: raise CGIError( "Script %r not found in path %r" % (script, self.path)) else: self.script = script self.include_os_environ = include_os_environ self.query_string = query_string def __call__(self, environ, start_response): if 'REQUEST_URI' not in environ: environ['REQUEST_URI'] = ( urllib.quote(environ.get('SCRIPT_NAME', '')) + urllib.quote(environ.get('PATH_INFO', ''))) if self.include_os_environ: cgi_environ = os.environ.copy() else: cgi_environ = {} for name in environ: # Should unicode values be encoded? if (name.upper() == name and isinstance(environ[name], str)): cgi_environ[name] = environ[name] if self.query_string is not None: old = cgi_environ.get('QUERY_STRING', '') if old: old += '&' cgi_environ['QUERY_STRING'] = old + self.query_string cgi_environ['SCRIPT_FILENAME'] = self.script proc = subprocess.Popen( [self.script], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=cgi_environ, cwd=os.path.dirname(self.script), ) writer = CGIWriter(environ, start_response) if select and sys.platform != 'win32': proc_communicate( proc, stdin=StdinReader.from_environ(environ), stdout=writer, stderr=environ['wsgi.errors']) else: stdout, stderr = proc.communicate(StdinReader.from_environ(environ).read()) if stderr: environ['wsgi.errors'].write(stderr) writer.write(stdout) if not writer.headers_finished: start_response(writer.status, writer.headers) return [] class CGIWriter(object): def __init__(self, environ, start_response): self.environ = environ self.start_response = start_response self.status = '200 OK' self.headers = [] self.headers_finished = False self.writer = None self.buffer = '' def write(self, data): if self.headers_finished: self.writer(data) return self.buffer += data while '\n' in self.buffer: if '\r\n' in self.buffer and self.buffer.find('\r\n') < self.buffer.find('\n'): line1, self.buffer = self.buffer.split('\r\n', 1) else: line1, self.buffer = self.buffer.split('\n', 1) if not line1: self.headers_finished = True self.writer = self.start_response( self.status, self.headers) self.writer(self.buffer) del self.buffer del self.headers del self.status break elif ':' not in line1: raise CGIError( "Bad header line: %r" % line1) else: name, value = line1.split(':', 1) value = value.lstrip() name = name.strip() if name.lower() == 'status': if ' ' not in value: # WSGI requires this space, sometimes CGI scripts don't set it: value = '%s General' % value self.status = value else: self.headers.append((name, value)) class StdinReader(object): def __init__(self, stdin, content_length): self.stdin = stdin self.content_length = content_length def from_environ(cls, environ): length = environ.get('CONTENT_LENGTH') if length: length = int(length) else: length = 0 return cls(environ['wsgi.input'], length) from_environ = classmethod(from_environ) def read(self, size=None): if not self.content_length: return '' if size is None: text = self.stdin.read(self.content_length) else: text = self.stdin.read(min(self.content_length, size)) self.content_length -= len(text) return text def proc_communicate(proc, stdin=None, stdout=None, stderr=None): """ Run the given process, piping input/output/errors to the given file-like objects (which need not be actual file objects, unlike the arguments passed to Popen). Wait for process to terminate. Note: this is taken from the posix version of subprocess.Popen.communicate, but made more general through the use of file-like objects. """ read_set = [] write_set = [] input_buffer = '' trans_nl = proc.universal_newlines and hasattr(open, 'newlines') if proc.stdin: # Flush stdio buffer. This might block, if the user has # been writing to .stdin in an uncontrolled fashion. proc.stdin.flush() if input: write_set.append(proc.stdin) else: proc.stdin.close() else: assert stdin is None if proc.stdout: read_set.append(proc.stdout) else: assert stdout is None if proc.stderr: read_set.append(proc.stderr) else: assert stderr is None while read_set or write_set: rlist, wlist, xlist = select.select(read_set, write_set, []) if proc.stdin in wlist: # When select has indicated that the file is writable, # we can write up to PIPE_BUF bytes without risk # blocking. POSIX defines PIPE_BUF >= 512 next, input_buffer = input_buffer, '' next_len = 512-len(next) if next_len: next += stdin.read(next_len) if not next: proc.stdin.close() write_set.remove(proc.stdin) else: bytes_written = os.write(proc.stdin.fileno(), next) if bytes_written < len(next): input_buffer = next[bytes_written:] if proc.stdout in rlist: data = os.read(proc.stdout.fileno(), 1024) if data == "": proc.stdout.close() read_set.remove(proc.stdout) if trans_nl: data = proc._translate_newlines(data) stdout.write(data) if proc.stderr in rlist: data = os.read(proc.stderr.fileno(), 1024) if data == "": proc.stderr.close() read_set.remove(proc.stderr) if trans_nl: data = proc._translate_newlines(data) stderr.write(data) try: proc.wait() except OSError, e: if e.errno != 10: raise def make_cgi_application(global_conf, script, path=None, include_os_environ=None, query_string=None): """ Paste Deploy interface for :class:`CGIApplication` This object acts as a proxy to a CGI application. You pass in the script path (``script``), an optional path to search for the script (if the name isn't absolute) (``path``). If you don't give a path, then ``$PATH`` will be used. """ if path is None: path = global_conf.get('path') or global_conf.get('PATH') include_os_environ = converters.asbool(include_os_environ) return CGIApplication( script, path=path, include_os_environ=include_os_environ, query_string=query_string) Paste-1.7.5.1/paste/auth/0000775000175000017500000000000011354703364013430 5ustar ianbianbPaste-1.7.5.1/paste/auth/open_id.py0000664000175000017500000003767711354703364015443 0ustar ianbianb# (c) 2005 Ben Bangert # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """ OpenID Authentication (Consumer) OpenID is a distributed authentication system for single sign-on originally developed at/for LiveJournal.com. http://openid.net/ URL. You can have multiple identities in the same way you can have multiple URLs. All OpenID does is provide a way to prove that you own a URL (identity). And it does this without passing around your password, your email address, or anything you don't want it to. There's no profile exchange component at all: your profiile is your identity URL, but recipients of your identity can then learn more about you from any public, semantically interesting documents linked thereunder (FOAF, RSS, Atom, vCARD, etc.). ``Note``: paste.auth.openid requires installation of the Python-OpenID libraries:: http://www.openidenabled.com/ This module is based highly off the consumer.py that Python OpenID comes with. Using the OpenID Middleware =========================== Using the OpenID middleware is fairly easy, the most minimal example using the basic login form thats included:: # Add to your wsgi app creation from paste.auth import open_id wsgi_app = open_id.middleware(wsgi_app, '/somewhere/to/store/openid/data') You will now have the OpenID form available at /oid on your site. Logging in will verify that the login worked. A more complete login should involve having the OpenID middleware load your own login page after verifying the OpenID URL so that you can retain the login information in your webapp (session, cookies, etc.):: wsgi_app = open_id.middleware(wsgi_app, '/somewhere/to/store/openid/data', login_redirect='/your/login/code') Your login code should then be configured to retrieve 'paste.auth.open_id' for the users OpenID URL. If this key does not exist, the user has not logged in. Once the login is retrieved, it should be saved in your webapp, and the user should be redirected to wherever they would normally go after a successful login. """ __all__ = ['AuthOpenIDHandler'] import cgi import urlparse import re import paste.request from paste import httpexceptions def quoteattr(s): qs = cgi.escape(s, 1) return '"%s"' % (qs,) # You may need to manually add the openid package into your # python path if you don't have it installed with your system python. # If so, uncomment the line below, and change the path where you have # Python-OpenID. # sys.path.append('/path/to/openid/') from openid.store import filestore from openid.consumer import consumer from openid.oidutil import appendArgs class AuthOpenIDHandler(object): """ This middleware implements OpenID Consumer behavior to authenticate a URL against an OpenID Server. """ def __init__(self, app, data_store_path, auth_prefix='/oid', login_redirect=None, catch_401=False, url_to_username=None): """ Initialize the OpenID middleware ``app`` Your WSGI app to call ``data_store_path`` Directory to store crypto data in for use with OpenID servers. ``auth_prefix`` Location for authentication process/verification ``login_redirect`` Location to load after successful process of login ``catch_401`` If true, then any 401 responses will turn into open ID login requirements. ``url_to_username`` A function called like ``url_to_username(environ, url)``, which should return a string username. If not given, the URL will be the username. """ store = filestore.FileOpenIDStore(data_store_path) self.oidconsumer = consumer.OpenIDConsumer(store) self.app = app self.auth_prefix = auth_prefix self.data_store_path = data_store_path self.login_redirect = login_redirect self.catch_401 = catch_401 self.url_to_username = url_to_username def __call__(self, environ, start_response): if environ['PATH_INFO'].startswith(self.auth_prefix): # Let's load everything into a request dict to pass around easier request = dict(environ=environ, start=start_response, body=[]) request['base_url'] = paste.request.construct_url(environ, with_path_info=False, with_query_string=False) path = re.sub(self.auth_prefix, '', environ['PATH_INFO']) request['parsed_uri'] = urlparse.urlparse(path) request['query'] = dict(paste.request.parse_querystring(environ)) path = request['parsed_uri'][2] if path == '/' or not path: return self.render(request) elif path == '/verify': return self.do_verify(request) elif path == '/process': return self.do_process(request) else: return self.not_found(request) else: if self.catch_401: return self.catch_401_app_call(environ, start_response) return self.app(environ, start_response) def catch_401_app_call(self, environ, start_response): """ Call the application, and redirect if the app returns a 401 response """ was_401 = [] def replacement_start_response(status, headers, exc_info=None): if int(status.split(None, 1)) == 401: # @@: Do I need to append something to go back to where we # came from? was_401.append(1) def dummy_writer(v): pass return dummy_writer else: return start_response(status, headers, exc_info) app_iter = self.app(environ, replacement_start_response) if was_401: try: list(app_iter) finally: if hasattr(app_iter, 'close'): app_iter.close() redir_url = paste.request.construct_url(environ, with_path_info=False, with_query_string=False) exc = httpexceptions.HTTPTemporaryRedirect(redir_url) return exc.wsgi_application(environ, start_response) else: return app_iter def do_verify(self, request): """Process the form submission, initating OpenID verification. """ # First, make sure that the user entered something openid_url = request['query'].get('openid_url') if not openid_url: return self.render(request, 'Enter an identity URL to verify.', css_class='error', form_contents=openid_url) oidconsumer = self.oidconsumer # Then, ask the library to begin the authorization. # Here we find out the identity server that will verify the # user's identity, and get a token that allows us to # communicate securely with the identity server. status, info = oidconsumer.beginAuth(openid_url) # If the URL was unusable (either because of network # conditions, a server error, or that the response returned # was not an OpenID identity page), the library will return # an error code. Let the user know that that URL is unusable. if status in [consumer.HTTP_FAILURE, consumer.PARSE_ERROR]: if status == consumer.HTTP_FAILURE: fmt = 'Failed to retrieve %s' else: fmt = 'Could not find OpenID information in %s' message = fmt % (cgi.escape(openid_url),) return self.render(request, message, css_class='error', form_contents=openid_url) elif status == consumer.SUCCESS: # The URL was a valid identity URL. Now we construct a URL # that will get us to process the server response. We will # need the token from the beginAuth call when processing # the response. A cookie or a session object could be used # to accomplish this, but for simplicity here we just add # it as a query parameter of the return-to URL. return_to = self.build_url(request, 'process', token=info.token) # Now ask the library for the URL to redirect the user to # his OpenID server. It is required for security that the # return_to URL must be under the specified trust_root. We # just use the base_url for this server as a trust root. redirect_url = oidconsumer.constructRedirect( info, return_to, trust_root=request['base_url']) # Send the redirect response return self.redirect(request, redirect_url) else: assert False, 'Not reached' def do_process(self, request): """Handle the redirect from the OpenID server. """ oidconsumer = self.oidconsumer # retrieve the token from the environment (in this case, the URL) token = request['query'].get('token', '') # Ask the library to check the response that the server sent # us. Status is a code indicating the response type. info is # either None or a string containing more information about # the return type. status, info = oidconsumer.completeAuth(token, request['query']) css_class = 'error' openid_url = None if status == consumer.FAILURE and info: # In the case of failure, if info is non-None, it is the # URL that we were verifying. We include it in the error # message to help the user figure out what happened. openid_url = info fmt = "Verification of %s failed." message = fmt % (cgi.escape(openid_url),) elif status == consumer.SUCCESS: # Success means that the transaction completed without # error. If info is None, it means that the user cancelled # the verification. css_class = 'alert' if info: # This is a successful verification attempt. If this # was a real application, we would do our login, # comment posting, etc. here. openid_url = info if self.url_to_username: username = self.url_to_username(request['environ'], openid_url) else: username = openid_url if 'paste.auth_tkt.set_user' in request['environ']: request['environ']['paste.auth_tkt.set_user'](username) if not self.login_redirect: fmt = ("If you had supplied a login redirect path, you would have " "been redirected there. " "You have successfully verified %s as your identity.") message = fmt % (cgi.escape(openid_url),) else: # @@: This stuff doesn't make sense to me; why not a remote redirect? request['environ']['paste.auth.open_id'] = openid_url request['environ']['PATH_INFO'] = self.login_redirect return self.app(request['environ'], request['start']) #exc = httpexceptions.HTTPTemporaryRedirect(self.login_redirect) #return exc.wsgi_application(request['environ'], request['start']) else: # cancelled message = 'Verification cancelled' else: # Either we don't understand the code or there is no # openid_url included with the error. Give a generic # failure message. The library should supply debug # information in a log. message = 'Verification failed.' return self.render(request, message, css_class, openid_url) def build_url(self, request, action, **query): """Build a URL relative to the server base_url, with the given query parameters added.""" base = urlparse.urljoin(request['base_url'], self.auth_prefix + '/' + action) return appendArgs(base, query) def redirect(self, request, redirect_url): """Send a redirect response to the given URL to the browser.""" response_headers = [('Content-type', 'text/plain'), ('Location', redirect_url)] request['start']('302 REDIRECT', response_headers) return ["Redirecting to %s" % redirect_url] def not_found(self, request): """Render a page with a 404 return code and a message.""" fmt = 'The path %s was not understood by this server.' msg = fmt % (request['parsed_uri'],) openid_url = request['query'].get('openid_url') return self.render(request, msg, 'error', openid_url, status='404 Not Found') def render(self, request, message=None, css_class='alert', form_contents=None, status='200 OK', title="Python OpenID Consumer"): """Render a page.""" response_headers = [('Content-type', 'text/html')] request['start'](str(status), response_headers) self.page_header(request, title) if message: request['body'].append("
" % (css_class,)) request['body'].append(message) request['body'].append("
") self.page_footer(request, form_contents) return request['body'] def page_header(self, request, title): """Render the page header""" request['body'].append('''\ %s

%s

This example consumer uses the Python OpenID library. It just verifies that the URL that you enter is your identity URL.

''' % (title, title)) def page_footer(self, request, form_contents): """Render the page footer""" if not form_contents: form_contents = '' request['body'].append('''\
Identity URL:
''' % (quoteattr(self.build_url(request, 'verify')), quoteattr(form_contents))) middleware = AuthOpenIDHandler def make_open_id_middleware( app, global_conf, # Should this default to something, or inherit something from global_conf?: data_store_path, auth_prefix='/oid', login_redirect=None, catch_401=False, url_to_username=None, apply_auth_tkt=False, auth_tkt_logout_path=None): from paste.deploy.converters import asbool from paste.util import import_string catch_401 = asbool(catch_401) if url_to_username and isinstance(url_to_username, basestring): url_to_username = import_string.eval_import(url_to_username) apply_auth_tkt = asbool(apply_auth_tkt) new_app = AuthOpenIDHandler( app, data_store_path=data_store_path, auth_prefix=auth_prefix, login_redirect=login_redirect, catch_401=catch_401, url_to_username=url_to_username or None) if apply_auth_tkt: from paste.auth import auth_tkt new_app = auth_tkt.make_auth_tkt_middleware( new_app, global_conf, logout_path=auth_tkt_logout_path) return new_app Paste-1.7.5.1/paste/auth/basic.py0000664000175000017500000000775411354703364015100 0ustar ianbianb# (c) 2005 Clark C. Evans # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # This code was written with funding by http://prometheusresearch.com """ Basic HTTP/1.0 Authentication This module implements ``Basic`` authentication as described in HTTP/1.0 specification [1]_ . Do not use this module unless you are using SSL or need to work with very out-dated clients, instead use ``digest`` authentication. >>> from paste.wsgilib import dump_environ >>> from paste.httpserver import serve >>> # from paste.auth.basic import AuthBasicHandler >>> realm = 'Test Realm' >>> def authfunc(environ, username, password): ... return username == password >>> serve(AuthBasicHandler(dump_environ, realm, authfunc)) serving on... .. [1] http://www.w3.org/Protocols/HTTP/1.0/draft-ietf-http-spec.html#BasicAA """ from paste.httpexceptions import HTTPUnauthorized from paste.httpheaders import * class AuthBasicAuthenticator(object): """ implements ``Basic`` authentication details """ type = 'basic' def __init__(self, realm, authfunc): self.realm = realm self.authfunc = authfunc def build_authentication(self): head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm) return HTTPUnauthorized(headers=head) def authenticate(self, environ): authorization = AUTHORIZATION(environ) if not authorization: return self.build_authentication() (authmeth, auth) = authorization.split(' ', 1) if 'basic' != authmeth.lower(): return self.build_authentication() auth = auth.strip().decode('base64') username, password = auth.split(':', 1) if self.authfunc(environ, username, password): return username return self.build_authentication() __call__ = authenticate class AuthBasicHandler(object): """ HTTP/1.0 ``Basic`` authentication middleware Parameters: ``application`` The application object is called only upon successful authentication, and can assume ``environ['REMOTE_USER']`` is set. If the ``REMOTE_USER`` is already set, this middleware is simply pass-through. ``realm`` This is a identifier for the authority that is requesting authorization. It is shown to the user and should be unique within the domain it is being used. ``authfunc`` This is a mandatory user-defined function which takes a ``environ``, ``username`` and ``password`` for its first three arguments. It should return ``True`` if the user is authenticated. """ def __init__(self, application, realm, authfunc): self.application = application self.authenticate = AuthBasicAuthenticator(realm, authfunc) def __call__(self, environ, start_response): username = REMOTE_USER(environ) if not username: result = self.authenticate(environ) if isinstance(result, str): AUTH_TYPE.update(environ, 'basic') REMOTE_USER.update(environ, result) else: return result.wsgi_application(environ, start_response) return self.application(environ, start_response) middleware = AuthBasicHandler __all__ = ['AuthBasicHandler'] def make_basic(app, global_conf, realm, authfunc, **kw): """ Grant access via basic authentication Config looks like this:: [filter:grant] use = egg:Paste#auth_basic realm=myrealm authfunc=somepackage.somemodule:somefunction """ from paste.util.import_string import eval_import import types authfunc = eval_import(authfunc) assert isinstance(authfunc, types.FunctionType), "authfunc must resolve to a function" return AuthBasicHandler(app, realm, authfunc) if "__main__" == __name__: import doctest doctest.testmod(optionflags=doctest.ELLIPSIS) Paste-1.7.5.1/paste/auth/auth_tkt.py0000664000175000017500000003414011444450501015617 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php ########################################################################## # # Copyright (c) 2005 Imaginary Landscape LLC and Contributors. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ########################################################################## """ Implementation of cookie signing as done in `mod_auth_tkt `_. mod_auth_tkt is an Apache module that looks for these signed cookies and sets ``REMOTE_USER``, ``REMOTE_USER_TOKENS`` (a comma-separated list of groups) and ``REMOTE_USER_DATA`` (arbitrary string data). This module is an alternative to the ``paste.auth.cookie`` module; it's primary benefit is compatibility with mod_auth_tkt, which in turn makes it possible to use the same authentication process with non-Python code run under Apache. """ import time as time_mod try: from hashlib import md5 except ImportError: from md5 import md5 import Cookie from paste import request from urllib import quote as url_quote from urllib import unquote as url_unquote class AuthTicket(object): """ This class represents an authentication token. You must pass in the shared secret, the userid, and the IP address. Optionally you can include tokens (a list of strings, representing role names), 'user_data', which is arbitrary data available for your own use in later scripts. Lastly, you can override the cookie name and timestamp. Once you provide all the arguments, use .cookie_value() to generate the appropriate authentication ticket. .cookie() generates a Cookie object, the str() of which is the complete cookie header to be sent. CGI usage:: token = auth_tkt.AuthTick('sharedsecret', 'username', os.environ['REMOTE_ADDR'], tokens=['admin']) print 'Status: 200 OK' print 'Content-type: text/html' print token.cookie() print ... redirect HTML ... Webware usage:: token = auth_tkt.AuthTick('sharedsecret', 'username', self.request().environ()['REMOTE_ADDR'], tokens=['admin']) self.response().setCookie('auth_tkt', token.cookie_value()) Be careful not to do an HTTP redirect after login; use meta refresh or Javascript -- some browsers have bugs where cookies aren't saved when set on a redirect. """ def __init__(self, secret, userid, ip, tokens=(), user_data='', time=None, cookie_name='auth_tkt', secure=False): self.secret = secret self.userid = userid self.ip = ip self.tokens = ','.join(tokens) self.user_data = user_data if time is None: self.time = time_mod.time() else: self.time = time self.cookie_name = cookie_name self.secure = secure def digest(self): return calculate_digest( self.ip, self.time, self.secret, self.userid, self.tokens, self.user_data) def cookie_value(self): v = '%s%08x%s!' % (self.digest(), int(self.time), url_quote(self.userid)) if self.tokens: v += self.tokens + '!' v += self.user_data return v def cookie(self): c = Cookie.SimpleCookie() c[self.cookie_name] = self.cookie_value().encode('base64').strip().replace('\n', '') c[self.cookie_name]['path'] = '/' if self.secure: c[self.cookie_name]['secure'] = 'true' return c class BadTicket(Exception): """ Exception raised when a ticket can't be parsed. If we get far enough to determine what the expected digest should have been, expected is set. This should not be shown by default, but can be useful for debugging. """ def __init__(self, msg, expected=None): self.expected = expected Exception.__init__(self, msg) def parse_ticket(secret, ticket, ip): """ Parse the ticket, returning (timestamp, userid, tokens, user_data). If the ticket cannot be parsed, ``BadTicket`` will be raised with an explanation. """ ticket = ticket.strip('"') digest = ticket[:32] try: timestamp = int(ticket[32:40], 16) except ValueError, e: raise BadTicket('Timestamp is not a hex integer: %s' % e) try: userid, data = ticket[40:].split('!', 1) except ValueError: raise BadTicket('userid is not followed by !') userid = url_unquote(userid) if '!' in data: tokens, user_data = data.split('!', 1) else: # @@: Is this the right order? tokens = '' user_data = data expected = calculate_digest(ip, timestamp, secret, userid, tokens, user_data) if expected != digest: raise BadTicket('Digest signature is not correct', expected=(expected, digest)) tokens = tokens.split(',') return (timestamp, userid, tokens, user_data) def calculate_digest(ip, timestamp, secret, userid, tokens, user_data): secret = maybe_encode(secret) userid = maybe_encode(userid) tokens = maybe_encode(tokens) user_data = maybe_encode(user_data) digest0 = md5( encode_ip_timestamp(ip, timestamp) + secret + userid + '\0' + tokens + '\0' + user_data).hexdigest() digest = md5(digest0 + secret).hexdigest() return digest def encode_ip_timestamp(ip, timestamp): ip_chars = ''.join(map(chr, map(int, ip.split('.')))) t = int(timestamp) ts = ((t & 0xff000000) >> 24, (t & 0xff0000) >> 16, (t & 0xff00) >> 8, t & 0xff) ts_chars = ''.join(map(chr, ts)) return ip_chars + ts_chars def maybe_encode(s, encoding='utf8'): if isinstance(s, unicode): s = s.encode(encoding) return s class AuthTKTMiddleware(object): """ Middleware that checks for signed cookies that match what `mod_auth_tkt `_ looks for (if you have mod_auth_tkt installed, you don't need this middleware, since Apache will set the environmental variables for you). Arguments: ``secret``: A secret that should be shared by any instances of this application. If this app is served from more than one machine, they should all have the same secret. ``cookie_name``: The name of the cookie to read and write from. Default ``auth_tkt``. ``secure``: If the cookie should be set as 'secure' (only sent over SSL) and if the login must be over SSL. (Defaults to False) ``httponly``: If the cookie should be marked as HttpOnly, which means that it's not accessible to JavaScript. (Defaults to False) ``include_ip``: If the cookie should include the user's IP address. If so, then if they change IPs their cookie will be invalid. ``logout_path``: The path under this middleware that should signify a logout. The page will be shown as usual, but the user will also be logged out when they visit this page. If used with mod_auth_tkt, then these settings (except logout_path) should match the analogous Apache configuration settings. This also adds two functions to the request: ``environ['paste.auth_tkt.set_user'](userid, tokens='', user_data='')`` This sets a cookie that logs the user in. ``tokens`` is a string (comma-separated groups) or a list of strings. ``user_data`` is a string for your own use. ``environ['paste.auth_tkt.logout_user']()`` Logs out the user. """ def __init__(self, app, secret, cookie_name='auth_tkt', secure=False, include_ip=True, logout_path=None, httponly=False, no_domain_cookie=True, current_domain_cookie=True, wildcard_cookie=True): self.app = app self.secret = secret self.cookie_name = cookie_name self.secure = secure self.httponly = httponly self.include_ip = include_ip self.logout_path = logout_path self.no_domain_cookie = no_domain_cookie self.current_domain_cookie = current_domain_cookie self.wildcard_cookie = wildcard_cookie def __call__(self, environ, start_response): cookies = request.get_cookies(environ) if self.cookie_name in cookies: cookie_value = cookies[self.cookie_name].value else: cookie_value = '' if cookie_value: if self.include_ip: remote_addr = environ['REMOTE_ADDR'] else: # mod_auth_tkt uses this dummy value when IP is not # checked: remote_addr = '0.0.0.0' # @@: This should handle bad signatures better: # Also, timeouts should cause cookie refresh try: timestamp, userid, tokens, user_data = parse_ticket( self.secret, cookie_value, remote_addr) tokens = ','.join(tokens) environ['REMOTE_USER'] = userid if environ.get('REMOTE_USER_TOKENS'): # We want to add tokens/roles to what's there: tokens = environ['REMOTE_USER_TOKENS'] + ',' + tokens environ['REMOTE_USER_TOKENS'] = tokens environ['REMOTE_USER_DATA'] = user_data environ['AUTH_TYPE'] = 'cookie' except BadTicket: # bad credentials, just ignore without logging the user # in or anything pass set_cookies = [] def set_user(userid, tokens='', user_data=''): set_cookies.extend(self.set_user_cookie( environ, userid, tokens, user_data)) def logout_user(): set_cookies.extend(self.logout_user_cookie(environ)) environ['paste.auth_tkt.set_user'] = set_user environ['paste.auth_tkt.logout_user'] = logout_user if self.logout_path and environ.get('PATH_INFO') == self.logout_path: logout_user() def cookie_setting_start_response(status, headers, exc_info=None): headers.extend(set_cookies) return start_response(status, headers, exc_info) return self.app(environ, cookie_setting_start_response) def set_user_cookie(self, environ, userid, tokens, user_data): if not isinstance(tokens, basestring): tokens = ','.join(tokens) if self.include_ip: remote_addr = environ['REMOTE_ADDR'] else: remote_addr = '0.0.0.0' ticket = AuthTicket( self.secret, userid, remote_addr, tokens=tokens, user_data=user_data, cookie_name=self.cookie_name, secure=self.secure) # @@: Should we set REMOTE_USER etc in the current # environment right now as well? cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME')) wild_domain = '.' + cur_domain cookie_options = "" if self.secure: cookie_options += "; secure" if self.httponly: cookie_options += "; HttpOnly" cookies = [] if self.no_domain_cookie: cookies.append(('Set-Cookie', '%s=%s; Path=/%s' % ( self.cookie_name, ticket.cookie_value(), cookie_options))) if self.current_domain_cookie: cookies.append(('Set-Cookie', '%s=%s; Path=/; Domain=%s%s' % ( self.cookie_name, ticket.cookie_value(), cur_domain, cookie_options))) if self.wildcard_cookie: cookies.append(('Set-Cookie', '%s=%s; Path=/; Domain=%s%s' % ( self.cookie_name, ticket.cookie_value(), wild_domain, cookie_options))) return cookies def logout_user_cookie(self, environ): cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME')) wild_domain = '.' + cur_domain expires = 'Sat, 01-Jan-2000 12:00:00 GMT' cookies = [ ('Set-Cookie', '%s=""; Expires="%s"; Path=/' % (self.cookie_name, expires)), ('Set-Cookie', '%s=""; Expires="%s"; Path=/; Domain=%s' % (self.cookie_name, expires, cur_domain)), ('Set-Cookie', '%s=""; Expires="%s"; Path=/; Domain=%s' % (self.cookie_name, expires, wild_domain)), ] return cookies def make_auth_tkt_middleware( app, global_conf, secret=None, cookie_name='auth_tkt', secure=False, include_ip=True, logout_path=None): """ Creates the `AuthTKTMiddleware `_. ``secret`` is requird, but can be set globally or locally. """ from paste.deploy.converters import asbool secure = asbool(secure) include_ip = asbool(include_ip) if secret is None: secret = global_conf.get('secret') if not secret: raise ValueError( "You must provide a 'secret' (in global or local configuration)") return AuthTKTMiddleware( app, secret, cookie_name, secure, include_ip, logout_path or None) Paste-1.7.5.1/paste/auth/cas.py0000664000175000017500000000763411354703364014562 0ustar ianbianb# (c) 2005 Clark C. Evans # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # This code was written with funding by http://prometheusresearch.com """ CAS 1.0 Authentication The Central Authentication System is a straight-forward single sign-on mechanism developed by Yale University's ITS department. It has since enjoyed widespread success and is deployed at many major universities and some corporations. https://clearinghouse.ja-sig.org/wiki/display/CAS/Home http://www.yale.edu/tp/auth/usingcasatyale.html This implementation has the goal of maintaining current path arguments passed to the system so that it can be used as middleware at any stage of processing. It has the secondary goal of allowing for other authentication methods to be used concurrently. """ import urllib from paste.request import construct_url from paste.httpexceptions import HTTPSeeOther, HTTPForbidden class CASLoginFailure(HTTPForbidden): """ The exception raised if the authority returns 'no' """ class CASAuthenticate(HTTPSeeOther): """ The exception raised to authenticate the user """ def AuthCASHandler(application, authority): """ middleware to implement CAS 1.0 authentication There are several possible outcomes: 0. If the REMOTE_USER environment variable is already populated; then this middleware is a no-op, and the request is passed along to the application. 1. If a query argument 'ticket' is found, then an attempt to validate said ticket /w the authentication service done. If the ticket is not validated; an 403 'Forbidden' exception is raised. Otherwise, the REMOTE_USER variable is set with the NetID that was validated and AUTH_TYPE is set to "cas". 2. Otherwise, a 303 'See Other' is returned to the client directing them to login using the CAS service. After logon, the service will send them back to this same URL, only with a 'ticket' query argument. Parameters: ``authority`` This is a fully-qualified URL to a CAS 1.0 service. The URL should end with a '/' and have the 'login' and 'validate' sub-paths as described in the CAS 1.0 documentation. """ assert authority.endswith("/") and authority.startswith("http") def cas_application(environ, start_response): username = environ.get('REMOTE_USER','') if username: return application(environ, start_response) qs = environ.get('QUERY_STRING','').split("&") if qs and qs[-1].startswith("ticket="): # assume a response from the authority ticket = qs.pop().split("=", 1)[1] environ['QUERY_STRING'] = "&".join(qs) service = construct_url(environ) args = urllib.urlencode( {'service': service,'ticket': ticket}) requrl = authority + "validate?" + args result = urllib.urlopen(requrl).read().split("\n") if 'yes' == result[0]: environ['REMOTE_USER'] = result[1] environ['AUTH_TYPE'] = 'cas' return application(environ, start_response) exce = CASLoginFailure() else: service = construct_url(environ) args = urllib.urlencode({'service': service}) location = authority + "login?" + args exce = CASAuthenticate(location) return exce.wsgi_application(environ, start_response) return cas_application middleware = AuthCASHandler __all__ = ['CASLoginFailure', 'CASAuthenticate', 'AuthCASHandler' ] if '__main__' == __name__: authority = "https://secure.its.yale.edu/cas/servlet/" from paste.wsgilib import dump_environ from paste.httpserver import serve from paste.httpexceptions import * serve(HTTPExceptionHandler( AuthCASHandler(dump_environ, authority))) Paste-1.7.5.1/paste/auth/multi.py0000664000175000017500000000574211354703364015144 0ustar ianbianb# (c) 2005 Clark C. Evans # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # This code was written with funding by http://prometheusresearch.com """ Authentication via Multiple Methods In some environments, the choice of authentication method to be used depends upon the environment and is not "fixed". This middleware allows N authentication methods to be registered along with a goodness function which determines which method should be used. The following example demonstrates how to use both form and digest authentication in a server stack; by default it uses form-based authentication unless ``*authmeth=digest`` is specified as a query argument. >>> from paste.auth import form, cookie, digest, multi >>> from paste.wsgilib import dump_environ >>> from paste.httpserver import serve >>> >>> multi = multi.MultiHandler(dump_environ) >>> def authfunc(environ, realm, user): ... return digest.digest_password(realm, user, user) >>> multi.add_method('digest', digest.middleware, "Test Realm", authfunc) >>> multi.set_query_argument('digest') >>> >>> def authfunc(environ, username, password): ... return username == password >>> multi.add_method('form', form.middleware, authfunc) >>> multi.set_default('form') >>> serve(cookie.middleware(multi)) serving on... """ class MultiHandler(object): """ Multiple Authentication Handler This middleware provides two othogonal facilities: - a manner to register any number of authentication middlewares - a mechanism to register predicates which cause one of the registered middlewares to be used depending upon the request If none of the predicates returns True, then the application is invoked directly without middleware """ def __init__(self, application): self.application = application self.default = application self.binding = {} self.predicate = [] def add_method(self, name, factory, *args, **kwargs): self.binding[name] = factory(self.application, *args, **kwargs) def add_predicate(self, name, checker): self.predicate.append((checker, self.binding[name])) def set_default(self, name): """ set default authentication method """ self.default = self.binding[name] def set_query_argument(self, name, key = '*authmeth', value = None): """ choose authentication method based on a query argument """ lookfor = "%s=%s" % (key, value or name) self.add_predicate(name, lambda environ: lookfor in environ.get('QUERY_STRING','')) def __call__(self, environ, start_response): for (checker, binding) in self.predicate: if checker(environ): return binding(environ, start_response) return self.default(environ, start_response) middleware = MultiHandler __all__ = ['MultiHandler'] if "__main__" == __name__: import doctest doctest.testmod(optionflags=doctest.ELLIPSIS) Paste-1.7.5.1/paste/auth/cookie.py0000664000175000017500000003726711354703364015272 0ustar ianbianb# (c) 2005 Clark C. Evans # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # This code was written with funding by http://prometheusresearch.com """ Cookie "Saved" Authentication This authentication middleware saves the current REMOTE_USER, REMOTE_SESSION, and any other environment variables specified in a cookie so that it can be retrieved during the next request without requiring re-authentication. This uses a session cookie on the client side (so it goes away when the user closes their window) and does server-side expiration. Following is a very simple example where a form is presented asking for a user name (no actual checking), and dummy session identifier (perhaps corresponding to a database session id) is stored in the cookie. :: >>> from paste.httpserver import serve >>> from paste.fileapp import DataApp >>> from paste.httpexceptions import * >>> from paste.auth.cookie import AuthCookieHandler >>> from paste.wsgilib import parse_querystring >>> def testapp(environ, start_response): ... user = dict(parse_querystring(environ)).get('user','') ... if user: ... environ['REMOTE_USER'] = user ... environ['REMOTE_SESSION'] = 'a-session-id' ... if environ.get('REMOTE_USER'): ... page = 'Welcome %s (%s)' ... page %= (environ['REMOTE_USER'], environ['REMOTE_SESSION']) ... else: ... page = ('
' ... '
') ... return DataApp(page, content_type="text/html")( ... environ, start_response) >>> serve(AuthCookieHandler(testapp)) serving on... """ import hmac, base64, random, time, warnings try: from hashlib import sha1 except ImportError: # NOTE: We have to use the callable with hashlib (hashlib.sha1), # otherwise hmac only accepts the sha module object itself import sha as sha1 from paste.request import get_cookies def make_time(value): return time.strftime("%Y%m%d%H%M", time.gmtime(value)) _signature_size = len(hmac.new('x', 'x', sha1).digest()) _header_size = _signature_size + len(make_time(time.time())) # @@: Should this be using urllib.quote? # build encode/decode functions to safely pack away values _encode = [('\\', '\\x5c'), ('"', '\\x22'), ('=', '\\x3d'), (';', '\\x3b')] _decode = [(v, k) for (k, v) in _encode] _decode.reverse() def encode(s, sublist = _encode): return reduce((lambda a, (b, c): a.replace(b, c)), sublist, str(s)) decode = lambda s: encode(s, _decode) class CookieTooLarge(RuntimeError): def __init__(self, content, cookie): RuntimeError.__init__("Signed cookie exceeds maximum size of 4096") self.content = content self.cookie = cookie _all_chars = ''.join([chr(x) for x in range(0, 255)]) def new_secret(): """ returns a 64 byte secret """ return ''.join(random.sample(_all_chars, 64)) class AuthCookieSigner(object): """ save/restore ``environ`` entries via digially signed cookie This class converts content into a timed and digitally signed cookie, as well as having the facility to reverse this procedure. If the cookie, after the content is encoded and signed exceeds the maximum length (4096), then CookieTooLarge exception is raised. The timeout of the cookie is handled on the server side for a few reasons. First, if a 'Expires' directive is added to a cookie, then the cookie becomes persistent (lasting even after the browser window has closed). Second, the user's clock may be wrong (perhaps intentionally). The timeout is specified in minutes; and expiration date returned is rounded to one second. Constructor Arguments: ``secret`` This is a secret key if you want to syncronize your keys so that the cookie will be good across a cluster of computers. It is recommended via the HMAC specification (RFC 2104) that the secret key be 64 bytes since this is the block size of the hashing. If you do not provide a secret key, a random one is generated each time you create the handler; this should be sufficient for most cases. ``timeout`` This is the time (in minutes) from which the cookie is set to expire. Note that on each request a new (replacement) cookie is sent, hence this is effectively a session timeout parameter for your entire cluster. If you do not provide a timeout, it is set at 30 minutes. ``maxlen`` This is the maximum size of the *signed* cookie; hence the actual content signed will be somewhat less. If the cookie goes over this size, a ``CookieTooLarge`` exception is raised so that unexpected handling of cookies on the client side are avoided. By default this is set at 4k (4096 bytes), which is the standard cookie size limit. """ def __init__(self, secret = None, timeout = None, maxlen = None): self.timeout = timeout or 30 if isinstance(timeout, basestring): raise ValueError( "Timeout must be a number (minutes), not a string (%r)" % timeout) self.maxlen = maxlen or 4096 self.secret = secret or new_secret() def sign(self, content): """ Sign the content returning a valid cookie (that does not need to be escaped and quoted). The expiration of this cookie is handled server-side in the auth() function. """ cookie = base64.encodestring( hmac.new(self.secret, content, sha1).digest() + make_time(time.time() + 60*self.timeout) + content) cookie = cookie.replace("/", "_").replace("=", "~") cookie = cookie.replace('\n', '').replace('\r', '') if len(cookie) > self.maxlen: raise CookieTooLarge(content, cookie) return cookie def auth(self, cookie): """ Authenticate the cooke using the signature, verify that it has not expired; and return the cookie's content """ decode = base64.decodestring( cookie.replace("_", "/").replace("~", "=")) signature = decode[:_signature_size] expires = decode[_signature_size:_header_size] content = decode[_header_size:] if signature == hmac.new(self.secret, content, sha1).digest(): if int(expires) > int(make_time(time.time())): return content else: # This is the normal case of an expired cookie; just # don't bother doing anything here. pass else: # This case can happen if the server is restarted with a # different secret; or if the user's IP address changed # due to a proxy. However, it could also be a break-in # attempt -- so should it be reported? pass class AuthCookieEnviron(list): """ a list of environment keys to be saved via cookie An instance of this object, found at ``environ['paste.auth.cookie']`` lists the `environ` keys that were restored from or will be added to the digially signed cookie. This object can be accessed from an `environ` variable by using this module's name. """ def __init__(self, handler, scanlist): list.__init__(self, scanlist) self.handler = handler def append(self, value): if value in self: return list.append(self, str(value)) class AuthCookieHandler(object): """ the actual handler that should be put in your middleware stack This middleware uses cookies to stash-away a previously authenticated user (and perhaps other variables) so that re-authentication is not needed. This does not implement sessions; and therefore N servers can be syncronized to accept the same saved authentication if they all use the same cookie_name and secret. By default, this handler scans the `environ` for the REMOTE_USER and REMOTE_SESSION key; if found, it is stored. It can be configured to scan other `environ` keys as well -- but be careful not to exceed 2-3k (so that the encoded and signed cookie does not exceed 4k). You can ask it to handle other environment variables by doing: ``environ['paste.auth.cookie'].append('your.environ.variable')`` Constructor Arguments: ``application`` This is the wrapped application which will have access to the ``environ['REMOTE_USER']`` restored by this middleware. ``cookie_name`` The name of the cookie used to store this content, by default it is ``PASTE_AUTH_COOKIE``. ``scanlist`` This is the initial set of ``environ`` keys to save/restore to the signed cookie. By default is consists only of ``REMOTE_USER`` and ``REMOTE_SESSION``; any tuple or list of environment keys will work. However, be careful, as the total saved size is limited to around 3k. ``signer`` This is the signer object used to create the actual cookie values, by default, it is ``AuthCookieSigner`` and is passed the remaining arguments to this function: ``secret``, ``timeout``, and ``maxlen``. At this time, each cookie is individually signed. To store more than the 4k of data; it is possible to sub-class this object to provide different ``environ_name`` and ``cookie_name`` """ environ_name = 'paste.auth.cookie' cookie_name = 'PASTE_AUTH_COOKIE' signer_class = AuthCookieSigner environ_class = AuthCookieEnviron def __init__(self, application, cookie_name=None, scanlist=None, signer=None, secret=None, timeout=None, maxlen=None): if not signer: signer = self.signer_class(secret, timeout, maxlen) self.signer = signer self.scanlist = scanlist or ('REMOTE_USER','REMOTE_SESSION') self.application = application self.cookie_name = cookie_name or self.cookie_name def __call__(self, environ, start_response): if self.environ_name in environ: raise AssertionError("AuthCookie already installed!") scanlist = self.environ_class(self, self.scanlist) jar = get_cookies(environ) if jar.has_key(self.cookie_name): content = self.signer.auth(jar[self.cookie_name].value) if content: for pair in content.split(";"): (k, v) = pair.split("=") k = decode(k) if k not in scanlist: scanlist.append(k) if k in environ: continue environ[k] = decode(v) if 'REMOTE_USER' == k: environ['AUTH_TYPE'] = 'cookie' environ[self.environ_name] = scanlist if "paste.httpexceptions" in environ: warnings.warn("Since paste.httpexceptions is hooked in your " "processing chain before paste.auth.cookie, if an " "HTTPRedirection is raised, the cookies this module sets " "will not be included in your response.\n") def response_hook(status, response_headers, exc_info=None): """ Scan the environment for keys specified in the scanlist, pack up their values, signs the content and issues a cookie. """ scanlist = environ.get(self.environ_name) assert scanlist and isinstance(scanlist, self.environ_class) content = [] for k in scanlist: v = environ.get(k) if v is not None: if type(v) is not str: raise ValueError( "The value of the environmental variable %r " "is not a str (only str is allowed; got %r)" % (k, v)) content.append("%s=%s" % (encode(k), encode(v))) if content: content = ";".join(content) content = self.signer.sign(content) cookie = '%s=%s; Path=/;' % (self.cookie_name, content) if 'https' == environ['wsgi.url_scheme']: cookie += ' secure;' response_headers.append(('Set-Cookie', cookie)) return start_response(status, response_headers, exc_info) return self.application(environ, response_hook) middleware = AuthCookieHandler # Paste Deploy entry point: def make_auth_cookie( app, global_conf, # Should this get picked up from global_conf somehow?: cookie_name='PASTE_AUTH_COOKIE', scanlist=('REMOTE_USER', 'REMOTE_SESSION'), # signer cannot be set secret=None, timeout=30, maxlen=4096): """ This middleware uses cookies to stash-away a previously authenticated user (and perhaps other variables) so that re-authentication is not needed. This does not implement sessions; and therefore N servers can be syncronized to accept the same saved authentication if they all use the same cookie_name and secret. By default, this handler scans the `environ` for the REMOTE_USER and REMOTE_SESSION key; if found, it is stored. It can be configured to scan other `environ` keys as well -- but be careful not to exceed 2-3k (so that the encoded and signed cookie does not exceed 4k). You can ask it to handle other environment variables by doing: ``environ['paste.auth.cookie'].append('your.environ.variable')`` Configuration: ``cookie_name`` The name of the cookie used to store this content, by default it is ``PASTE_AUTH_COOKIE``. ``scanlist`` This is the initial set of ``environ`` keys to save/restore to the signed cookie. By default is consists only of ``REMOTE_USER`` and ``REMOTE_SESSION``; any space-separated list of environment keys will work. However, be careful, as the total saved size is limited to around 3k. ``secret`` The secret that will be used to sign the cookies. If you don't provide one (and none is set globally) then a random secret will be created. Each time the server is restarted a new secret will then be created and all cookies will become invalid! This can be any string value. ``timeout`` The time to keep the cookie, expressed in minutes. This is handled server-side, so a new cookie with a new timeout is added to every response. ``maxlen`` The maximum length of the cookie that is sent (default 4k, which is a typical browser maximum) """ if isinstance(scanlist, basestring): scanlist = scanlist.split() if secret is None and global_conf.get('secret'): secret = global_conf['secret'] try: timeout = int(timeout) except ValueError: raise ValueError('Bad value for timeout (must be int): %r' % timeout) try: maxlen = int(maxlen) except ValueError: raise ValueError('Bad value for maxlen (must be int): %r' % maxlen) return AuthCookieHandler( app, cookie_name=cookie_name, scanlist=scanlist, secret=secret, timeout=timeout, maxlen=maxlen) __all__ = ['AuthCookieHandler', 'AuthCookieSigner', 'AuthCookieEnviron'] if "__main__" == __name__: import doctest doctest.testmod(optionflags=doctest.ELLIPSIS) Paste-1.7.5.1/paste/auth/__init__.py0000664000175000017500000000067411354703364015550 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Package for authentication/identification of requests. The objective of this package is to provide single-focused middleware components that implement a particular specification. Integration of the components into a usable system is up to a higher-level framework. """ Paste-1.7.5.1/paste/auth/digest.py0000664000175000017500000001775611443715765015310 0ustar ianbianb# (c) 2005 Clark C. Evans # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # This code was written with funding by http://prometheusresearch.com """ Digest HTTP/1.1 Authentication This module implements ``Digest`` authentication as described by RFC 2617 [1]_ . Basically, you just put this module before your application, and it takes care of requesting and handling authentication requests. This module has been tested with several common browsers "out-in-the-wild". >>> from paste.wsgilib import dump_environ >>> from paste.httpserver import serve >>> # from paste.auth.digest import digest_password, AuthDigestHandler >>> realm = 'Test Realm' >>> def authfunc(environ, realm, username): ... return digest_password(realm, username, username) >>> serve(AuthDigestHandler(dump_environ, realm, authfunc)) serving on... This code has not been audited by a security expert, please use with caution (or better yet, report security holes). At this time, this implementation does not provide for further challenges, nor does it support Authentication-Info header. It also uses md5, and an option to use sha would be a good thing. .. [1] http://www.faqs.org/rfcs/rfc2617.html """ from paste.httpexceptions import HTTPUnauthorized from paste.httpheaders import * try: from hashlib import md5 except ImportError: from md5 import md5 import time, random from urllib import quote as url_quote def digest_password(realm, username, password): """ construct the appropriate hashcode needed for HTTP digest """ return md5("%s:%s:%s" % (username, realm, password)).hexdigest() class AuthDigestAuthenticator(object): """ implementation of RFC 2617 - HTTP Digest Authentication """ def __init__(self, realm, authfunc): self.nonce = {} # list to prevent replay attacks self.authfunc = authfunc self.realm = realm def build_authentication(self, stale = ''): """ builds the authentication error """ nonce = md5( "%s:%s" % (time.time(), random.random())).hexdigest() opaque = md5( "%s:%s" % (time.time(), random.random())).hexdigest() self.nonce[nonce] = None parts = {'realm': self.realm, 'qop': 'auth', 'nonce': nonce, 'opaque': opaque } if stale: parts['stale'] = 'true' head = ", ".join(['%s="%s"' % (k, v) for (k, v) in parts.items()]) head = [("WWW-Authenticate", 'Digest %s' % head)] return HTTPUnauthorized(headers=head) def compute(self, ha1, username, response, method, path, nonce, nc, cnonce, qop): """ computes the authentication, raises error if unsuccessful """ if not ha1: return self.build_authentication() ha2 = md5('%s:%s' % (method, path)).hexdigest() if qop: chk = "%s:%s:%s:%s:%s:%s" % (ha1, nonce, nc, cnonce, qop, ha2) else: chk = "%s:%s:%s" % (ha1, nonce, ha2) if response != md5(chk).hexdigest(): if nonce in self.nonce: del self.nonce[nonce] return self.build_authentication() pnc = self.nonce.get(nonce,'00000000') if nc <= pnc: if nonce in self.nonce: del self.nonce[nonce] return self.build_authentication(stale = True) self.nonce[nonce] = nc return username def authenticate(self, environ): """ This function takes a WSGI environment and authenticates the request returning authenticated user or error. """ method = REQUEST_METHOD(environ) fullpath = url_quote(SCRIPT_NAME(environ)) + url_quote(PATH_INFO(environ)) authorization = AUTHORIZATION(environ) if not authorization: return self.build_authentication() (authmeth, auth) = authorization.split(" ", 1) if 'digest' != authmeth.lower(): return self.build_authentication() amap = {} for itm in auth.split(", "): (k,v) = [s.strip() for s in itm.split("=", 1)] amap[k] = v.replace('"', '') try: username = amap['username'] authpath = amap['uri'] nonce = amap['nonce'] realm = amap['realm'] response = amap['response'] assert authpath.split("?", 1)[0] in fullpath assert realm == self.realm qop = amap.get('qop', '') cnonce = amap.get('cnonce', '') nc = amap.get('nc', '00000000') if qop: assert 'auth' == qop assert nonce and nc except: return self.build_authentication() ha1 = self.authfunc(environ, realm, username) return self.compute(ha1, username, response, method, authpath, nonce, nc, cnonce, qop) __call__ = authenticate class AuthDigestHandler(object): """ middleware for HTTP Digest authentication (RFC 2617) This component follows the procedure below: 0. If the REMOTE_USER environment variable is already populated; then this middleware is a no-op, and the request is passed along to the application. 1. If the HTTP_AUTHORIZATION header was not provided or specifies an algorithem other than ``digest``, then a HTTPUnauthorized response is generated with the challenge. 2. If the response is malformed or or if the user's credientials do not pass muster, another HTTPUnauthorized is raised. 3. If all goes well, and the user's credintials pass; then REMOTE_USER environment variable is filled in and the AUTH_TYPE is listed as 'digest'. Parameters: ``application`` The application object is called only upon successful authentication, and can assume ``environ['REMOTE_USER']`` is set. If the ``REMOTE_USER`` is already set, this middleware is simply pass-through. ``realm`` This is a identifier for the authority that is requesting authorization. It is shown to the user and should be unique within the domain it is being used. ``authfunc`` This is a callback function which performs the actual authentication; the signature of this callback is: authfunc(environ, realm, username) -> hashcode This module provides a 'digest_password' helper function which can help construct the hashcode; it is recommended that the hashcode is stored in a database, not the user's actual password (since you only need the hashcode). """ def __init__(self, application, realm, authfunc): self.authenticate = AuthDigestAuthenticator(realm, authfunc) self.application = application def __call__(self, environ, start_response): username = REMOTE_USER(environ) if not username: result = self.authenticate(environ) if isinstance(result, str): AUTH_TYPE.update(environ,'digest') REMOTE_USER.update(environ, result) else: return result.wsgi_application(environ, start_response) return self.application(environ, start_response) middleware = AuthDigestHandler __all__ = ['digest_password', 'AuthDigestHandler' ] def make_digest(app, global_conf, realm, authfunc, **kw): """ Grant access via digest authentication Config looks like this:: [filter:grant] use = egg:Paste#auth_digest realm=myrealm authfunc=somepackage.somemodule:somefunction """ from paste.util.import_string import eval_import import types authfunc = eval_import(authfunc) assert isinstance(authfunc, types.FunctionType), "authfunc must resolve to a function" return AuthDigestHandler(app, realm, authfunc) if "__main__" == __name__: import doctest doctest.testmod(optionflags=doctest.ELLIPSIS) Paste-1.7.5.1/paste/auth/form.py0000664000175000017500000001251211354703364014746 0ustar ianbianb# (c) 2005 Clark C. Evans # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # This code was written with funding by http://prometheusresearch.com """ Authentication via HTML Form This is a very simple HTML form login screen that asks for the username and password. This middleware component requires that an authorization function taking the name and passsword and that it be placed in your application stack. This class does not include any session management code or way to save the user's authorization; however, it is easy enough to put ``paste.auth.cookie`` in your application stack. >>> from paste.wsgilib import dump_environ >>> from paste.httpserver import serve >>> from paste.auth.cookie import AuthCookieHandler >>> from paste.auth.form import AuthFormHandler >>> def authfunc(environ, username, password): ... return username == password >>> serve(AuthCookieHandler( ... AuthFormHandler(dump_environ, authfunc))) serving on... """ from paste.request import construct_url, parse_formvars TEMPLATE = """\ Please Login!

Please Login

Username:
Password:

""" class AuthFormHandler(object): """ HTML-based login middleware This causes a HTML form to be returned if ``REMOTE_USER`` is not found in the ``environ``. If the form is returned, the ``username`` and ``password`` combination are given to a user-supplied authentication function, ``authfunc``. If this is successful, then application processing continues. Parameters: ``application`` The application object is called only upon successful authentication, and can assume ``environ['REMOTE_USER']`` is set. If the ``REMOTE_USER`` is already set, this middleware is simply pass-through. ``authfunc`` This is a mandatory user-defined function which takes a ``environ``, ``username`` and ``password`` for its first three arguments. It should return ``True`` if the user is authenticated. ``template`` This is an optional (a default is provided) HTML fragment that takes exactly one ``%s`` substution argument; which *must* be used for the form's ``action`` to ensure that this middleware component does not alter the current path. The HTML form must use ``POST`` and have two input names: ``username`` and ``password``. Since the authentication form is submitted (via ``POST``) neither the ``PATH_INFO`` nor the ``QUERY_STRING`` are accessed, and hence the current path remains _unaltered_ through the entire authentication process. If authentication succeeds, the ``REQUEST_METHOD`` is converted from a ``POST`` to a ``GET``, so that a redirect is unnecessary (unlike most form auth implementations) """ def __init__(self, application, authfunc, template=None): self.application = application self.authfunc = authfunc self.template = template or TEMPLATE def __call__(self, environ, start_response): username = environ.get('REMOTE_USER','') if username: return self.application(environ, start_response) if 'POST' == environ['REQUEST_METHOD']: formvars = parse_formvars(environ, include_get_vars=False) username = formvars.get('username') password = formvars.get('password') if username and password: if self.authfunc(environ, username, password): environ['AUTH_TYPE'] = 'form' environ['REMOTE_USER'] = username environ['REQUEST_METHOD'] = 'GET' environ['CONTENT_LENGTH'] = '' environ['CONTENT_TYPE'] = '' del environ['paste.parsed_formvars'] return self.application(environ, start_response) content = self.template % construct_url(environ) start_response("200 OK", [('Content-Type', 'text/html'), ('Content-Length', str(len(content)))]) return [content] middleware = AuthFormHandler __all__ = ['AuthFormHandler'] def make_form(app, global_conf, realm, authfunc, **kw): """ Grant access via form authentication Config looks like this:: [filter:grant] use = egg:Paste#auth_form realm=myrealm authfunc=somepackage.somemodule:somefunction """ from paste.util.import_string import eval_import import types authfunc = eval_import(authfunc) assert isinstance(authfunc, types.FunctionType), "authfunc must resolve to a function" template = kw.get('template') if template is not None: template = eval_import(template) assert isinstance(template, str), "template must resolve to a string" return AuthFormHandler(app, authfunc, template) if "__main__" == __name__: import doctest doctest.testmod(optionflags=doctest.ELLIPSIS) Paste-1.7.5.1/paste/auth/grantip.py0000664000175000017500000000771411354703364015457 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Grant roles and logins based on IP address. """ from paste.util import ip4 class GrantIPMiddleware(object): """ On each request, ``ip_map`` is checked against ``REMOTE_ADDR`` and logins and roles are assigned based on that. ``ip_map`` is a map of {ip_mask: (username, roles)}. Either ``username`` or ``roles`` may be None. Roles may also be prefixed with ``-``, like ``'-system'`` meaning that role should be revoked. ``'__remove__'`` for a username will remove the username. If ``clobber_username`` is true (default) then any user specification will override the current value of ``REMOTE_USER``. ``'__remove__'`` will always clobber the username. ``ip_mask`` is something that `paste.util.ip4:IP4Range `_ can parse. Simple IP addresses, IP/mask, ip<->ip ranges, and hostnames are allowed. """ def __init__(self, app, ip_map, clobber_username=True): self.app = app self.ip_map = [] for key, value in ip_map.items(): self.ip_map.append((ip4.IP4Range(key), self._convert_user_role(value[0], value[1]))) self.clobber_username = clobber_username def _convert_user_role(self, username, roles): if roles and isinstance(roles, basestring): roles = roles.split(',') return (username, roles) def __call__(self, environ, start_response): addr = ip4.ip2int(environ['REMOTE_ADDR'], False) remove_user = False add_roles = [] for range, (username, roles) in self.ip_map: if addr in range: if roles: add_roles.extend(roles) if username == '__remove__': remove_user = True elif username: if (not environ.get('REMOTE_USER') or self.clobber_username): environ['REMOTE_USER'] = username if (remove_user and 'REMOTE_USER' in environ): del environ['REMOTE_USER'] if roles: self._set_roles(environ, add_roles) return self.app(environ, start_response) def _set_roles(self, environ, roles): cur_roles = environ.get('REMOTE_USER_TOKENS', '').split(',') # Get rid of empty roles: cur_roles = filter(None, cur_roles) remove_roles = [] for role in roles: if role.startswith('-'): remove_roles.append(role[1:]) else: if role not in cur_roles: cur_roles.append(role) for role in remove_roles: if role in cur_roles: cur_roles.remove(role) environ['REMOTE_USER_TOKENS'] = ','.join(cur_roles) def make_grantip(app, global_conf, clobber_username=False, **kw): """ Grant roles or usernames based on IP addresses. Config looks like this:: [filter:grant] use = egg:Paste#grantip clobber_username = true # Give localhost system role (no username): 127.0.0.1 = -:system # Give everyone in 192.168.0.* editor role: 192.168.0.0/24 = -:editor # Give one IP the username joe: 192.168.0.7 = joe # And one IP is should not be logged in: 192.168.0.10 = __remove__:-editor """ from paste.deploy.converters import asbool clobber_username = asbool(clobber_username) ip_map = {} for key, value in kw.items(): if ':' in value: username, role = value.split(':', 1) else: username = value role = '' if username == '-': username = '' if role == '-': role = '' ip_map[key] = value return GrantIPMiddleware(app, ip_map, clobber_username) Paste-1.7.5.1/paste/httpexceptions.py0000664000175000017500000005723611443715765016146 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php # (c) 2005 Ian Bicking, Clark C. Evans and contributors # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # Some of this code was funded by http://prometheusresearch.com """ HTTP Exception Middleware This module processes Python exceptions that relate to HTTP exceptions by defining a set of exceptions, all subclasses of HTTPException, and a request handler (`middleware`) that catches these exceptions and turns them into proper responses. This module defines exceptions according to RFC 2068 [1]_ : codes with 100-300 are not really errors; 400's are client errors, and 500's are server errors. According to the WSGI specification [2]_ , the application can call ``start_response`` more then once only under two conditions: (a) the response has not yet been sent, or (b) if the second and subsequent invocations of ``start_response`` have a valid ``exc_info`` argument obtained from ``sys.exc_info()``. The WSGI specification then requires the server or gateway to handle the case where content has been sent and then an exception was encountered. Exceptions in the 5xx range and those raised after ``start_response`` has been called are treated as serious errors and the ``exc_info`` is filled-in with information needed for a lower level module to generate a stack trace and log information. Exception HTTPException HTTPRedirection * 300 - HTTPMultipleChoices * 301 - HTTPMovedPermanently * 302 - HTTPFound * 303 - HTTPSeeOther * 304 - HTTPNotModified * 305 - HTTPUseProxy * 306 - Unused (not implemented, obviously) * 307 - HTTPTemporaryRedirect HTTPError HTTPClientError * 400 - HTTPBadRequest * 401 - HTTPUnauthorized * 402 - HTTPPaymentRequired * 403 - HTTPForbidden * 404 - HTTPNotFound * 405 - HTTPMethodNotAllowed * 406 - HTTPNotAcceptable * 407 - HTTPProxyAuthenticationRequired * 408 - HTTPRequestTimeout * 409 - HTTPConfict * 410 - HTTPGone * 411 - HTTPLengthRequired * 412 - HTTPPreconditionFailed * 413 - HTTPRequestEntityTooLarge * 414 - HTTPRequestURITooLong * 415 - HTTPUnsupportedMediaType * 416 - HTTPRequestRangeNotSatisfiable * 417 - HTTPExpectationFailed HTTPServerError * 500 - HTTPInternalServerError * 501 - HTTPNotImplemented * 502 - HTTPBadGateway * 503 - HTTPServiceUnavailable * 504 - HTTPGatewayTimeout * 505 - HTTPVersionNotSupported References: .. [1] http://www.python.org/peps/pep-0333.html#error-handling .. [2] http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5 """ import types from paste.wsgilib import catch_errors_app from paste.response import has_header, header_value, replace_header from paste.request import resolve_relative_url from paste.util.quoting import strip_html, html_quote, no_quote, comment_quote SERVER_NAME = 'WSGI Server' TEMPLATE = """\ \r %(title)s\r \r

%(title)s

\r

%(body)s

\r
\r
%(server)s
\r \r \r """ class HTTPException(Exception): """ the HTTP exception base class This encapsulates an HTTP response that interrupts normal application flow; but one which is not necessarly an error condition. For example, codes in the 300's are exceptions in that they interrupt normal processing; however, they are not considered errors. This class is complicated by 4 factors: 1. The content given to the exception may either be plain-text or as html-text. 2. The template may want to have string-substitutions taken from the current ``environ`` or values from incoming headers. This is especially troublesome due to case sensitivity. 3. The final output may either be text/plain or text/html mime-type as requested by the client application. 4. Each exception has a default explanation, but those who raise exceptions may want to provide additional detail. Attributes: ``code`` the HTTP status code for the exception ``title`` remainder of the status line (stuff after the code) ``explanation`` a plain-text explanation of the error message that is not subject to environment or header substitutions; it is accessible in the template via %(explanation)s ``detail`` a plain-text message customization that is not subject to environment or header substitutions; accessible in the template via %(detail)s ``template`` a content fragment (in HTML) used for environment and header substitution; the default template includes both the explanation and further detail provided in the message ``required_headers`` a sequence of headers which are required for proper construction of the exception Parameters: ``detail`` a plain-text override of the default ``detail`` ``headers`` a list of (k,v) header pairs ``comment`` a plain-text additional information which is usually stripped/hidden for end-users To override the template (which is HTML content) or the plain-text explanation, one must subclass the given exception; or customize it after it has been created. This particular breakdown of a message into explanation, detail and template allows both the creation of plain-text and html messages for various clients as well as error-free substitution of environment variables and headers. """ code = None title = None explanation = '' detail = '' comment = '' template = "%(explanation)s\r\n
%(detail)s\r\n" required_headers = () def __init__(self, detail=None, headers=None, comment=None): assert self.code, "Do not directly instantiate abstract exceptions." assert isinstance(headers, (type(None), list)), ( "headers must be None or a list: %r" % headers) assert isinstance(detail, (type(None), basestring)), ( "detail must be None or a string: %r" % detail) assert isinstance(comment, (type(None), basestring)), ( "comment must be None or a string: %r" % comment) self.headers = headers or tuple() for req in self.required_headers: assert headers and has_header(headers, req), ( "Exception %s must be passed the header %r " "(got headers: %r)" % (self.__class__.__name__, req, headers)) if detail is not None: self.detail = detail if comment is not None: self.comment = comment Exception.__init__(self,"%s %s\n%s\n%s\n" % ( self.code, self.title, self.explanation, self.detail)) def make_body(self, environ, template, escfunc, comment_escfunc=None): comment_escfunc = comment_escfunc or escfunc args = {'explanation': escfunc(self.explanation), 'detail': escfunc(self.detail), 'comment': comment_escfunc(self.comment)} if HTTPException.template != self.template: for (k, v) in environ.items(): args[k] = escfunc(v) if self.headers: for (k, v) in self.headers: args[k.lower()] = escfunc(v) for key, value in args.items(): if isinstance(value, unicode): args[key] = value.encode('utf8', 'xmlcharrefreplace') return template % args def plain(self, environ): """ text/plain representation of the exception """ body = self.make_body(environ, strip_html(self.template), no_quote, comment_quote) return ('%s %s\r\n%s\r\n' % (self.code, self.title, body)) def html(self, environ): """ text/html representation of the exception """ body = self.make_body(environ, self.template, html_quote, comment_quote) return TEMPLATE % { 'title': self.title, 'code': self.code, 'server': SERVER_NAME, 'body': body } def prepare_content(self, environ): if self.headers: headers = list(self.headers) else: headers = [] if 'html' in environ.get('HTTP_ACCEPT','') or \ '*/*' in environ.get('HTTP_ACCEPT',''): replace_header(headers, 'content-type', 'text/html') content = self.html(environ) else: replace_header(headers, 'content-type', 'text/plain') content = self.plain(environ) if isinstance(content, unicode): content = content.encode('utf8') cur_content_type = ( header_value(headers, 'content-type') or 'text/html') replace_header( headers, 'content-type', cur_content_type + '; charset=utf8') return headers, content def response(self, environ): from paste.wsgiwrappers import WSGIResponse headers, content = self.prepare_content(environ) resp = WSGIResponse(code=self.code, content=content) resp.headers = resp.headers.fromlist(headers) return resp def wsgi_application(self, environ, start_response, exc_info=None): """ This exception as a WSGI application """ headers, content = self.prepare_content(environ) start_response('%s %s' % (self.code, self.title), headers, exc_info) return [content] __call__ = wsgi_application def __repr__(self): return '<%s %s; code=%s>' % (self.__class__.__name__, self.title, self.code) class HTTPError(HTTPException): """ base class for status codes in the 400's and 500's This is an exception which indicates that an error has occurred, and that any work in progress should not be committed. These are typically results in the 400's and 500's. """ # # 3xx Redirection # # This class of status code indicates that further action needs to be # taken by the user agent in order to fulfill the request. The action # required MAY be carried out by the user agent without interaction with # the user if and only if the method used in the second request is GET or # HEAD. A client SHOULD detect infinite redirection loops, since such # loops generate network traffic for each redirection. # class HTTPRedirection(HTTPException): """ base class for 300's status code (redirections) This is an abstract base class for 3xx redirection. It indicates that further action needs to be taken by the user agent in order to fulfill the request. It does not necessarly signal an error condition. """ class _HTTPMove(HTTPRedirection): """ redirections which require a Location field Since a 'Location' header is a required attribute of 301, 302, 303, 305 and 307 (but not 304), this base class provides the mechanics to make this easy. While this has the same parameters as HTTPException, if a location is not provided in the headers; it is assumed that the detail _is_ the location (this for backward compatibility, otherwise we'd add a new attribute). """ required_headers = ('location',) explanation = 'The resource has been moved to' template = ( '%(explanation)s %(location)s;\r\n' 'you should be redirected automatically.\r\n' '%(detail)s\r\n') def __init__(self, detail=None, headers=None, comment=None): assert isinstance(headers, (type(None), list)) headers = headers or [] location = header_value(headers,'location') if not location: location = detail detail = '' headers.append(('location', location)) assert location, ("HTTPRedirection specified neither a " "location in the headers nor did it " "provide a detail argument.") HTTPRedirection.__init__(self, location, headers, comment) if detail is not None: self.detail = detail def relative_redirect(cls, dest_uri, environ, detail=None, headers=None, comment=None): """ Create a redirect object with the dest_uri, which may be relative, considering it relative to the uri implied by the given environ. """ location = resolve_relative_url(dest_uri, environ) headers = headers or [] headers.append(('Location', location)) return cls(detail=detail, headers=headers, comment=comment) relative_redirect = classmethod(relative_redirect) def location(self): for name, value in self.headers: if name.lower() == 'location': return value else: raise KeyError("No location set for %s" % self) class HTTPMultipleChoices(_HTTPMove): code = 300 title = 'Multiple Choices' class HTTPMovedPermanently(_HTTPMove): code = 301 title = 'Moved Permanently' class HTTPFound(_HTTPMove): code = 302 title = 'Found' explanation = 'The resource was found at' # This one is safe after a POST (the redirected location will be # retrieved with GET): class HTTPSeeOther(_HTTPMove): code = 303 title = 'See Other' class HTTPNotModified(HTTPRedirection): # @@: but not always (HTTP section 14.18.1)...? # @@: Removed 'date' requirement, as its not required for an ETag # @@: FIXME: This should require either an ETag or a date header code = 304 title = 'Not Modified' message = '' # @@: should include date header, optionally other headers # @@: should not return a content body def plain(self, environ): return '' def html(self, environ): """ text/html representation of the exception """ return '' class HTTPUseProxy(_HTTPMove): # @@: OK, not a move, but looks a little like one code = 305 title = 'Use Proxy' explanation = ( 'The resource must be accessed through a proxy ' 'located at') class HTTPTemporaryRedirect(_HTTPMove): code = 307 title = 'Temporary Redirect' # # 4xx Client Error # # The 4xx class of status code is intended for cases in which the client # seems to have erred. Except when responding to a HEAD request, the # server SHOULD include an entity containing an explanation of the error # situation, and whether it is a temporary or permanent condition. These # status codes are applicable to any request method. User agents SHOULD # display any included entity to the user. # class HTTPClientError(HTTPError): """ base class for the 400's, where the client is in-error This is an error condition in which the client is presumed to be in-error. This is an expected problem, and thus is not considered a bug. A server-side traceback is not warranted. Unless specialized, this is a '400 Bad Request' """ code = 400 title = 'Bad Request' explanation = ('The server could not comply with the request since\r\n' 'it is either malformed or otherwise incorrect.\r\n') class HTTPBadRequest(HTTPClientError): pass class HTTPUnauthorized(HTTPClientError): code = 401 title = 'Unauthorized' explanation = ( 'This server could not verify that you are authorized to\r\n' 'access the document you requested. Either you supplied the\r\n' 'wrong credentials (e.g., bad password), or your browser\r\n' 'does not understand how to supply the credentials required.\r\n') class HTTPPaymentRequired(HTTPClientError): code = 402 title = 'Payment Required' explanation = ('Access was denied for financial reasons.') class HTTPForbidden(HTTPClientError): code = 403 title = 'Forbidden' explanation = ('Access was denied to this resource.') class HTTPNotFound(HTTPClientError): code = 404 title = 'Not Found' explanation = ('The resource could not be found.') class HTTPMethodNotAllowed(HTTPClientError): required_headers = ('allow',) code = 405 title = 'Method Not Allowed' # override template since we need an environment variable template = ('The method %(REQUEST_METHOD)s is not allowed for ' 'this resource.\r\n%(detail)s') class HTTPNotAcceptable(HTTPClientError): code = 406 title = 'Not Acceptable' # override template since we need an environment variable template = ('The resource could not be generated that was ' 'acceptable to your browser (content\r\nof type ' '%(HTTP_ACCEPT)s).\r\n%(detail)s') class HTTPProxyAuthenticationRequired(HTTPClientError): code = 407 title = 'Proxy Authentication Required' explanation = ('Authentication /w a local proxy is needed.') class HTTPRequestTimeout(HTTPClientError): code = 408 title = 'Request Timeout' explanation = ('The server has waited too long for the request to ' 'be sent by the client.') class HTTPConflict(HTTPClientError): code = 409 title = 'Conflict' explanation = ('There was a conflict when trying to complete ' 'your request.') class HTTPGone(HTTPClientError): code = 410 title = 'Gone' explanation = ('This resource is no longer available. No forwarding ' 'address is given.') class HTTPLengthRequired(HTTPClientError): code = 411 title = 'Length Required' explanation = ('Content-Length header required.') class HTTPPreconditionFailed(HTTPClientError): code = 412 title = 'Precondition Failed' explanation = ('Request precondition failed.') class HTTPRequestEntityTooLarge(HTTPClientError): code = 413 title = 'Request Entity Too Large' explanation = ('The body of your request was too large for this server.') class HTTPRequestURITooLong(HTTPClientError): code = 414 title = 'Request-URI Too Long' explanation = ('The request URI was too long for this server.') class HTTPUnsupportedMediaType(HTTPClientError): code = 415 title = 'Unsupported Media Type' # override template since we need an environment variable template = ('The request media type %(CONTENT_TYPE)s is not ' 'supported by this server.\r\n%(detail)s') class HTTPRequestRangeNotSatisfiable(HTTPClientError): code = 416 title = 'Request Range Not Satisfiable' explanation = ('The Range requested is not available.') class HTTPExpectationFailed(HTTPClientError): code = 417 title = 'Expectation Failed' explanation = ('Expectation failed.') # # 5xx Server Error # # Response status codes beginning with the digit "5" indicate cases in # which the server is aware that it has erred or is incapable of # performing the request. Except when responding to a HEAD request, the # server SHOULD include an entity containing an explanation of the error # situation, and whether it is a temporary or permanent condition. User # agents SHOULD display any included entity to the user. These response # codes are applicable to any request method. # class HTTPServerError(HTTPError): """ base class for the 500's, where the server is in-error This is an error condition in which the server is presumed to be in-error. This is usually unexpected, and thus requires a traceback; ideally, opening a support ticket for the customer. Unless specialized, this is a '500 Internal Server Error' """ code = 500 title = 'Internal Server Error' explanation = ( 'The server has either erred or is incapable of performing\r\n' 'the requested operation.\r\n') class HTTPInternalServerError(HTTPServerError): pass class HTTPNotImplemented(HTTPServerError): code = 501 title = 'Not Implemented' # override template since we need an environment variable template = ('The request method %(REQUEST_METHOD)s is not implemented ' 'for this server.\r\n%(detail)s') class HTTPBadGateway(HTTPServerError): code = 502 title = 'Bad Gateway' explanation = ('Bad gateway.') class HTTPServiceUnavailable(HTTPServerError): code = 503 title = 'Service Unavailable' explanation = ('The server is currently unavailable. ' 'Please try again at a later time.') class HTTPGatewayTimeout(HTTPServerError): code = 504 title = 'Gateway Timeout' explanation = ('The gateway has timed out.') class HTTPVersionNotSupported(HTTPServerError): code = 505 title = 'HTTP Version Not Supported' explanation = ('The HTTP version is not supported.') # abstract HTTP related exceptions __all__ = ['HTTPException', 'HTTPRedirection', 'HTTPError' ] _exceptions = {} for name, value in globals().items(): if (isinstance(value, (type, types.ClassType)) and issubclass(value, HTTPException) and value.code): _exceptions[value.code] = value __all__.append(name) def get_exception(code): return _exceptions[code] ############################################################ ## Middleware implementation: ############################################################ class HTTPExceptionHandler(object): """ catches exceptions and turns them into proper HTTP responses This middleware catches any exceptions (which are subclasses of ``HTTPException``) and turns them into proper HTTP responses. Note if the headers have already been sent, the stack trace is always maintained as this indicates a programming error. Note that you must raise the exception before returning the app_iter, and you cannot use this with generator apps that don't raise an exception until after their app_iter is iterated over. """ def __init__(self, application, warning_level=None): assert not warning_level or ( warning_level > 99 and warning_level < 600) if warning_level is not None: import warnings warnings.warn('The warning_level parameter is not used or supported', DeprecationWarning, 2) self.warning_level = warning_level or 500 self.application = application def __call__(self, environ, start_response): environ['paste.httpexceptions'] = self environ.setdefault('paste.expected_exceptions', []).append(HTTPException) try: return self.application(environ, start_response) except HTTPException, exc: return exc(environ, start_response) def middleware(*args, **kw): import warnings # deprecated 13 dec 2005 warnings.warn('httpexceptions.middleware is deprecated; use ' 'make_middleware or HTTPExceptionHandler instead', DeprecationWarning, 2) return make_middleware(*args, **kw) def make_middleware(app, global_conf=None, warning_level=None): """ ``httpexceptions`` middleware; this catches any ``paste.httpexceptions.HTTPException`` exceptions (exceptions like ``HTTPNotFound``, ``HTTPMovedPermanently``, etc) and turns them into proper HTTP responses. ``warning_level`` can be an integer corresponding to an HTTP code. Any code over that value will be passed 'up' the chain, potentially reported on by another piece of middleware. """ if warning_level: warning_level = int(warning_level) return HTTPExceptionHandler(app, warning_level=warning_level) __all__.extend(['HTTPExceptionHandler', 'get_exception']) Paste-1.7.5.1/paste/registry.py0000664000175000017500000005326111437571722014723 0ustar ianbianb# (c) 2005 Ben Bangert # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php """Registry for handling request-local module globals sanely Dealing with module globals in a thread-safe way is good if your application is the sole responder in a thread, however that approach fails to properly account for various scenarios that occur with WSGI applications and middleware. What is actually needed in the case where a module global is desired that is always set properly depending on the current request, is a stacked thread-local object. Such an object is popped or pushed during the request cycle so that it properly represents the object that should be active for the current request. To make it easy to deal with such variables, this module provides a special StackedObjectProxy class which you can instantiate and attach to your module where you'd like others to access it. The object you'd like this to actually "be" during the request is then registered with the RegistryManager middleware, which ensures that for the scope of the current WSGI application everything will work properly. Example: .. code-block:: python #yourpackage/__init__.py from paste.registry import RegistryManager, StackedObjectProxy myglobal = StackedObjectProxy() #wsgi app stack app = RegistryManager(yourapp) #inside your wsgi app class yourapp(object): def __call__(self, environ, start_response): obj = someobject # The request-local object you want to access # via yourpackage.myglobal if environ.has_key('paste.registry'): environ['paste.registry'].register(myglobal, obj) You will then be able to import yourpackage anywhere in your WSGI app or in the calling stack below it and be assured that it is using the object you registered with Registry. RegistryManager can be in the WSGI stack multiple times, each time it appears it registers a new request context. Performance =========== The overhead of the proxy object is very minimal, however if you are using proxy objects extensively (Thousands of accesses per request or more), there are some ways to avoid them. A proxy object runs approximately 3-20x slower than direct access to the object, this is rarely your performance bottleneck when developing web applications. Should you be developing a system which may be accessing the proxy object thousands of times per request, the performance of the proxy will start to become more noticeable. In that circumstance, the problem can be avoided by getting at the actual object via the proxy with the ``_current_obj`` function: .. code-block:: python #sessions.py Session = StackedObjectProxy() # ... initialization code, etc. # somemodule.py import sessions def somefunc(): session = sessions.Session._current_obj() # ... tons of session access This way the proxy is used only once to retrieve the object for the current context and the overhead is minimized while still making it easy to access the underlying object. The ``_current_obj`` function is preceded by an underscore to more likely avoid clashing with the contained object's attributes. **NOTE:** This is *highly* unlikely to be an issue in the vast majority of cases, and requires incredibly large amounts of proxy object access before one should consider the proxy object to be causing slow-downs. This section is provided solely in the extremely rare case that it is an issue so that a quick way to work around it is documented. """ import sys import paste.util.threadinglocal as threadinglocal __all__ = ['StackedObjectProxy', 'RegistryManager', 'StackedObjectRestorer', 'restorer'] class NoDefault(object): pass class StackedObjectProxy(object): """Track an object instance internally using a stack The StackedObjectProxy proxies access to an object internally using a stacked thread-local. This makes it safe for complex WSGI environments where access to the object may be desired in multiple places without having to pass the actual object around. New objects are added to the top of the stack with _push_object while objects can be removed with _pop_object. """ def __init__(self, default=NoDefault, name="Default"): """Create a new StackedObjectProxy If a default is given, its used in every thread if no other object has been pushed on. """ self.__dict__['____name__'] = name self.__dict__['____local__'] = threadinglocal.local() if default is not NoDefault: self.__dict__['____default_object__'] = default def __dir__(self): """Return a list of the StackedObjectProxy's and proxied object's (if one exists) names. """ dir_list = dir(self.__class__) + self.__dict__.keys() try: dir_list.extend(dir(self._current_obj())) except TypeError: pass dir_list.sort() return dir_list def __getattr__(self, attr): return getattr(self._current_obj(), attr) def __setattr__(self, attr, value): setattr(self._current_obj(), attr, value) def __delattr__(self, name): delattr(self._current_obj(), name) def __getitem__(self, key): return self._current_obj()[key] def __setitem__(self, key, value): self._current_obj()[key] = value def __delitem__(self, key): del self._current_obj()[key] def __call__(self, *args, **kw): return self._current_obj()(*args, **kw) def __repr__(self): try: return repr(self._current_obj()) except (TypeError, AttributeError): return '<%s.%s object at 0x%x>' % (self.__class__.__module__, self.__class__.__name__, id(self)) def __iter__(self): return iter(self._current_obj()) def __len__(self): return len(self._current_obj()) def __contains__(self, key): return key in self._current_obj() def __nonzero__(self): return bool(self._current_obj()) def _current_obj(self): """Returns the current active object being proxied to In the event that no object was pushed, the default object if provided will be used. Otherwise, a TypeError will be raised. """ try: objects = self.____local__.objects except AttributeError: objects = None if objects: return objects[-1] else: obj = self.__dict__.get('____default_object__', NoDefault) if obj is not NoDefault: return obj else: raise TypeError( 'No object (name: %s) has been registered for this ' 'thread' % self.____name__) def _push_object(self, obj): """Make ``obj`` the active object for this thread-local. This should be used like: .. code-block:: python obj = yourobject() module.glob = StackedObjectProxy() module.glob._push_object(obj) try: ... do stuff ... finally: module.glob._pop_object(conf) """ try: self.____local__.objects.append(obj) except AttributeError: self.____local__.objects = [] self.____local__.objects.append(obj) def _pop_object(self, obj=None): """Remove a thread-local object. If ``obj`` is given, it is checked against the popped object and an error is emitted if they don't match. """ try: popped = self.____local__.objects.pop() if obj and popped is not obj: raise AssertionError( 'The object popped (%s) is not the same as the object ' 'expected (%s)' % (popped, obj)) except AttributeError: raise AssertionError('No object has been registered for this thread') def _object_stack(self): """Returns all of the objects stacked in this container (Might return [] if there are none) """ try: try: objs = self.____local__.objects except AttributeError: return [] return objs[:] except AssertionError: return [] # The following methods will be swapped for their original versions by # StackedObjectRestorer when restoration is enabled. The original # functions (e.g. _current_obj) will be available at _current_obj_orig def _current_obj_restoration(self): request_id = restorer.in_restoration() if request_id: return restorer.get_saved_proxied_obj(self, request_id) return self._current_obj_orig() _current_obj_restoration.__doc__ = \ ('%s\n(StackedObjectRestorer restoration enabled)' % \ _current_obj.__doc__) def _push_object_restoration(self, obj): if not restorer.in_restoration(): self._push_object_orig(obj) _push_object_restoration.__doc__ = \ ('%s\n(StackedObjectRestorer restoration enabled)' % \ _push_object.__doc__) def _pop_object_restoration(self, obj=None): if not restorer.in_restoration(): self._pop_object_orig(obj) _pop_object_restoration.__doc__ = \ ('%s\n(StackedObjectRestorer restoration enabled)' % \ _pop_object.__doc__) class Registry(object): """Track objects and stacked object proxies for removal The Registry object is instantiated a single time for the request no matter how many times the RegistryManager is used in a WSGI stack. Each RegistryManager must call ``prepare`` before continuing the call to start a new context for object registering. Each context is tracked with a dict inside a list. The last list element is the currently executing context. Each context dict is keyed by the id of the StackedObjectProxy instance being proxied, the value is a tuple of the StackedObjectProxy instance and the object being tracked. """ def __init__(self): """Create a new Registry object ``prepare`` must still be called before this Registry object can be used to register objects. """ self.reglist = [] def prepare(self): """Used to create a new registry context Anytime a new RegistryManager is called, ``prepare`` needs to be called on the existing Registry object. This sets up a new context for registering objects. """ self.reglist.append({}) def register(self, stacked, obj): """Register an object with a StackedObjectProxy""" myreglist = self.reglist[-1] stacked_id = id(stacked) if stacked_id in myreglist: stacked._pop_object(myreglist[stacked_id][1]) del myreglist[stacked_id] stacked._push_object(obj) myreglist[stacked_id] = (stacked, obj) def multiregister(self, stacklist): """Register a list of tuples Similar call semantics as register, except this registers multiple objects at once. Example:: registry.multiregister([(sop, obj), (anothersop, anotherobj)]) """ myreglist = self.reglist[-1] for stacked, obj in stacklist: stacked_id = id(stacked) if stacked_id in myreglist: stacked._pop_object(myreglist[stacked_id][1]) del myreglist[stacked_id] stacked._push_object(obj) myreglist[stacked_id] = (stacked, obj) # Replace now does the same thing as register replace = register def cleanup(self): """Remove all objects from all StackedObjectProxy instances that were tracked at this Registry context""" for stacked, obj in self.reglist[-1].itervalues(): stacked._pop_object(obj) self.reglist.pop() class RegistryManager(object): """Creates and maintains a Registry context RegistryManager creates a new registry context for the registration of StackedObjectProxy instances. Multiple RegistryManager's can be in a WSGI stack and will manage the context so that the StackedObjectProxies always proxy to the proper object. The object being registered can be any object sub-class, list, or dict. Registering objects is done inside a WSGI application under the RegistryManager instance, using the ``environ['paste.registry']`` object which is a Registry instance. """ def __init__(self, application, streaming=False): self.application = application self.streaming = streaming def __call__(self, environ, start_response): app_iter = None reg = environ.setdefault('paste.registry', Registry()) reg.prepare() if self.streaming: return self.streaming_iter(reg, environ, start_response) try: app_iter = self.application(environ, start_response) except Exception, e: # Regardless of if the content is an iterable, generator, list # or tuple, we clean-up right now. If its an iterable/generator # care should be used to ensure the generator has its own ref # to the actual object if environ.get('paste.evalexception'): # EvalException is present in the WSGI stack expected = False for expect in environ.get('paste.expected_exceptions', []): if isinstance(e, expect): expected = True if not expected: # An unexpected exception: save state for EvalException restorer.save_registry_state(environ) reg.cleanup() raise except: # Save state for EvalException if it's present if environ.get('paste.evalexception'): restorer.save_registry_state(environ) reg.cleanup() raise else: reg.cleanup() return app_iter def streaming_iter(self, reg, environ, start_response): try: for item in self.application(environ, start_response): yield item except Exception, e: # Regardless of if the content is an iterable, generator, list # or tuple, we clean-up right now. If its an iterable/generator # care should be used to ensure the generator has its own ref # to the actual object if environ.get('paste.evalexception'): # EvalException is present in the WSGI stack expected = False for expect in environ.get('paste.expected_exceptions', []): if isinstance(e, expect): expected = True if not expected: # An unexpected exception: save state for EvalException restorer.save_registry_state(environ) reg.cleanup() raise except: # Save state for EvalException if it's present if environ.get('paste.evalexception'): restorer.save_registry_state(environ) reg.cleanup() raise else: reg.cleanup() class StackedObjectRestorer(object): """Track StackedObjectProxies and their proxied objects for automatic restoration within EvalException's interactive debugger. An instance of this class tracks all StackedObjectProxy state in existence when unexpected exceptions are raised by WSGI applications housed by EvalException and RegistryManager. Like EvalException, this information is stored for the life of the process. When an unexpected exception occurs and EvalException is present in the WSGI stack, save_registry_state is intended to be called to store the Registry state and enable automatic restoration on all currently registered StackedObjectProxies. With restoration enabled, those StackedObjectProxies' _current_obj (overwritten by _current_obj_restoration) method's strategy is modified: it will return its appropriate proxied object from the restorer when a restoration context is active in the current thread. The StackedObjectProxies' _push/pop_object methods strategies are also changed: they no-op when a restoration context is active in the current thread (because the pushing/popping work is all handled by the Registry/restorer). The request's Registry objects' reglists are restored from the restorer when a restoration context begins, enabling the Registry methods to work while their changes are tracked by the restorer. The overhead of enabling restoration is negligible (another threadlocal access for the changed StackedObjectProxy methods) for normal use outside of a restoration context, but worth mentioning when combined with StackedObjectProxies normal overhead. Once enabled it does not turn off, however: o Enabling restoration only occurs after an unexpected exception is detected. The server is likely to be restarted shortly after the exception is raised to fix the cause o StackedObjectRestorer is only enabled when EvalException is enabled (not on a production server) and RegistryManager exists in the middleware stack""" def __init__(self): # Registries and their saved reglists by request_id self.saved_registry_states = {} self.restoration_context_id = threadinglocal.local() def save_registry_state(self, environ): """Save the state of this request's Registry (if it hasn't already been saved) to the saved_registry_states dict, keyed by the request's unique identifier""" registry = environ.get('paste.registry') if not registry or not len(registry.reglist) or \ self.get_request_id(environ) in self.saved_registry_states: # No Registry, no state to save, or this request's state has # already been saved return self.saved_registry_states[self.get_request_id(environ)] = \ (registry, registry.reglist[:]) # Tweak the StackedObjectProxies we want to save state for -- change # their methods to act differently when a restoration context is active # in the current thread for reglist in registry.reglist: for stacked, obj in reglist.itervalues(): self.enable_restoration(stacked) def get_saved_proxied_obj(self, stacked, request_id): """Retrieve the saved object proxied by the specified StackedObjectProxy for the request identified by request_id""" # All state for the request identified by request_id reglist = self.saved_registry_states[request_id][1] # The top of the stack was current when the exception occurred stack_level = len(reglist) - 1 stacked_id = id(stacked) while True: if stack_level < 0: # Nothing registered: Call _current_obj_orig to raise a # TypeError return stacked._current_obj_orig() context = reglist[stack_level] if stacked_id in context: break # This StackedObjectProxy may not have been registered by the # RegistryManager that was active when the exception was raised -- # continue searching down the stack until it's found stack_level -= 1 return context[stacked_id][1] def enable_restoration(self, stacked): """Replace the specified StackedObjectProxy's methods with their respective restoration versions. _current_obj_restoration forces recovery of the saved proxied object when a restoration context is active in the current thread. _push/pop_object_restoration avoid pushing/popping data (pushing/popping is only done at the Registry level) when a restoration context is active in the current thread""" if '_current_obj_orig' in stacked.__dict__: # Restoration already enabled return for func_name in ('_current_obj', '_push_object', '_pop_object'): orig_func = getattr(stacked, func_name) restoration_func = getattr(stacked, func_name + '_restoration') stacked.__dict__[func_name + '_orig'] = orig_func stacked.__dict__[func_name] = restoration_func def get_request_id(self, environ): """Return a unique identifier for the current request""" from paste.evalexception.middleware import get_debug_count return get_debug_count(environ) def restoration_begin(self, request_id): """Enable a restoration context in the current thread for the specified request_id""" if request_id in self.saved_registry_states: # Restore the old Registry object's state registry, reglist = self.saved_registry_states[request_id] registry.reglist = reglist self.restoration_context_id.request_id = request_id def restoration_end(self): """Register a restoration context as finished, if one exists""" try: del self.restoration_context_id.request_id except AttributeError: pass def in_restoration(self): """Determine if a restoration context is active for the current thread. Returns the request_id it's active for if so, otherwise False""" return getattr(self.restoration_context_id, 'request_id', False) restorer = StackedObjectRestorer() # Paste Deploy entry point def make_registry_manager(app, global_conf): return RegistryManager(app) make_registry_manager.__doc__ = RegistryManager.__doc__ Paste-1.7.5.1/paste/wsgilib.py0000664000175000017500000004724611354703364014516 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ A module of many disparate routines. """ # functions which moved to paste.request and paste.response # Deprecated around 15 Dec 2005 from paste.request import get_cookies, parse_querystring, parse_formvars from paste.request import construct_url, path_info_split, path_info_pop from paste.response import HeaderDict, has_header, header_value, remove_header from paste.response import error_body_response, error_response, error_response_app from traceback import print_exception import urllib from cStringIO import StringIO import sys from urlparse import urlsplit import warnings __all__ = ['add_close', 'add_start_close', 'capture_output', 'catch_errors', 'catch_errors_app', 'chained_app_iters', 'construct_url', 'dump_environ', 'encode_unicode_app_iter', 'error_body_response', 'error_response', 'get_cookies', 'has_header', 'header_value', 'interactive', 'intercept_output', 'path_info_pop', 'path_info_split', 'raw_interactive', 'send_file'] class add_close(object): """ An an iterable that iterates over app_iter, then calls close_func. """ def __init__(self, app_iterable, close_func): self.app_iterable = app_iterable self.app_iter = iter(app_iterable) self.close_func = close_func self._closed = False def __iter__(self): return self def next(self): return self.app_iter.next() def close(self): self._closed = True if hasattr(self.app_iterable, 'close'): self.app_iterable.close() self.close_func() def __del__(self): if not self._closed: # We can't raise an error or anything at this stage print >> sys.stderr, ( "Error: app_iter.close() was not called when finishing " "WSGI request. finalization function %s not called" % self.close_func) class add_start_close(object): """ An an iterable that iterates over app_iter, calls start_func before the first item is returned, then calls close_func at the end. """ def __init__(self, app_iterable, start_func, close_func=None): self.app_iterable = app_iterable self.app_iter = iter(app_iterable) self.first = True self.start_func = start_func self.close_func = close_func self._closed = False def __iter__(self): return self def next(self): if self.first: self.start_func() self.first = False return self.app_iter.next() def close(self): self._closed = True if hasattr(self.app_iterable, 'close'): self.app_iterable.close() if self.close_func is not None: self.close_func() def __del__(self): if not self._closed: # We can't raise an error or anything at this stage print >> sys.stderr, ( "Error: app_iter.close() was not called when finishing " "WSGI request. finalization function %s not called" % self.close_func) class chained_app_iters(object): """ Chains several app_iters together, also delegating .close() to each of them. """ def __init__(self, *chained): self.app_iters = chained self.chained = [iter(item) for item in chained] self._closed = False def __iter__(self): return self def next(self): if len(self.chained) == 1: return self.chained[0].next() else: try: return self.chained[0].next() except StopIteration: self.chained.pop(0) return self.next() def close(self): self._closed = True got_exc = None for app_iter in self.app_iters: try: if hasattr(app_iter, 'close'): app_iter.close() except: got_exc = sys.exc_info() if got_exc: raise got_exc[0], got_exc[1], got_exc[2] def __del__(self): if not self._closed: # We can't raise an error or anything at this stage print >> sys.stderr, ( "Error: app_iter.close() was not called when finishing " "WSGI request. finalization function %s not called" % self.close_func) class encode_unicode_app_iter(object): """ Encodes an app_iterable's unicode responses as strings """ def __init__(self, app_iterable, encoding=sys.getdefaultencoding(), errors='strict'): self.app_iterable = app_iterable self.app_iter = iter(app_iterable) self.encoding = encoding self.errors = errors def __iter__(self): return self def next(self): content = self.app_iter.next() if isinstance(content, unicode): content = content.encode(self.encoding, self.errors) return content def close(self): if hasattr(self.app_iterable, 'close'): self.app_iterable.close() def catch_errors(application, environ, start_response, error_callback, ok_callback=None): """ Runs the application, and returns the application iterator (which should be passed upstream). If an error occurs then error_callback will be called with exc_info as its sole argument. If no errors occur and ok_callback is given, then it will be called with no arguments. """ try: app_iter = application(environ, start_response) except: error_callback(sys.exc_info()) raise if type(app_iter) in (list, tuple): # These won't produce exceptions if ok_callback: ok_callback() return app_iter else: return _wrap_app_iter(app_iter, error_callback, ok_callback) class _wrap_app_iter(object): def __init__(self, app_iterable, error_callback, ok_callback): self.app_iterable = app_iterable self.app_iter = iter(app_iterable) self.error_callback = error_callback self.ok_callback = ok_callback if hasattr(self.app_iterable, 'close'): self.close = self.app_iterable.close def __iter__(self): return self def next(self): try: return self.app_iter.next() except StopIteration: if self.ok_callback: self.ok_callback() raise except: self.error_callback(sys.exc_info()) raise def catch_errors_app(application, environ, start_response, error_callback_app, ok_callback=None, catch=Exception): """ Like ``catch_errors``, except error_callback_app should be a callable that will receive *three* arguments -- ``environ``, ``start_response``, and ``exc_info``. It should call ``start_response`` (*with* the exc_info argument!) and return an iterator. """ try: app_iter = application(environ, start_response) except catch: return error_callback_app(environ, start_response, sys.exc_info()) if type(app_iter) in (list, tuple): # These won't produce exceptions if ok_callback is not None: ok_callback() return app_iter else: return _wrap_app_iter_app( environ, start_response, app_iter, error_callback_app, ok_callback, catch=catch) class _wrap_app_iter_app(object): def __init__(self, environ, start_response, app_iterable, error_callback_app, ok_callback, catch=Exception): self.environ = environ self.start_response = start_response self.app_iterable = app_iterable self.app_iter = iter(app_iterable) self.error_callback_app = error_callback_app self.ok_callback = ok_callback self.catch = catch if hasattr(self.app_iterable, 'close'): self.close = self.app_iterable.close def __iter__(self): return self def next(self): try: return self.app_iter.next() except StopIteration: if self.ok_callback: self.ok_callback() raise except self.catch: if hasattr(self.app_iterable, 'close'): try: self.app_iterable.close() except: # @@: Print to wsgi.errors? pass new_app_iterable = self.error_callback_app( self.environ, self.start_response, sys.exc_info()) app_iter = iter(new_app_iterable) if hasattr(new_app_iterable, 'close'): self.close = new_app_iterable.close self.next = app_iter.next return self.next() def raw_interactive(application, path='', raise_on_wsgi_error=False, **environ): """ Runs the application in a fake environment. """ assert "path_info" not in environ, "argument list changed" if raise_on_wsgi_error: errors = ErrorRaiser() else: errors = StringIO() basic_environ = { # mandatory CGI variables 'REQUEST_METHOD': 'GET', # always mandatory 'SCRIPT_NAME': '', # may be empty if app is at the root 'PATH_INFO': '', # may be empty if at root of app 'SERVER_NAME': 'localhost', # always mandatory 'SERVER_PORT': '80', # always mandatory 'SERVER_PROTOCOL': 'HTTP/1.0', # mandatory wsgi variables 'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.input': StringIO(''), 'wsgi.errors': errors, 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': False, } if path: (_, _, path_info, query, fragment) = urlsplit(str(path)) path_info = urllib.unquote(path_info) # urlsplit returns unicode so coerce it back to str path_info, query = str(path_info), str(query) basic_environ['PATH_INFO'] = path_info if query: basic_environ['QUERY_STRING'] = query for name, value in environ.items(): name = name.replace('__', '.') basic_environ[name] = value if ('SERVER_NAME' in basic_environ and 'HTTP_HOST' not in basic_environ): basic_environ['HTTP_HOST'] = basic_environ['SERVER_NAME'] istream = basic_environ['wsgi.input'] if isinstance(istream, str): basic_environ['wsgi.input'] = StringIO(istream) basic_environ['CONTENT_LENGTH'] = len(istream) data = {} output = [] headers_set = [] headers_sent = [] def start_response(status, headers, exc_info=None): if exc_info: try: if headers_sent: # Re-raise original exception only if headers sent raise exc_info[0], exc_info[1], exc_info[2] finally: # avoid dangling circular reference exc_info = None elif headers_set: # You cannot set the headers more than once, unless the # exc_info is provided. raise AssertionError("Headers already set and no exc_info!") headers_set.append(True) data['status'] = status data['headers'] = headers return output.append app_iter = application(basic_environ, start_response) try: try: for s in app_iter: if not isinstance(s, str): raise ValueError( "The app_iter response can only contain str (not " "unicode); got: %r" % s) headers_sent.append(True) if not headers_set: raise AssertionError("Content sent w/o headers!") output.append(s) except TypeError, e: # Typically "iteration over non-sequence", so we want # to give better debugging information... e.args = ((e.args[0] + ' iterable: %r' % app_iter),) + e.args[1:] raise finally: if hasattr(app_iter, 'close'): app_iter.close() return (data['status'], data['headers'], ''.join(output), errors.getvalue()) class ErrorRaiser(object): def flush(self): pass def write(self, value): if not value: return raise AssertionError( "No errors should be written (got: %r)" % value) def writelines(self, seq): raise AssertionError( "No errors should be written (got lines: %s)" % list(seq)) def getvalue(self): return '' def interactive(*args, **kw): """ Runs the application interatively, wrapping `raw_interactive` but returning the output in a formatted way. """ status, headers, content, errors = raw_interactive(*args, **kw) full = StringIO() if errors: full.write('Errors:\n') full.write(errors.strip()) full.write('\n----------end errors\n') full.write(status + '\n') for name, value in headers: full.write('%s: %s\n' % (name, value)) full.write('\n') full.write(content) return full.getvalue() interactive.proxy = 'raw_interactive' def dump_environ(environ, start_response): """ Application which simply dumps the current environment variables out as a plain text response. """ output = [] keys = environ.keys() keys.sort() for k in keys: v = str(environ[k]).replace("\n","\n ") output.append("%s: %s\n" % (k, v)) output.append("\n") content_length = environ.get("CONTENT_LENGTH", '') if content_length: output.append(environ['wsgi.input'].read(int(content_length))) output.append("\n") output = "".join(output) headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(output)))] start_response("200 OK", headers) return [output] def send_file(filename): warnings.warn( "wsgilib.send_file has been moved to paste.fileapp.FileApp", DeprecationWarning, 2) from paste import fileapp return fileapp.FileApp(filename) def capture_output(environ, start_response, application): """ Runs application with environ and start_response, and captures status, headers, and body. Sends status and header, but *not* body. Returns (status, headers, body). Typically this is used like: .. code-block:: python def dehtmlifying_middleware(application): def replacement_app(environ, start_response): status, headers, body = capture_output( environ, start_response, application) content_type = header_value(headers, 'content-type') if (not content_type or not content_type.startswith('text/html')): return [body] body = re.sub(r'<.*?>', '', body) return [body] return replacement_app """ warnings.warn( 'wsgilib.capture_output has been deprecated in favor ' 'of wsgilib.intercept_output', DeprecationWarning, 2) data = [] output = StringIO() def replacement_start_response(status, headers, exc_info=None): if data: data[:] = [] data.append(status) data.append(headers) start_response(status, headers, exc_info) return output.write app_iter = application(environ, replacement_start_response) try: for item in app_iter: output.write(item) finally: if hasattr(app_iter, 'close'): app_iter.close() if not data: data.append(None) if len(data) < 2: data.append(None) data.append(output.getvalue()) return data def intercept_output(environ, application, conditional=None, start_response=None): """ Runs application with environ and captures status, headers, and body. None are sent on; you must send them on yourself (unlike ``capture_output``) Typically this is used like: .. code-block:: python def dehtmlifying_middleware(application): def replacement_app(environ, start_response): status, headers, body = intercept_output( environ, application) start_response(status, headers) content_type = header_value(headers, 'content-type') if (not content_type or not content_type.startswith('text/html')): return [body] body = re.sub(r'<.*?>', '', body) return [body] return replacement_app A third optional argument ``conditional`` should be a function that takes ``conditional(status, headers)`` and returns False if the request should not be intercepted. In that case ``start_response`` will be called and ``(None, None, app_iter)`` will be returned. You must detect that in your code and return the app_iter, like: .. code-block:: python def dehtmlifying_middleware(application): def replacement_app(environ, start_response): status, headers, body = intercept_output( environ, application, lambda s, h: header_value(headers, 'content-type').startswith('text/html'), start_response) if status is None: return body start_response(status, headers) body = re.sub(r'<.*?>', '', body) return [body] return replacement_app """ if conditional is not None and start_response is None: raise TypeError( "If you provide conditional you must also provide " "start_response") data = [] output = StringIO() def replacement_start_response(status, headers, exc_info=None): if conditional is not None and not conditional(status, headers): data.append(None) return start_response(status, headers, exc_info) if data: data[:] = [] data.append(status) data.append(headers) return output.write app_iter = application(environ, replacement_start_response) if data[0] is None: return (None, None, app_iter) try: for item in app_iter: output.write(item) finally: if hasattr(app_iter, 'close'): app_iter.close() if not data: data.append(None) if len(data) < 2: data.append(None) data.append(output.getvalue()) return data ## Deprecation warning wrapper: class ResponseHeaderDict(HeaderDict): def __init__(self, *args, **kw): warnings.warn( "The class wsgilib.ResponseHeaderDict has been moved " "to paste.response.HeaderDict", DeprecationWarning, 2) HeaderDict.__init__(self, *args, **kw) def _warn_deprecated(new_func): new_name = new_func.func_name new_path = new_func.func_globals['__name__'] + '.' + new_name def replacement(*args, **kw): warnings.warn( "The function wsgilib.%s has been moved to %s" % (new_name, new_path), DeprecationWarning, 2) return new_func(*args, **kw) try: replacement.func_name = new_func.func_name except: pass return replacement # Put warnings wrapper in place for all public functions that # were imported from elsewhere: for _name in __all__: _func = globals()[_name] if (hasattr(_func, 'func_globals') and _func.func_globals['__name__'] != __name__): globals()[_name] = _warn_deprecated(_func) if __name__ == '__main__': import doctest doctest.testmod() Paste-1.7.5.1/paste/evalexception/0000775000175000017500000000000011363356617015342 5ustar ianbianbPaste-1.7.5.1/paste/evalexception/evalcontext.py0000664000175000017500000000411711354703364020246 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php from cStringIO import StringIO import traceback import threading import pdb import sys exec_lock = threading.Lock() class EvalContext(object): """ Class that represents a interactive interface. It has its own namespace. Use eval_context.exec_expr(expr) to run commands; the output of those commands is returned, as are print statements. This is essentially what doctest does, and is taken directly from doctest. """ def __init__(self, namespace, globs): self.namespace = namespace self.globs = globs def exec_expr(self, s): out = StringIO() exec_lock.acquire() save_stdout = sys.stdout try: debugger = _OutputRedirectingPdb(save_stdout) debugger.reset() pdb.set_trace = debugger.set_trace sys.stdout = out try: code = compile(s, '', "single", 0, 1) exec code in self.namespace, self.globs debugger.set_continue() except KeyboardInterrupt: raise except: traceback.print_exc(file=out) debugger.set_continue() finally: sys.stdout = save_stdout exec_lock.release() return out.getvalue() # From doctest class _OutputRedirectingPdb(pdb.Pdb): """ A specialized version of the python debugger that redirects stdout to a given stream when interacting with the user. Stdout is *not* redirected when traced code is executed. """ def __init__(self, out): self.__out = out pdb.Pdb.__init__(self) def trace_dispatch(self, *args): # Redirect stdout to the given stream. save_stdout = sys.stdout sys.stdout = self.__out # Call Pdb's trace dispatch method. try: return pdb.Pdb.trace_dispatch(self, *args) finally: sys.stdout = save_stdout Paste-1.7.5.1/paste/evalexception/middleware.py0000664000175000017500000005310711363356617020037 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Exception-catching middleware that allows interactive debugging. This middleware catches all unexpected exceptions. A normal traceback, like produced by ``paste.exceptions.errormiddleware.ErrorMiddleware`` is given, plus controls to see local variables and evaluate expressions in a local context. This can only be used in single-process environments, because subsequent requests must go back to the same process that the exception originally occurred in. Threaded or non-concurrent environments both work. This shouldn't be used in production in any way. That would just be silly. If calling from an XMLHttpRequest call, if the GET variable ``_`` is given then it will make the response more compact (and less Javascripty), since if you use innerHTML it'll kill your browser. You can look for the header X-Debug-URL in your 500 responses if you want to see the full debuggable traceback. Also, this URL is printed to ``wsgi.errors``, so you can open it up in another browser window. """ import sys import os import cgi import traceback from cStringIO import StringIO import pprint import itertools import time import re from paste.exceptions import errormiddleware, formatter, collector from paste import wsgilib from paste import urlparser from paste import httpexceptions from paste import registry from paste import request from paste import response import evalcontext limit = 200 def html_quote(v): """ Escape HTML characters, plus translate None to '' """ if v is None: return '' return cgi.escape(str(v), 1) def preserve_whitespace(v, quote=True): """ Quote a value for HTML, preserving whitespace (translating newlines to ``
`` and multiple spaces to use `` ``). If ``quote`` is true, then the value will be HTML quoted first. """ if quote: v = html_quote(v) v = v.replace('\n', '
\n') v = re.sub(r'()( +)', _repl_nbsp, v) v = re.sub(r'(\n)( +)', _repl_nbsp, v) v = re.sub(r'^()( +)', _repl_nbsp, v) return '%s' % v def _repl_nbsp(match): if len(match.group(2)) == 1: return ' ' return match.group(1) + ' ' * (len(match.group(2))-1) + ' ' def simplecatcher(application): """ A simple middleware that catches errors and turns them into simple tracebacks. """ def simplecatcher_app(environ, start_response): try: return application(environ, start_response) except: out = StringIO() traceback.print_exc(file=out) start_response('500 Server Error', [('content-type', 'text/html')], sys.exc_info()) res = out.getvalue() return ['

Error

%s
' % html_quote(res)] return simplecatcher_app def wsgiapp(): """ Turns a function or method into a WSGI application. """ def decorator(func): def wsgiapp_wrapper(*args): # we get 3 args when this is a method, two when it is # a function :( if len(args) == 3: environ = args[1] start_response = args[2] args = [args[0]] else: environ, start_response = args args = [] def application(environ, start_response): form = wsgilib.parse_formvars(environ, include_get_vars=True) headers = response.HeaderDict( {'content-type': 'text/html', 'status': '200 OK'}) form['environ'] = environ form['headers'] = headers res = func(*args, **form.mixed()) status = headers.pop('status') start_response(status, headers.headeritems()) return [res] app = httpexceptions.make_middleware(application) app = simplecatcher(app) return app(environ, start_response) wsgiapp_wrapper.exposed = True return wsgiapp_wrapper return decorator def get_debug_info(func): """ A decorator (meant to be used under ``wsgiapp()``) that resolves the ``debugcount`` variable to a ``DebugInfo`` object (or gives an error if it can't be found). """ def debug_info_replacement(self, **form): try: if 'debugcount' not in form: raise ValueError('You must provide a debugcount parameter') debugcount = form.pop('debugcount') try: debugcount = int(debugcount) except ValueError: raise ValueError('Bad value for debugcount') if debugcount not in self.debug_infos: raise ValueError( 'Debug %s no longer found (maybe it has expired?)' % debugcount) debug_info = self.debug_infos[debugcount] return func(self, debug_info=debug_info, **form) except ValueError, e: form['headers']['status'] = '500 Server Error' return 'There was an error: %s' % html_quote(e) return debug_info_replacement debug_counter = itertools.count(int(time.time())) def get_debug_count(environ): """ Return the unique debug count for the current request """ if 'paste.evalexception.debug_count' in environ: return environ['paste.evalexception.debug_count'] else: environ['paste.evalexception.debug_count'] = next = debug_counter.next() return next class EvalException(object): def __init__(self, application, global_conf=None, xmlhttp_key=None): self.application = application self.debug_infos = {} if xmlhttp_key is None: if global_conf is None: xmlhttp_key = '_' else: xmlhttp_key = global_conf.get('xmlhttp_key', '_') self.xmlhttp_key = xmlhttp_key def __call__(self, environ, start_response): assert not environ['wsgi.multiprocess'], ( "The EvalException middleware is not usable in a " "multi-process environment") environ['paste.evalexception'] = self if environ.get('PATH_INFO', '').startswith('/_debug/'): return self.debug(environ, start_response) else: return self.respond(environ, start_response) def debug(self, environ, start_response): assert request.path_info_pop(environ) == '_debug' next_part = request.path_info_pop(environ) method = getattr(self, next_part, None) if not method: exc = httpexceptions.HTTPNotFound( '%r not found when parsing %r' % (next_part, wsgilib.construct_url(environ))) return exc.wsgi_application(environ, start_response) if not getattr(method, 'exposed', False): exc = httpexceptions.HTTPForbidden( '%r not allowed' % next_part) return exc.wsgi_application(environ, start_response) return method(environ, start_response) def media(self, environ, start_response): """ Static path where images and other files live """ app = urlparser.StaticURLParser( os.path.join(os.path.dirname(__file__), 'media')) return app(environ, start_response) media.exposed = True def mochikit(self, environ, start_response): """ Static path where MochiKit lives """ app = urlparser.StaticURLParser( os.path.join(os.path.dirname(__file__), 'mochikit')) return app(environ, start_response) mochikit.exposed = True def summary(self, environ, start_response): """ Returns a JSON-format summary of all the cached exception reports """ start_response('200 OK', [('Content-type', 'text/x-json')]) data = []; items = self.debug_infos.values() items.sort(lambda a, b: cmp(a.created, b.created)) data = [item.json() for item in items] return [repr(data)] summary.exposed = True def view(self, environ, start_response): """ View old exception reports """ id = int(request.path_info_pop(environ)) if id not in self.debug_infos: start_response( '500 Server Error', [('Content-type', 'text/html')]) return [ "Traceback by id %s does not exist (maybe " "the server has been restarted?)" % id] debug_info = self.debug_infos[id] return debug_info.wsgi_application(environ, start_response) view.exposed = True def make_view_url(self, environ, base_path, count): return base_path + '/_debug/view/%s' % count #@wsgiapp() #@get_debug_info def show_frame(self, tbid, debug_info, **kw): frame = debug_info.frame(int(tbid)) vars = frame.tb_frame.f_locals if vars: registry.restorer.restoration_begin(debug_info.counter) local_vars = make_table(vars) registry.restorer.restoration_end() else: local_vars = 'No local vars' return input_form(tbid, debug_info) + local_vars show_frame = wsgiapp()(get_debug_info(show_frame)) #@wsgiapp() #@get_debug_info def exec_input(self, tbid, debug_info, input, **kw): if not input.strip(): return '' input = input.rstrip() + '\n' frame = debug_info.frame(int(tbid)) vars = frame.tb_frame.f_locals glob_vars = frame.tb_frame.f_globals context = evalcontext.EvalContext(vars, glob_vars) registry.restorer.restoration_begin(debug_info.counter) output = context.exec_expr(input) registry.restorer.restoration_end() input_html = formatter.str2html(input) return ('>>> ' '%s
\n%s' % (preserve_whitespace(input_html, quote=False), preserve_whitespace(output))) exec_input = wsgiapp()(get_debug_info(exec_input)) def respond(self, environ, start_response): if environ.get('paste.throw_errors'): return self.application(environ, start_response) base_path = request.construct_url(environ, with_path_info=False, with_query_string=False) environ['paste.throw_errors'] = True started = [] def detect_start_response(status, headers, exc_info=None): try: return start_response(status, headers, exc_info) except: raise else: started.append(True) try: __traceback_supplement__ = errormiddleware.Supplement, self, environ app_iter = self.application(environ, detect_start_response) try: return_iter = list(app_iter) return return_iter finally: if hasattr(app_iter, 'close'): app_iter.close() except: exc_info = sys.exc_info() for expected in environ.get('paste.expected_exceptions', []): if isinstance(exc_info[1], expected): raise # Tell the Registry to save its StackedObjectProxies current state # for later restoration registry.restorer.save_registry_state(environ) count = get_debug_count(environ) view_uri = self.make_view_url(environ, base_path, count) if not started: headers = [('content-type', 'text/html')] headers.append(('X-Debug-URL', view_uri)) start_response('500 Internal Server Error', headers, exc_info) environ['wsgi.errors'].write('Debug at: %s\n' % view_uri) exc_data = collector.collect_exception(*exc_info) debug_info = DebugInfo(count, exc_info, exc_data, base_path, environ, view_uri) assert count not in self.debug_infos self.debug_infos[count] = debug_info if self.xmlhttp_key: get_vars = wsgilib.parse_querystring(environ) if dict(get_vars).get(self.xmlhttp_key): exc_data = collector.collect_exception(*exc_info) html = formatter.format_html( exc_data, include_hidden_frames=False, include_reusable=False, show_extra_data=False) return [html] # @@: it would be nice to deal with bad content types here return debug_info.content() def exception_handler(self, exc_info, environ): simple_html_error = False if self.xmlhttp_key: get_vars = wsgilib.parse_querystring(environ) if dict(get_vars).get(self.xmlhttp_key): simple_html_error = True return errormiddleware.handle_exception( exc_info, environ['wsgi.errors'], html=True, debug_mode=True, simple_html_error=simple_html_error) class DebugInfo(object): def __init__(self, counter, exc_info, exc_data, base_path, environ, view_uri): self.counter = counter self.exc_data = exc_data self.base_path = base_path self.environ = environ self.view_uri = view_uri self.created = time.time() self.exc_type, self.exc_value, self.tb = exc_info __exception_formatter__ = 1 self.frames = [] n = 0 tb = self.tb while tb is not None and (limit is None or n < limit): if tb.tb_frame.f_locals.get('__exception_formatter__'): # Stop recursion. @@: should make a fake ExceptionFrame break self.frames.append(tb) tb = tb.tb_next n += 1 def json(self): """Return the JSON-able representation of this object""" return { 'uri': self.view_uri, 'created': time.strftime('%c', time.gmtime(self.created)), 'created_timestamp': self.created, 'exception_type': str(self.exc_type), 'exception': str(self.exc_value), } def frame(self, tbid): for frame in self.frames: if id(frame) == tbid: return frame else: raise ValueError, ( "No frame by id %s found from %r" % (tbid, self.frames)) def wsgi_application(self, environ, start_response): start_response('200 OK', [('content-type', 'text/html')]) return self.content() def content(self): html = format_eval_html(self.exc_data, self.base_path, self.counter) head_html = (formatter.error_css + formatter.hide_display_js) head_html += self.eval_javascript() repost_button = make_repost_button(self.environ) page = error_template % { 'repost_button': repost_button or '', 'head_html': head_html, 'body': html} return [page] def eval_javascript(self): base_path = self.base_path + '/_debug' return ( '\n' '\n' '\n' % (base_path, base_path, base_path, self.counter)) class EvalHTMLFormatter(formatter.HTMLFormatter): def __init__(self, base_path, counter, **kw): super(EvalHTMLFormatter, self).__init__(**kw) self.base_path = base_path self.counter = counter def format_source_line(self, filename, frame): line = formatter.HTMLFormatter.format_source_line( self, filename, frame) return (line + '     ' '    ' % (frame.tbid, self.base_path)) def make_table(items): if isinstance(items, dict): items = items.items() items.sort() rows = [] i = 0 for name, value in items: i += 1 out = StringIO() try: pprint.pprint(value, out) except Exception, e: print >> out, 'Error: %s' % e value = html_quote(out.getvalue()) if len(value) > 100: # @@: This can actually break the HTML :( # should I truncate before quoting? orig_value = value value = value[:100] value += '...' value += '%s' % orig_value[100:] value = formatter.make_wrappable(value) if i % 2: attr = ' class="even"' else: attr = ' class="odd"' rows.append('' '%s%s' % (attr, html_quote(name), preserve_whitespace(value, quote=False))) return '%s
' % ( '\n'.join(rows)) def format_eval_html(exc_data, base_path, counter): short_formatter = EvalHTMLFormatter( base_path=base_path, counter=counter, include_reusable=False) short_er = short_formatter.format_collected_data(exc_data) long_formatter = EvalHTMLFormatter( base_path=base_path, counter=counter, show_hidden_frames=True, show_extra_data=False, include_reusable=False) long_er = long_formatter.format_collected_data(exc_data) text_er = formatter.format_text(exc_data, show_hidden_frames=True) if short_formatter.filter_frames(exc_data.frames) != \ long_formatter.filter_frames(exc_data.frames): # Only display the full traceback when it differs from the # short version full_traceback_html = """
%s
""" % long_er else: full_traceback_html = '' return """ %s %s
""" % (short_er, full_traceback_html, cgi.escape(text_er)) def make_repost_button(environ): url = request.construct_url(environ) if environ['REQUEST_METHOD'] == 'GET': return ('
' % url) else: # @@: I'd like to reconstruct this, but I can't because # the POST body is probably lost at this point, and # I can't get it back :( return None # @@: Use or lose the following code block """ fields = [] for name, value in wsgilib.parse_formvars( environ, include_get_vars=False).items(): if hasattr(value, 'filename'): # @@: Arg, we'll just submit the body, and leave out # the filename :( value = value.value fields.append( '' % (html_quote(name), html_quote(value))) return '''
%s
''' % (url, '\n'.join(fields)) """ def input_form(tbid, debug_info): return '''

''' % {'tbid': tbid} error_template = ''' Server Error %(head_html)s %(repost_button)s %(body)s ''' def make_eval_exception(app, global_conf, xmlhttp_key=None): """ Wraps the application in an interactive debugger. This debugger is a major security hole, and should only be used during development. xmlhttp_key is a string that, if present in QUERY_STRING, indicates that the request is an XMLHttp request, and the Javascript/interactive debugger should not be returned. (If you try to put the debugger somewhere with innerHTML, you will often crash the browser) """ if xmlhttp_key is None: xmlhttp_key = global_conf.get('xmlhttp_key', '_') return EvalException(app, xmlhttp_key=xmlhttp_key) Paste-1.7.5.1/paste/evalexception/__init__.py0000664000175000017500000000043211354703364017445 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ An exception handler for interactive debugging """ from paste.evalexception.middleware import EvalException Paste-1.7.5.1/paste/evalexception/media/0000775000175000017500000000000011354703364016414 5ustar ianbianbPaste-1.7.5.1/paste/evalexception/media/MochiKit.packed.js0000660000175000017500000061302611113656660021712 0ustar ianbianb/*** MochiKit.MochiKit 1.4.2 : PACKED VERSION THIS FILE IS AUTOMATICALLY GENERATED. If creating patches, please diff against the source tree, not this file. See for documentation, downloads, license, etc. (c) 2005 Bob Ippolito. All rights Reserved. ***/ if(typeof (dojo)!="undefined"){ dojo.provide("MochiKit.Base"); } if(typeof (MochiKit)=="undefined"){ MochiKit={}; } if(typeof (MochiKit.Base)=="undefined"){ MochiKit.Base={}; } if(typeof (MochiKit.__export__)=="undefined"){ MochiKit.__export__=(MochiKit.__compat__||(typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")); } MochiKit.Base.VERSION="1.4.2"; MochiKit.Base.NAME="MochiKit.Base"; MochiKit.Base.update=function(_1,_2){ if(_1===null||_1===undefined){ _1={}; } for(var i=1;i=0;i--){ _18.unshift(o[i]); } }else{ res.push(o); } } return res; },extend:function(_1b,obj,_1d){ if(!_1d){ _1d=0; } if(obj){ var l=obj.length; if(typeof (l)!="number"){ if(typeof (MochiKit.Iter)!="undefined"){ obj=MochiKit.Iter.list(obj); l=obj.length; }else{ throw new TypeError("Argument not an array-like and MochiKit.Iter not present"); } } if(!_1b){ _1b=[]; } for(var i=_1d;i>b; },zrshift:function(a,b){ return a>>>b; },eq:function(a,b){ return a==b; },ne:function(a,b){ return a!=b; },gt:function(a,b){ return a>b; },ge:function(a,b){ return a>=b; },lt:function(a,b){ return al){ _93=l; } } _91=[]; for(i=0;i<_93;i++){ var _95=[]; for(var j=1;j=0;i--){ _b2=[_ae[i].apply(this,_b2)]; } return _b2[0]; }; },bind:function(_b4,_b5){ if(typeof (_b4)=="string"){ _b4=_b5[_b4]; } var _b6=_b4.im_func; var _b7=_b4.im_preargs; var _b8=_b4.im_self; var m=MochiKit.Base; if(typeof (_b4)=="function"&&typeof (_b4.apply)=="undefined"){ _b4=m._wrapDumbFunction(_b4); } if(typeof (_b6)!="function"){ _b6=_b4; } if(typeof (_b5)!="undefined"){ _b8=_b5; } if(typeof (_b7)=="undefined"){ _b7=[]; }else{ _b7=_b7.slice(); } m.extend(_b7,arguments,2); var _ba=function(){ var _bb=arguments; var me=arguments.callee; if(me.im_preargs.length>0){ _bb=m.concat(me.im_preargs,_bb); } var _bd=me.im_self; if(!_bd){ _bd=this; } return me.im_func.apply(_bd,_bb); }; _ba.im_self=_b8; _ba.im_func=_b6; _ba.im_preargs=_b7; return _ba; },bindLate:function(_be,_bf){ var m=MochiKit.Base; if(typeof (_be)!="string"){ return m.bind.apply(this,arguments); } var _c1=m.extend([],arguments,2); var _c2=function(){ var _c3=arguments; var me=arguments.callee; if(me.im_preargs.length>0){ _c3=m.concat(me.im_preargs,_c3); } var _c5=me.im_self; if(!_c5){ _c5=this; } return _c5[me.im_func].apply(_c5,_c3); }; _c2.im_self=_bf; _c2.im_func=_be; _c2.im_preargs=_c1; return _c2; },bindMethods:function(_c6){ var _c7=MochiKit.Base.bind; for(var k in _c6){ var _c9=_c6[k]; if(typeof (_c9)=="function"){ _c6[k]=_c7(_c9,_c6); } } },registerComparator:function(_ca,_cb,_cc,_cd){ MochiKit.Base.comparatorRegistry.register(_ca,_cb,_cc,_cd); },_primitives:{"boolean":true,"string":true,"number":true},compare:function(a,b){ if(a==b){ return 0; } var _d0=(typeof (a)=="undefined"||a===null); var _d1=(typeof (b)=="undefined"||b===null); if(_d0&&_d1){ return 0; }else{ if(_d0){ return -1; }else{ if(_d1){ return 1; } } } var m=MochiKit.Base; var _d3=m._primitives; if(!(typeof (a) in _d3&&typeof (b) in _d3)){ try{ return m.comparatorRegistry.match(a,b); } catch(e){ if(e!=m.NotFound){ throw e; } } } if(ab){ return 1; } } var _d4=m.repr; throw new TypeError(_d4(a)+" and "+_d4(b)+" can not be compared"); },compareDateLike:function(a,b){ return MochiKit.Base.compare(a.getTime(),b.getTime()); },compareArrayLike:function(a,b){ var _d9=MochiKit.Base.compare; var _da=a.length; var _db=0; if(_da>b.length){ _db=1; _da=b.length; }else{ if(_da=0;i--){ sum+=o[i]; } }else{ sum+=o; } } if(_121<=0){ throw new TypeError("mean() requires at least one argument"); } return sum/_121; },median:function(){ var data=MochiKit.Base.flattenArguments(arguments); if(data.length===0){ throw new TypeError("median() requires at least one argument"); } data.sort(compare); if(data.length%2==0){ var _125=data.length/2; return (data[_125]+data[_125-1])/2; }else{ return data[(data.length-1)/2]; } },findValue:function(lst,_127,_128,end){ if(typeof (end)=="undefined"||end===null){ end=lst.length; } if(typeof (_128)=="undefined"||_128===null){ _128=0; } var cmp=MochiKit.Base.compare; for(var i=_128;i0))){ var kv=MochiKit.DOM.formContents(_135); _135=kv[0]; _136=kv[1]; }else{ if(arguments.length==1){ if(typeof (_135.length)=="number"&&_135.length==2){ return arguments.callee(_135[0],_135[1]); } var o=_135; _135=[]; _136=[]; for(var k in o){ var v=o[k]; if(typeof (v)=="function"){ continue; }else{ if(MochiKit.Base.isArrayLike(v)){ for(var i=0;i=stop){ throw self.StopIteration; } _183+=step; return rval; }}; },imap:function(fun,p,q){ var m=MochiKit.Base; var self=MochiKit.Iter; var _18d=m.map(self.iter,m.extend(null,arguments,1)); var map=m.map; var next=self.next; return {repr:function(){ return "imap(...)"; },toString:m.forwardCall("repr"),next:function(){ return fun.apply(this,map(next,_18d)); }}; },applymap:function(fun,seq,self){ seq=MochiKit.Iter.iter(seq); var m=MochiKit.Base; return {repr:function(){ return "applymap(...)"; },toString:m.forwardCall("repr"),next:function(){ return fun.apply(self,seq.next()); }}; },chain:function(p,q){ var self=MochiKit.Iter; var m=MochiKit.Base; if(arguments.length==1){ return self.iter(arguments[0]); } var _198=m.map(self.iter,arguments); return {repr:function(){ return "chain(...)"; },toString:m.forwardCall("repr"),next:function(){ while(_198.length>1){ try{ var _199=_198[0].next(); return _199; } catch(e){ if(e!=self.StopIteration){ throw e; } _198.shift(); var _199=_198[0].next(); return _199; } } if(_198.length==1){ var arg=_198.shift(); this.next=m.bind("next",arg); return this.next(); } throw self.StopIteration; }}; },takewhile:function(pred,seq){ var self=MochiKit.Iter; seq=self.iter(seq); return {repr:function(){ return "takewhile(...)"; },toString:MochiKit.Base.forwardCall("repr"),next:function(){ var rval=seq.next(); if(!pred(rval)){ this.next=function(){ throw self.StopIteration; }; this.next(); } return rval; }}; },dropwhile:function(pred,seq){ seq=MochiKit.Iter.iter(seq); var m=MochiKit.Base; var bind=m.bind; return {"repr":function(){ return "dropwhile(...)"; },"toString":m.forwardCall("repr"),"next":function(){ while(true){ var rval=seq.next(); if(!pred(rval)){ break; } } this.next=bind("next",seq); return rval; }}; },_tee:function(_1a4,sync,_1a6){ sync.pos[_1a4]=-1; var m=MochiKit.Base; var _1a8=m.listMin; return {repr:function(){ return "tee("+_1a4+", ...)"; },toString:m.forwardCall("repr"),next:function(){ var rval; var i=sync.pos[_1a4]; if(i==sync.max){ rval=_1a6.next(); sync.deque.push(rval); sync.max+=1; sync.pos[_1a4]+=1; }else{ rval=sync.deque[i-sync.min]; sync.pos[_1a4]+=1; if(i==sync.min&&_1a8(sync.pos)!=sync.min){ sync.min+=1; sync.deque.shift(); } } return rval; }}; },tee:function(_1ab,n){ var rval=[]; var sync={"pos":[],"deque":[],"max":-1,"min":-1}; if(arguments.length==1||typeof (n)=="undefined"||n===null){ n=2; } var self=MochiKit.Iter; _1ab=self.iter(_1ab); var _tee=self._tee; for(var i=0;i0&&_1bd>=stop)||(step<0&&_1bd<=stop)){ throw MochiKit.Iter.StopIteration; } var rval=_1bd; _1bd+=step; return rval; },repr:function(){ return "range("+[_1bd,stop,step].join(", ")+")"; },toString:MochiKit.Base.forwardCall("repr")}; },sum:function(_1c1,_1c2){ if(typeof (_1c2)=="undefined"||_1c2===null){ _1c2=0; } var x=_1c2; var self=MochiKit.Iter; _1c1=self.iter(_1c1); try{ while(true){ x+=_1c1.next(); } } catch(e){ if(e!=self.StopIteration){ throw e; } } return x; },exhaust:function(_1c5){ var self=MochiKit.Iter; _1c5=self.iter(_1c5); try{ while(true){ _1c5.next(); } } catch(e){ if(e!=self.StopIteration){ throw e; } } },forEach:function(_1c7,func,obj){ var m=MochiKit.Base; var self=MochiKit.Iter; if(arguments.length>2){ func=m.bind(func,obj); } if(m.isArrayLike(_1c7)&&!self.isIterable(_1c7)){ try{ for(var i=0;i<_1c7.length;i++){ func(_1c7[i]); } } catch(e){ if(e!=self.StopIteration){ throw e; } } }else{ self.exhaust(self.imap(func,_1c7)); } },every:function(_1cd,func){ var self=MochiKit.Iter; try{ self.ifilterfalse(func,_1cd).next(); return false; } catch(e){ if(e!=self.StopIteration){ throw e; } return true; } },sorted:function(_1d0,cmp){ var rval=MochiKit.Iter.list(_1d0); if(arguments.length==1){ cmp=MochiKit.Base.compare; } rval.sort(cmp); return rval; },reversed:function(_1d3){ var rval=MochiKit.Iter.list(_1d3); rval.reverse(); return rval; },some:function(_1d5,func){ var self=MochiKit.Iter; try{ self.ifilter(func,_1d5).next(); return true; } catch(e){ if(e!=self.StopIteration){ throw e; } return false; } },iextend:function(lst,_1d9){ var m=MochiKit.Base; var self=MochiKit.Iter; if(m.isArrayLike(_1d9)&&!self.isIterable(_1d9)){ for(var i=0;i<_1d9.length;i++){ lst.push(_1d9[i]); } }else{ _1d9=self.iter(_1d9); try{ while(true){ lst.push(_1d9.next()); } } catch(e){ if(e!=self.StopIteration){ throw e; } } } return lst; },groupby:function(_1dd,_1de){ var m=MochiKit.Base; var self=MochiKit.Iter; if(arguments.length<2){ _1de=m.operator.identity; } _1dd=self.iter(_1dd); var pk=undefined; var k=undefined; var v; function fetch(){ v=_1dd.next(); k=_1de(v); } function eat(){ var ret=v; v=undefined; return ret; } var _1e5=true; var _1e6=m.compare; return {repr:function(){ return "groupby(...)"; },next:function(){ while(_1e6(k,pk)===0){ fetch(); if(_1e5){ _1e5=false; break; } } pk=k; return [k,{next:function(){ if(v==undefined){ fetch(); } if(_1e6(k,pk)!==0){ throw self.StopIteration; } return eat(); }}]; }}; },groupby_as_array:function(_1e7,_1e8){ var m=MochiKit.Base; var self=MochiKit.Iter; if(arguments.length<2){ _1e8=m.operator.identity; } _1e7=self.iter(_1e7); var _1eb=[]; var _1ec=true; var _1ed; var _1ee=m.compare; while(true){ try{ var _1ef=_1e7.next(); var key=_1e8(_1ef); } catch(e){ if(e==self.StopIteration){ break; } throw e; } if(_1ec||_1ee(key,_1ed)!==0){ var _1f1=[]; _1eb.push([key,_1f1]); } _1f1.push(_1ef); _1ec=false; _1ed=key; } return _1eb; },arrayLikeIter:function(_1f2){ var i=0; return {repr:function(){ return "arrayLikeIter(...)"; },toString:MochiKit.Base.forwardCall("repr"),next:function(){ if(i>=_1f2.length){ throw MochiKit.Iter.StopIteration; } return _1f2[i++]; }}; },hasIterateNext:function(_1f4){ return (_1f4&&typeof (_1f4.iterateNext)=="function"); },iterateNextIter:function(_1f5){ return {repr:function(){ return "iterateNextIter(...)"; },toString:MochiKit.Base.forwardCall("repr"),next:function(){ var rval=_1f5.iterateNext(); if(rval===null||rval===undefined){ throw MochiKit.Iter.StopIteration; } return rval; }}; }}); MochiKit.Iter.EXPORT_OK=["iteratorRegistry","arrayLikeIter","hasIterateNext","iterateNextIter"]; MochiKit.Iter.EXPORT=["StopIteration","registerIteratorFactory","iter","count","cycle","repeat","next","izip","ifilter","ifilterfalse","islice","imap","applymap","chain","takewhile","dropwhile","tee","list","reduce","range","sum","exhaust","forEach","every","sorted","reversed","some","iextend","groupby","groupby_as_array"]; MochiKit.Iter.__new__=function(){ var m=MochiKit.Base; if(typeof (StopIteration)!="undefined"){ this.StopIteration=StopIteration; }else{ this.StopIteration=new m.NamedError("StopIteration"); } this.iteratorRegistry=new m.AdapterRegistry(); this.registerIteratorFactory("arrayLike",m.isArrayLike,this.arrayLikeIter); this.registerIteratorFactory("iterateNext",this.hasIterateNext,this.iterateNextIter); this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; m.nameFunctions(this); }; MochiKit.Iter.__new__(); if(MochiKit.__export__){ reduce=MochiKit.Iter.reduce; } MochiKit.Base._exportSymbols(this,MochiKit.Iter); MochiKit.Base._deps("Logging",["Base"]); MochiKit.Logging.NAME="MochiKit.Logging"; MochiKit.Logging.VERSION="1.4.2"; MochiKit.Logging.__repr__=function(){ return "["+this.NAME+" "+this.VERSION+"]"; }; MochiKit.Logging.toString=function(){ return this.__repr__(); }; MochiKit.Logging.EXPORT=["LogLevel","LogMessage","Logger","alertListener","logger","log","logError","logDebug","logFatal","logWarning"]; MochiKit.Logging.EXPORT_OK=["logLevelAtLeast","isLogMessage","compareLogMessage"]; MochiKit.Logging.LogMessage=function(num,_1f9,info){ this.num=num; this.level=_1f9; this.info=info; this.timestamp=new Date(); }; MochiKit.Logging.LogMessage.prototype={repr:function(){ var m=MochiKit.Base; return "LogMessage("+m.map(m.repr,[this.num,this.level,this.info]).join(", ")+")"; },toString:MochiKit.Base.forwardCall("repr")}; MochiKit.Base.update(MochiKit.Logging,{logLevelAtLeast:function(_1fc){ var self=MochiKit.Logging; if(typeof (_1fc)=="string"){ _1fc=self.LogLevel[_1fc]; } return function(msg){ var _1ff=msg.level; if(typeof (_1ff)=="string"){ _1ff=self.LogLevel[_1ff]; } return _1ff>=_1fc; }; },isLogMessage:function(){ var _200=MochiKit.Logging.LogMessage; for(var i=0;i=MochiKit.Logging.LogLevel.FATAL){ _20f="FATAL"; }else{ if(_20f>=MochiKit.Logging.LogLevel.ERROR){ _20f="ERROR"; }else{ if(_20f>=MochiKit.Logging.LogLevel.WARNING){ _20f="WARNING"; }else{ if(_20f>=MochiKit.Logging.LogLevel.INFO){ _20f="INFO"; }else{ _20f="DEBUG"; } } } } } var msg=new MochiKit.Logging.LogMessage(this.counter,_20f,MochiKit.Base.extend(null,arguments,1)); this._messages.push(msg); this.dispatchListeners(msg); if(this.useNativeConsole){ this.logToConsole(msg.level+": "+msg.info.join(" ")); } this.counter+=1; while(this.maxSize>=0&&this._messages.length>this.maxSize){ this._messages.shift(); } },getMessages:function(_212){ var _213=0; if(!(typeof (_212)=="undefined"||_212===null)){ _213=Math.max(0,this._messages.length-_212); } return this._messages.slice(_213); },getMessageText:function(_214){ if(typeof (_214)=="undefined"||_214===null){ _214=30; } var _215=this.getMessages(_214); if(_215.length){ var lst=map(function(m){ return "\n ["+m.num+"] "+m.level+": "+m.info.join(" "); },_215); lst.unshift("LAST "+_215.length+" MESSAGES:"); return lst.join(""); } return ""; },debuggingBookmarklet:function(_218){ if(typeof (MochiKit.LoggingPane)=="undefined"){ alert(this.getMessageText()); }else{ MochiKit.LoggingPane.createLoggingPane(_218||false); } }}; MochiKit.Logging.__new__=function(){ this.LogLevel={ERROR:40,FATAL:50,WARNING:30,INFO:20,DEBUG:10}; var m=MochiKit.Base; m.registerComparator("LogMessage",this.isLogMessage,this.compareLogMessage); var _21a=m.partial; var _21b=this.Logger; var _21c=_21b.prototype.baseLog; m.update(this.Logger.prototype,{debug:_21a(_21c,"DEBUG"),log:_21a(_21c,"INFO"),error:_21a(_21c,"ERROR"),fatal:_21a(_21c,"FATAL"),warning:_21a(_21c,"WARNING")}); var self=this; var _21e=function(name){ return function(){ self.logger[name].apply(self.logger,arguments); }; }; this.log=_21e("log"); this.logError=_21e("error"); this.logDebug=_21e("debug"); this.logFatal=_21e("fatal"); this.logWarning=_21e("warning"); this.logger=new _21b(); this.logger.useNativeConsole=true; this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; m.nameFunctions(this); }; if(typeof (printfire)=="undefined"&&typeof (document)!="undefined"&&document.createEvent&&typeof (dispatchEvent)!="undefined"){ printfire=function(){ printfire.args=arguments; var ev=document.createEvent("Events"); ev.initEvent("printfire",false,true); dispatchEvent(ev); }; } MochiKit.Logging.__new__(); MochiKit.Base._exportSymbols(this,MochiKit.Logging); MochiKit.Base._deps("DateTime",["Base"]); MochiKit.DateTime.NAME="MochiKit.DateTime"; MochiKit.DateTime.VERSION="1.4.2"; MochiKit.DateTime.__repr__=function(){ return "["+this.NAME+" "+this.VERSION+"]"; }; MochiKit.DateTime.toString=function(){ return this.__repr__(); }; MochiKit.DateTime.isoDate=function(str){ str=str+""; if(typeof (str)!="string"||str.length===0){ return null; } var iso=str.split("-"); if(iso.length===0){ return null; } var date=new Date(iso[0],iso[1]-1,iso[2]); date.setFullYear(iso[0]); date.setMonth(iso[1]-1); date.setDate(iso[2]); return date; }; MochiKit.DateTime._isoRegexp=/(\d{4,})(?:-(\d{1,2})(?:-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d+))?)?(?:(Z)|([+-])(\d{1,2})(?::(\d{1,2}))?)?)?)?)?/; MochiKit.DateTime.isoTimestamp=function(str){ str=str+""; if(typeof (str)!="string"||str.length===0){ return null; } var res=str.match(MochiKit.DateTime._isoRegexp); if(typeof (res)=="undefined"||res===null){ return null; } var year,_227,day,hour,min,sec,msec; year=parseInt(res[1],10); if(typeof (res[2])=="undefined"||res[2]===""){ return new Date(year); } _227=parseInt(res[2],10)-1; day=parseInt(res[3],10); if(typeof (res[4])=="undefined"||res[4]===""){ return new Date(year,_227,day); } hour=parseInt(res[4],10); min=parseInt(res[5],10); sec=(typeof (res[6])!="undefined"&&res[6]!=="")?parseInt(res[6],10):0; if(typeof (res[7])!="undefined"&&res[7]!==""){ msec=Math.round(1000*parseFloat("0."+res[7])); }else{ msec=0; } if((typeof (res[8])=="undefined"||res[8]==="")&&(typeof (res[9])=="undefined"||res[9]==="")){ return new Date(year,_227,day,hour,min,sec,msec); } var ofs; if(typeof (res[9])!="undefined"&&res[9]!==""){ ofs=parseInt(res[10],10)*3600000; if(typeof (res[11])!="undefined"&&res[11]!==""){ ofs+=parseInt(res[11],10)*60000; } if(res[9]=="-"){ ofs=-ofs; } }else{ ofs=0; } return new Date(Date.UTC(year,_227,day,hour,min,sec,msec)-ofs); }; MochiKit.DateTime.toISOTime=function(date,_22f){ if(typeof (date)=="undefined"||date===null){ return null; } var hh=date.getHours(); var mm=date.getMinutes(); var ss=date.getSeconds(); var lst=[((_22f&&(hh<10))?"0"+hh:hh),((mm<10)?"0"+mm:mm),((ss<10)?"0"+ss:ss)]; return lst.join(":"); }; MochiKit.DateTime.toISOTimestamp=function(date,_235){ if(typeof (date)=="undefined"||date===null){ return null; } var sep=_235?"T":" "; var foot=_235?"Z":""; if(_235){ date=new Date(date.getTime()+(date.getTimezoneOffset()*60000)); } return MochiKit.DateTime.toISODate(date)+sep+MochiKit.DateTime.toISOTime(date,_235)+foot; }; MochiKit.DateTime.toISODate=function(date){ if(typeof (date)=="undefined"||date===null){ return null; } var _239=MochiKit.DateTime._padTwo; var _23a=MochiKit.DateTime._padFour; return [_23a(date.getFullYear()),_239(date.getMonth()+1),_239(date.getDate())].join("-"); }; MochiKit.DateTime.americanDate=function(d){ d=d+""; if(typeof (d)!="string"||d.length===0){ return null; } var a=d.split("/"); return new Date(a[2],a[0]-1,a[1]); }; MochiKit.DateTime._padTwo=function(n){ return (n>9)?n:"0"+n; }; MochiKit.DateTime._padFour=function(n){ switch(n.toString().length){ case 1: return "000"+n; break; case 2: return "00"+n; break; case 3: return "0"+n; break; case 4: default: return n; } }; MochiKit.DateTime.toPaddedAmericanDate=function(d){ if(typeof (d)=="undefined"||d===null){ return null; } var _240=MochiKit.DateTime._padTwo; return [_240(d.getMonth()+1),_240(d.getDate()),d.getFullYear()].join("/"); }; MochiKit.DateTime.toAmericanDate=function(d){ if(typeof (d)=="undefined"||d===null){ return null; } return [d.getMonth()+1,d.getDate(),d.getFullYear()].join("/"); }; MochiKit.DateTime.EXPORT=["isoDate","isoTimestamp","toISOTime","toISOTimestamp","toISODate","americanDate","toPaddedAmericanDate","toAmericanDate"]; MochiKit.DateTime.EXPORT_OK=[]; MochiKit.DateTime.EXPORT_TAGS={":common":MochiKit.DateTime.EXPORT,":all":MochiKit.DateTime.EXPORT}; MochiKit.DateTime.__new__=function(){ var base=this.NAME+"."; for(var k in this){ var o=this[k]; if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){ try{ o.NAME=base+k; } catch(e){ } } } }; MochiKit.DateTime.__new__(); if(typeof (MochiKit.Base)!="undefined"){ MochiKit.Base._exportSymbols(this,MochiKit.DateTime); }else{ (function(_245,_246){ if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(MochiKit.__export__===false)){ var all=_246.EXPORT_TAGS[":all"]; for(var i=0;i_250){ var i=_258.length-_250; res=fmt.separator+_258.substring(i,_258.length)+res; _258=_258.substring(0,i); } } res=_258+res; if(_24e>0){ while(frac.length<_251){ frac=frac+"0"; } res=res+fmt.decimal+frac; } return _253+res+_254; }; }; MochiKit.Format.numberFormatter=function(_25c,_25d,_25e){ if(typeof (_25d)=="undefined"){ _25d=""; } var _25f=_25c.match(/((?:[0#]+,)?[0#]+)(?:\.([0#]+))?(%)?/); if(!_25f){ throw TypeError("Invalid pattern"); } var _260=_25c.substr(0,_25f.index); var _261=_25c.substr(_25f.index+_25f[0].length); if(_260.search(/-/)==-1){ _260=_260+"-"; } var _262=_25f[1]; var frac=(typeof (_25f[2])=="string"&&_25f[2]!="")?_25f[2]:""; var _264=(typeof (_25f[3])=="string"&&_25f[3]!=""); var tmp=_262.split(/,/); var _266; if(typeof (_25e)=="undefined"){ _25e="default"; } if(tmp.length==1){ _266=null; }else{ _266=tmp[1].length; } var _267=_262.length-_262.replace(/0/g,"").length; var _268=frac.length-frac.replace(/0/g,"").length; var _269=frac.length; var rval=MochiKit.Format._numberFormatter(_25d,_260,_261,_25e,_264,_269,_267,_266,_268); var m=MochiKit.Base; if(m){ var fn=arguments.callee; var args=m.concat(arguments); rval.repr=function(){ return [self.NAME,"(",map(m.repr,args).join(", "),")"].join(""); }; } return rval; }; MochiKit.Format.formatLocale=function(_26e){ if(typeof (_26e)=="undefined"||_26e===null){ _26e="default"; } if(typeof (_26e)=="string"){ var rval=MochiKit.Format.LOCALE[_26e]; if(typeof (rval)=="string"){ rval=arguments.callee(rval); MochiKit.Format.LOCALE[_26e]=rval; } return rval; }else{ return _26e; } }; MochiKit.Format.twoDigitAverage=function(_270,_271){ if(_271){ var res=_270/_271; if(!isNaN(res)){ return MochiKit.Format.twoDigitFloat(res); } } return "0"; }; MochiKit.Format.twoDigitFloat=function(_273){ var res=roundToFixed(_273,2); if(res.indexOf(".00")>0){ return res.substring(0,res.length-3); }else{ if(res.charAt(res.length-1)=="0"){ return res.substring(0,res.length-1); }else{ return res; } } }; MochiKit.Format.lstrip=function(str,_276){ str=str+""; if(typeof (str)!="string"){ return null; } if(!_276){ return str.replace(/^\s+/,""); }else{ return str.replace(new RegExp("^["+_276+"]+"),""); } }; MochiKit.Format.rstrip=function(str,_278){ str=str+""; if(typeof (str)!="string"){ return null; } if(!_278){ return str.replace(/\s+$/,""); }else{ return str.replace(new RegExp("["+_278+"]+$"),""); } }; MochiKit.Format.strip=function(str,_27a){ var self=MochiKit.Format; return self.rstrip(self.lstrip(str,_27a),_27a); }; MochiKit.Format.truncToFixed=function(_27c,_27d){ var res=Math.floor(_27c).toFixed(0); if(_27c<0){ res=Math.ceil(_27c).toFixed(0); if(res.charAt(0)!="-"&&_27d>0){ res="-"+res; } } if(res.indexOf("e")<0&&_27d>0){ var tail=_27c.toString(); if(tail.indexOf("e")>0){ tail="."; }else{ if(tail.indexOf(".")<0){ tail="."; }else{ tail=tail.substring(tail.indexOf(".")); } } if(tail.length-1>_27d){ tail=tail.substring(0,_27d+1); } while(tail.length-1<_27d){ tail+="0"; } res+=tail; } return res; }; MochiKit.Format.roundToFixed=function(_280,_281){ var _282=Math.abs(_280)+0.5*Math.pow(10,-_281); var res=MochiKit.Format.truncToFixed(_282,_281); if(_280<0){ res="-"+res; } return res; }; MochiKit.Format.percentFormat=function(_284){ return MochiKit.Format.twoDigitFloat(100*_284)+"%"; }; MochiKit.Format.EXPORT=["truncToFixed","roundToFixed","numberFormatter","formatLocale","twoDigitAverage","twoDigitFloat","percentFormat","lstrip","rstrip","strip"]; MochiKit.Format.LOCALE={en_US:{separator:",",decimal:".",percent:"%"},de_DE:{separator:".",decimal:",",percent:"%"},pt_BR:{separator:".",decimal:",",percent:"%"},fr_FR:{separator:" ",decimal:",",percent:"%"},"default":"en_US"}; MochiKit.Format.EXPORT_OK=[]; MochiKit.Format.EXPORT_TAGS={":all":MochiKit.Format.EXPORT,":common":MochiKit.Format.EXPORT}; MochiKit.Format.__new__=function(){ var base=this.NAME+"."; var k,v,o; for(k in this.LOCALE){ o=this.LOCALE[k]; if(typeof (o)=="object"){ o.repr=function(){ return this.NAME; }; o.NAME=base+"LOCALE."+k; } } for(k in this){ o=this[k]; if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){ try{ o.NAME=base+k; } catch(e){ } } } }; MochiKit.Format.__new__(); if(typeof (MochiKit.Base)!="undefined"){ MochiKit.Base._exportSymbols(this,MochiKit.Format); }else{ (function(_289,_28a){ if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(MochiKit.__export__===false)){ var all=_28a.EXPORT_TAGS[":all"]; for(var i=0;i1){ fn=MochiKit.Base.partial.apply(null,arguments); } return this.addCallbacks(fn,fn); },addCallback:function(fn){ if(arguments.length>1){ fn=MochiKit.Base.partial.apply(null,arguments); } return this.addCallbacks(fn,null); },addErrback:function(fn){ if(arguments.length>1){ fn=MochiKit.Base.partial.apply(null,arguments); } return this.addCallbacks(null,fn); },addCallbacks:function(cb,eb){ if(this.chained){ throw new Error("Chained Deferreds can not be re-used"); } this.chain.push([cb,eb]); if(this.fired>=0){ this._fire(); } return this; },_fire:function(){ var _299=this.chain; var _29a=this.fired; var res=this.results[_29a]; var self=this; var cb=null; while(_299.length>0&&this.paused===0){ var pair=_299.shift(); var f=pair[_29a]; if(f===null){ continue; } try{ res=f(res); _29a=((res instanceof Error)?1:0); if(res instanceof MochiKit.Async.Deferred){ cb=function(res){ self._resback(res); self.paused--; if((self.paused===0)&&(self.fired>=0)){ self._fire(); } }; this.paused++; } } catch(err){ _29a=1; if(!(err instanceof Error)){ err=new MochiKit.Async.GenericError(err); } res=err; } } this.fired=_29a; this.results[_29a]=res; if(cb&&this.paused){ res.addBoth(cb); res.chained=true; } }}; MochiKit.Base.update(MochiKit.Async,{evalJSONRequest:function(req){ return MochiKit.Base.evalJSON(req.responseText); },succeed:function(_2a2){ var d=new MochiKit.Async.Deferred(); d.callback.apply(d,arguments); return d; },fail:function(_2a4){ var d=new MochiKit.Async.Deferred(); d.errback.apply(d,arguments); return d; },getXMLHttpRequest:function(){ var self=arguments.callee; if(!self.XMLHttpRequest){ var _2a7=[function(){ return new XMLHttpRequest(); },function(){ return new ActiveXObject("Msxml2.XMLHTTP"); },function(){ return new ActiveXObject("Microsoft.XMLHTTP"); },function(){ return new ActiveXObject("Msxml2.XMLHTTP.4.0"); },function(){ throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest"); }]; for(var i=0;i<_2a7.length;i++){ var func=_2a7[i]; try{ self.XMLHttpRequest=func; return func(); } catch(e){ } } } return self.XMLHttpRequest(); },_xhr_onreadystatechange:function(d){ var m=MochiKit.Base; if(this.readyState==4){ try{ this.onreadystatechange=null; } catch(e){ try{ this.onreadystatechange=m.noop; } catch(e){ } } var _2ac=null; try{ _2ac=this.status; if(!_2ac&&m.isNotEmpty(this.responseText)){ _2ac=304; } } catch(e){ } if(_2ac==200||_2ac==201||_2ac==204||_2ac==304||_2ac==1223){ d.callback(this); }else{ var err=new MochiKit.Async.XMLHttpRequestError(this,"Request failed"); if(err.number){ d.errback(err); }else{ d.errback(err); } } } },_xhr_canceller:function(req){ try{ req.onreadystatechange=null; } catch(e){ try{ req.onreadystatechange=MochiKit.Base.noop; } catch(e){ } } req.abort(); },sendXMLHttpRequest:function(req,_2b0){ if(typeof (_2b0)=="undefined"||_2b0===null){ _2b0=""; } var m=MochiKit.Base; var self=MochiKit.Async; var d=new self.Deferred(m.partial(self._xhr_canceller,req)); try{ req.onreadystatechange=m.bind(self._xhr_onreadystatechange,req,d); req.send(_2b0); } catch(e){ try{ req.onreadystatechange=null; } catch(ignore){ } d.errback(e); } return d; },doXHR:function(url,opts){ var self=MochiKit.Async; return self.callLater(0,self._doXHR,url,opts); },_doXHR:function(url,opts){ var m=MochiKit.Base; opts=m.update({method:"GET",sendContent:""},opts); var self=MochiKit.Async; var req=self.getXMLHttpRequest(); if(opts.queryString){ var qs=m.queryString(opts.queryString); if(qs){ url+="?"+qs; } } if("username" in opts){ req.open(opts.method,url,true,opts.username,opts.password); }else{ req.open(opts.method,url,true); } if(req.overrideMimeType&&opts.mimeType){ req.overrideMimeType(opts.mimeType); } req.setRequestHeader("X-Requested-With","XMLHttpRequest"); if(opts.headers){ var _2bd=opts.headers; if(!m.isArrayLike(_2bd)){ _2bd=m.items(_2bd); } for(var i=0;i<_2bd.length;i++){ var _2bf=_2bd[i]; var name=_2bf[0]; var _2c1=_2bf[1]; req.setRequestHeader(name,_2c1); } } return self.sendXMLHttpRequest(req,opts.sendContent); },_buildURL:function(url){ if(arguments.length>1){ var m=MochiKit.Base; var qs=m.queryString.apply(null,m.extend(null,arguments,1)); if(qs){ return url+"?"+qs; } } return url; },doSimpleXMLHttpRequest:function(url){ var self=MochiKit.Async; url=self._buildURL.apply(self,arguments); return self.doXHR(url); },loadJSONDoc:function(url){ var self=MochiKit.Async; url=self._buildURL.apply(self,arguments); var d=self.doXHR(url,{"mimeType":"text/plain","headers":[["Accept","application/json"]]}); d=d.addCallback(self.evalJSONRequest); return d; },wait:function(_2ca,_2cb){ var d=new MochiKit.Async.Deferred(); var m=MochiKit.Base; if(typeof (_2cb)!="undefined"){ d.addCallback(function(){ return _2cb; }); } var _2ce=setTimeout(m.bind("callback",d),Math.floor(_2ca*1000)); d.canceller=function(){ try{ clearTimeout(_2ce); } catch(e){ } }; return d; },callLater:function(_2cf,func){ var m=MochiKit.Base; var _2d2=m.partial.apply(m,m.extend(null,arguments,1)); return MochiKit.Async.wait(_2cf).addCallback(function(res){ return _2d2(); }); }}); MochiKit.Async.DeferredLock=function(){ this.waiting=[]; this.locked=false; this.id=this._nextId(); }; MochiKit.Async.DeferredLock.prototype={__class__:MochiKit.Async.DeferredLock,acquire:function(){ var d=new MochiKit.Async.Deferred(); if(this.locked){ this.waiting.push(d); }else{ this.locked=true; d.callback(this); } return d; },release:function(){ if(!this.locked){ throw TypeError("Tried to release an unlocked DeferredLock"); } this.locked=false; if(this.waiting.length>0){ this.locked=true; this.waiting.shift().callback(this); } },_nextId:MochiKit.Base.counter(),repr:function(){ var _2d5; if(this.locked){ _2d5="locked, "+this.waiting.length+" waiting"; }else{ _2d5="unlocked"; } return "DeferredLock("+this.id+", "+_2d5+")"; },toString:MochiKit.Base.forwardCall("repr")}; MochiKit.Async.DeferredList=function(list,_2d7,_2d8,_2d9,_2da){ MochiKit.Async.Deferred.apply(this,[_2da]); this.list=list; var _2db=[]; this.resultList=_2db; this.finishedCount=0; this.fireOnOneCallback=_2d7; this.fireOnOneErrback=_2d8; this.consumeErrors=_2d9; var cb=MochiKit.Base.bind(this._cbDeferred,this); for(var i=0;i=0){ var opt=elem.options[elem.selectedIndex]; var v=opt.value; if(!v){ var h=opt.outerHTML; if(h&&!h.match(/^[^>]+\svalue\s*=/i)){ v=opt.text; } } _2fa.push(name); _2fb.push(v); return null; } _2fa.push(name); _2fb.push(""); return null; }else{ var opts=elem.options; if(!opts.length){ _2fa.push(name); _2fb.push(""); return null; } for(var i=0;i]+\svalue\s*=/i)){ v=opt.text; } } _2fa.push(name); _2fb.push(v); } return null; } } if(_300==="FORM"||_300==="P"||_300==="SPAN"||_300==="DIV"){ return elem.childNodes; } _2fa.push(name); _2fb.push(elem.value||""); return null; } return elem.childNodes; }); return [_2fa,_2fb]; },withDocument:function(doc,func){ var self=MochiKit.DOM; var _309=self._document; var rval; try{ self._document=doc; rval=func(); } catch(e){ self._document=_309; throw e; } self._document=_309; return rval; },registerDOMConverter:function(name,_30c,wrap,_30e){ MochiKit.DOM.domConverters.register(name,_30c,wrap,_30e); },coerceToDOM:function(node,ctx){ var m=MochiKit.Base; var im=MochiKit.Iter; var self=MochiKit.DOM; if(im){ var iter=im.iter; var _315=im.repeat; } var map=m.map; var _317=self.domConverters; var _318=arguments.callee; var _319=m.NotFound; while(true){ if(typeof (node)=="undefined"||node===null){ return null; } if(typeof (node)=="function"&&typeof (node.length)=="number"&&!(node instanceof Function)){ node=im?im.list(node):m.extend(null,node); } if(typeof (node.nodeType)!="undefined"&&node.nodeType>0){ return node; } if(typeof (node)=="number"||typeof (node)=="boolean"){ node=node.toString(); } if(typeof (node)=="string"){ return self._document.createTextNode(node); } if(typeof (node.__dom__)=="function"){ node=node.__dom__(ctx); continue; } if(typeof (node.dom)=="function"){ node=node.dom(ctx); continue; } if(typeof (node)=="function"){ node=node.apply(ctx,[ctx]); continue; } if(im){ var _31a=null; try{ _31a=iter(node); } catch(e){ } if(_31a){ return map(_318,_31a,_315(ctx)); } }else{ if(m.isArrayLike(node)){ var func=function(n){ return _318(n,ctx); }; return map(func,node); } } try{ node=_317.match(node,ctx); continue; } catch(e){ if(e!=_319){ throw e; } } return self._document.createTextNode(node.toString()); } return undefined; },isChildNode:function(node,_31e){ var self=MochiKit.DOM; if(typeof (node)=="string"){ node=self.getElement(node); } if(typeof (_31e)=="string"){ _31e=self.getElement(_31e); } if(typeof (node)=="undefined"||node===null){ return false; } while(node!=null&&node!==self._document){ if(node===_31e){ return true; } node=node.parentNode; } return false; },setNodeAttribute:function(node,attr,_322){ var o={}; o[attr]=_322; try{ return MochiKit.DOM.updateNodeAttributes(node,o); } catch(e){ } return null; },getNodeAttribute:function(node,attr){ var self=MochiKit.DOM; var _327=self.attributeArray.renames[attr]; var _328=self.attributeArray.ignoreAttr[attr]; node=self.getElement(node); try{ if(_327){ return node[_327]; } var _329=node.getAttribute(attr); if(_329!=_328){ return _329; } } catch(e){ } return null; },removeNodeAttribute:function(node,attr){ var self=MochiKit.DOM; var _32d=self.attributeArray.renames[attr]; node=self.getElement(node); try{ if(_32d){ return node[_32d]; } return node.removeAttribute(attr); } catch(e){ } return null; },updateNodeAttributes:function(node,_32f){ var elem=node; var self=MochiKit.DOM; if(typeof (node)=="string"){ elem=self.getElement(node); } if(_32f){ var _332=MochiKit.Base.updatetree; if(self.attributeArray.compliant){ for(var k in _32f){ var v=_32f[k]; if(typeof (v)=="object"&&typeof (elem[k])=="object"){ if(k=="style"&&MochiKit.Style){ MochiKit.Style.setStyle(elem,v); }else{ _332(elem[k],v); } }else{ if(k.substring(0,2)=="on"){ if(typeof (v)=="string"){ v=new Function(v); } elem[k]=v; }else{ elem.setAttribute(k,v); } } if(typeof (elem[k])=="string"&&elem[k]!=v){ elem[k]=v; } } }else{ var _335=self.attributeArray.renames; for(var k in _32f){ v=_32f[k]; var _336=_335[k]; if(k=="style"&&typeof (v)=="string"){ elem.style.cssText=v; }else{ if(typeof (_336)=="string"){ elem[_336]=v; }else{ if(typeof (elem[k])=="object"&&typeof (v)=="object"){ if(k=="style"&&MochiKit.Style){ MochiKit.Style.setStyle(elem,v); }else{ _332(elem[k],v); } }else{ if(k.substring(0,2)=="on"){ if(typeof (v)=="string"){ v=new Function(v); } elem[k]=v; }else{ elem.setAttribute(k,v); } } } } if(typeof (elem[k])=="string"&&elem[k]!=v){ elem[k]=v; } } } } return elem; },appendChildNodes:function(node){ var elem=node; var self=MochiKit.DOM; if(typeof (node)=="string"){ elem=self.getElement(node); } var _33a=[self.coerceToDOM(MochiKit.Base.extend(null,arguments,1),elem)]; var _33b=MochiKit.Base.concat; while(_33a.length){ var n=_33a.shift(); if(typeof (n)=="undefined"||n===null){ }else{ if(typeof (n.nodeType)=="number"){ elem.appendChild(n); }else{ _33a=_33b(n,_33a); } } } return elem; },insertSiblingNodesBefore:function(node){ var elem=node; var self=MochiKit.DOM; if(typeof (node)=="string"){ elem=self.getElement(node); } var _340=[self.coerceToDOM(MochiKit.Base.extend(null,arguments,1),elem)]; var _341=elem.parentNode; var _342=MochiKit.Base.concat; while(_340.length){ var n=_340.shift(); if(typeof (n)=="undefined"||n===null){ }else{ if(typeof (n.nodeType)=="number"){ _341.insertBefore(n,elem); }else{ _340=_342(n,_340); } } } return _341; },insertSiblingNodesAfter:function(node){ var elem=node; var self=MochiKit.DOM; if(typeof (node)=="string"){ elem=self.getElement(node); } var _347=[self.coerceToDOM(MochiKit.Base.extend(null,arguments,1),elem)]; if(elem.nextSibling){ return self.insertSiblingNodesBefore(elem.nextSibling,_347); }else{ return self.appendChildNodes(elem.parentNode,_347); } },replaceChildNodes:function(node){ var elem=node; var self=MochiKit.DOM; if(typeof (node)=="string"){ elem=self.getElement(node); arguments[0]=elem; } var _34b; while((_34b=elem.firstChild)){ elem.removeChild(_34b); } if(arguments.length<2){ return elem; }else{ return self.appendChildNodes.apply(this,arguments); } },createDOM:function(name,_34d){ var elem; var self=MochiKit.DOM; var m=MochiKit.Base; if(typeof (_34d)=="string"||typeof (_34d)=="number"){ var args=m.extend([name,null],arguments,1); return arguments.callee.apply(this,args); } if(typeof (name)=="string"){ var _352=self._xhtml; if(_34d&&!self.attributeArray.compliant){ var _353=""; if("name" in _34d){ _353+=" name=\""+self.escapeHTML(_34d.name)+"\""; } if(name=="input"&&"type" in _34d){ _353+=" type=\""+self.escapeHTML(_34d.type)+"\""; } if(_353){ name="<"+name+_353+">"; _352=false; } } var d=self._document; if(_352&&d===document){ elem=d.createElementNS("http://www.w3.org/1999/xhtml",name); }else{ elem=d.createElement(name); } }else{ elem=name; } if(_34d){ self.updateNodeAttributes(elem,_34d); } if(arguments.length<=2){ return elem; }else{ var args=m.extend([elem],arguments,2); return self.appendChildNodes.apply(this,args); } },createDOMFunc:function(){ var m=MochiKit.Base; return m.partial.apply(this,m.extend([MochiKit.DOM.createDOM],arguments)); },removeElement:function(elem){ var self=MochiKit.DOM; var e=self.coerceToDOM(self.getElement(elem)); e.parentNode.removeChild(e); return e; },swapDOM:function(dest,src){ var self=MochiKit.DOM; dest=self.getElement(dest); var _35c=dest.parentNode; if(src){ src=self.coerceToDOM(self.getElement(src),_35c); _35c.replaceChild(src,dest); }else{ _35c.removeChild(dest); } return src; },getElement:function(id){ var self=MochiKit.DOM; if(arguments.length==1){ return ((typeof (id)=="string")?self._document.getElementById(id):id); }else{ return MochiKit.Base.map(self.getElement,arguments); } },getElementsByTagAndClassName:function(_35f,_360,_361){ var self=MochiKit.DOM; if(typeof (_35f)=="undefined"||_35f===null){ _35f="*"; } if(typeof (_361)=="undefined"||_361===null){ _361=self._document; } _361=self.getElement(_361); if(_361==null){ return []; } var _363=(_361.getElementsByTagName(_35f)||self._document.all); if(typeof (_360)=="undefined"||_360===null){ return MochiKit.Base.extend(null,_363); } var _364=[]; for(var i=0;i<_363.length;i++){ var _366=_363[i]; var cls=_366.className; if(typeof (cls)!="string"){ cls=_366.getAttribute("class"); } if(typeof (cls)=="string"){ var _368=cls.split(" "); for(var j=0;j<_368.length;j++){ if(_368[j]==_360){ _364.push(_366); break; } } } } return _364; },_newCallStack:function(path,once){ var rval=function(){ var _36d=arguments.callee.callStack; for(var i=0;i<_36d.length;i++){ if(_36d[i].apply(this,arguments)===false){ break; } } if(once){ try{ this[path]=null; } catch(e){ } } }; rval.callStack=[]; return rval; },addToCallStack:function(_36f,path,func,once){ var self=MochiKit.DOM; var _374=_36f[path]; var _375=_374; if(!(typeof (_374)=="function"&&typeof (_374.callStack)=="object"&&_374.callStack!==null)){ _375=self._newCallStack(path,once); if(typeof (_374)=="function"){ _375.callStack.push(_374); } _36f[path]=_375; } _375.callStack.push(func); },addLoadEvent:function(func){ var self=MochiKit.DOM; self.addToCallStack(self._window,"onload",func,true); },focusOnLoad:function(_378){ var self=MochiKit.DOM; self.addLoadEvent(function(){ _378=self.getElement(_378); if(_378){ _378.focus(); } }); },setElementClass:function(_37a,_37b){ var self=MochiKit.DOM; var obj=self.getElement(_37a); if(self.attributeArray.compliant){ obj.setAttribute("class",_37b); }else{ obj.setAttribute("className",_37b); } },toggleElementClass:function(_37e){ var self=MochiKit.DOM; for(var i=1;i/g,">"); },toHTML:function(dom){ return MochiKit.DOM.emitHTML(dom).join(""); },emitHTML:function(dom,lst){ if(typeof (lst)=="undefined"||lst===null){ lst=[]; } var _3a1=[dom]; var self=MochiKit.DOM; var _3a3=self.escapeHTML; var _3a4=self.attributeArray; while(_3a1.length){ dom=_3a1.pop(); if(typeof (dom)=="string"){ lst.push(dom); }else{ if(dom.nodeType==1){ lst.push("<"+dom.tagName.toLowerCase()); var _3a5=[]; var _3a6=_3a4(dom); for(var i=0;i<_3a6.length;i++){ var a=_3a6[i]; _3a5.push([" ",a.name,"=\"",_3a3(a.value),"\""]); } _3a5.sort(); for(i=0;i<_3a5.length;i++){ var _3a9=_3a5[i]; for(var j=0;j<_3a9.length;j++){ lst.push(_3a9[j]); } } if(dom.hasChildNodes()){ lst.push(">"); _3a1.push(""); var _3ab=dom.childNodes; for(i=_3ab.length-1;i>=0;i--){ _3a1.push(_3ab[i]); } }else{ lst.push("/>"); } }else{ if(dom.nodeType==3){ lst.push(_3a3(dom.nodeValue)); } } } } return lst; },scrapeText:function(node,_3ad){ var rval=[]; (function(node){ var cn=node.childNodes; if(cn){ for(var i=0;i0){ var _3ca=m.filter; _3c9=function(node){ return _3ca(_3c9.ignoreAttrFilter,node.attributes); }; _3c9.ignoreAttr={}; var _3cc=_3c8.attributes; var _3cd=_3c9.ignoreAttr; for(var i=0;i<_3cc.length;i++){ var a=_3cc[i]; _3cd[a.name]=a.value; } _3c9.ignoreAttrFilter=function(a){ return (_3c9.ignoreAttr[a.name]!=a.value); }; _3c9.compliant=false; _3c9.renames={"class":"className","checked":"defaultChecked","usemap":"useMap","for":"htmlFor","readonly":"readOnly","colspan":"colSpan","bgcolor":"bgColor","cellspacing":"cellSpacing","cellpadding":"cellPadding"}; }else{ _3c9=function(node){ return node.attributes; }; _3c9.compliant=true; _3c9.ignoreAttr={}; _3c9.renames={}; } this.attributeArray=_3c9; var _3d2=function(_3d3,arr){ var _3d5=arr[0]; var _3d6=arr[1]; var _3d7=_3d6.split(".")[1]; var str=""; str+="if (!MochiKit."+_3d7+") { throw new Error(\""; str+="This function has been deprecated and depends on MochiKit."; str+=_3d7+".\");}"; str+="return "+_3d6+".apply(this, arguments);"; MochiKit[_3d3][_3d5]=new Function(str); }; for(var i=0;i0){ abort(repr(expr)); } },buildMatchExpression:function(){ var repr=MochiKit.Base.repr; var _3e4=this.params; var _3e5=[]; var _3e6,i; function childElements(_3e8){ return "MochiKit.Base.filter(function (node) { return node.nodeType == 1; }, "+_3e8+".childNodes)"; } if(_3e4.wildcard){ _3e5.push("true"); } if(_3e6=_3e4.id){ _3e5.push("element.id == "+repr(_3e6)); } if(_3e6=_3e4.tagName){ _3e5.push("element.tagName.toUpperCase() == "+repr(_3e6)); } if((_3e6=_3e4.classNames).length>0){ for(i=0;i<_3e6.length;i++){ _3e5.push("MochiKit.DOM.hasElementClass(element, "+repr(_3e6[i])+")"); } } if((_3e6=_3e4.pseudoClassNames).length>0){ for(i=0;i<_3e6.length;i++){ var _3e9=_3e6[i].match(/^([^(]+)(?:\((.*)\))?$/); var _3ea=_3e9[1]; var _3eb=_3e9[2]; switch(_3ea){ case "root": _3e5.push("element.nodeType == 9 || element === element.ownerDocument.documentElement"); break; case "nth-child": case "nth-last-child": case "nth-of-type": case "nth-last-of-type": _3e9=_3eb.match(/^((?:(\d+)n\+)?(\d+)|odd|even)$/); if(!_3e9){ throw "Invalid argument to pseudo element nth-child: "+_3eb; } var a,b; if(_3e9[0]=="odd"){ a=2; b=1; }else{ if(_3e9[0]=="even"){ a=2; b=0; }else{ a=_3e9[2]&&parseInt(_3e9)||null; b=parseInt(_3e9[3]); } } _3e5.push("this.nthChild(element,"+a+","+b+","+!!_3ea.match("^nth-last")+","+!!_3ea.match("of-type$")+")"); break; case "first-child": _3e5.push("this.nthChild(element, null, 1)"); break; case "last-child": _3e5.push("this.nthChild(element, null, 1, true)"); break; case "first-of-type": _3e5.push("this.nthChild(element, null, 1, false, true)"); break; case "last-of-type": _3e5.push("this.nthChild(element, null, 1, true, true)"); break; case "only-child": _3e5.push(childElements("element.parentNode")+".length == 1"); break; case "only-of-type": _3e5.push("MochiKit.Base.filter(function (node) { return node.tagName == element.tagName; }, "+childElements("element.parentNode")+").length == 1"); break; case "empty": _3e5.push("element.childNodes.length == 0"); break; case "enabled": _3e5.push("(this.isUIElement(element) && element.disabled === false)"); break; case "disabled": _3e5.push("(this.isUIElement(element) && element.disabled === true)"); break; case "checked": _3e5.push("(this.isUIElement(element) && element.checked === true)"); break; case "not": var _3ee=new MochiKit.Selector.Selector(_3eb); _3e5.push("!( "+_3ee.buildMatchExpression()+")"); break; } } } if(_3e6=_3e4.attributes){ MochiKit.Base.map(function(_3ef){ var _3f0="MochiKit.DOM.getNodeAttribute(element, "+repr(_3ef.name)+")"; var _3f1=function(_3f2){ return _3f0+".split("+repr(_3f2)+")"; }; _3e5.push(_3f0+" != null"); switch(_3ef.operator){ case "=": _3e5.push(_3f0+" == "+repr(_3ef.value)); break; case "~=": _3e5.push("MochiKit.Base.findValue("+_3f1(" ")+", "+repr(_3ef.value)+") > -1"); break; case "^=": _3e5.push(_3f0+".substring(0, "+_3ef.value.length+") == "+repr(_3ef.value)); break; case "$=": _3e5.push(_3f0+".substring("+_3f0+".length - "+_3ef.value.length+") == "+repr(_3ef.value)); break; case "*=": _3e5.push(_3f0+".match("+repr(_3ef.value)+")"); break; case "|=": _3e5.push(_3f1("-")+"[0].toUpperCase() == "+repr(_3ef.value.toUpperCase())); break; case "!=": _3e5.push(_3f0+" != "+repr(_3ef.value)); break; case "": case undefined: break; default: throw "Unknown operator "+_3ef.operator+" in selector"; } },_3e6); } return _3e5.join(" && "); },compileMatcher:function(){ var code="return (!element.tagName) ? false : "+this.buildMatchExpression()+";"; this.match=new Function("element",code); },nthChild:function(_3f4,a,b,_3f7,_3f8){ var _3f9=MochiKit.Base.filter(function(node){ return node.nodeType==1; },_3f4.parentNode.childNodes); if(_3f8){ _3f9=MochiKit.Base.filter(function(node){ return node.tagName==_3f4.tagName; },_3f9); } if(_3f7){ _3f9=MochiKit.Iter.reversed(_3f9); } if(a){ var _3fc=MochiKit.Base.findIdentical(_3f9,_3f4); return ((_3fc+1-b)/a)%1==0; }else{ return b==MochiKit.Base.findIdentical(_3f9,_3f4)+1; } },isUIElement:function(_3fd){ return MochiKit.Base.findValue(["input","button","select","option","textarea","object"],_3fd.tagName.toLowerCase())>-1; },findElements:function(_3fe,axis){ var _400; if(axis==undefined){ axis=""; } function inScope(_401,_402){ if(axis==""){ return MochiKit.DOM.isChildNode(_401,_402); }else{ if(axis==">"){ return _401.parentNode===_402; }else{ if(axis=="+"){ return _401===nextSiblingElement(_402); }else{ if(axis=="~"){ var _403=_402; while(_403=nextSiblingElement(_403)){ if(_401===_403){ return true; } } return false; }else{ throw "Invalid axis: "+axis; } } } } } if(_400=MochiKit.DOM.getElement(this.params.id)){ if(this.match(_400)){ if(!_3fe||inScope(_400,_3fe)){ return [_400]; } } } function nextSiblingElement(node){ node=node.nextSibling; while(node&&node.nodeType!=1){ node=node.nextSibling; } return node; } if(axis==""){ _3fe=(_3fe||MochiKit.DOM.currentDocument()).getElementsByTagName(this.params.tagName||"*"); }else{ if(axis==">"){ if(!_3fe){ throw "> combinator not allowed without preceeding expression"; } _3fe=MochiKit.Base.filter(function(node){ return node.nodeType==1; },_3fe.childNodes); }else{ if(axis=="+"){ if(!_3fe){ throw "+ combinator not allowed without preceeding expression"; } _3fe=nextSiblingElement(_3fe)&&[nextSiblingElement(_3fe)]; }else{ if(axis=="~"){ if(!_3fe){ throw "~ combinator not allowed without preceeding expression"; } var _406=[]; while(nextSiblingElement(_3fe)){ _3fe=nextSiblingElement(_3fe); _406.push(_3fe); } _3fe=_406; } } } } if(!_3fe){ return []; } var _407=MochiKit.Base.filter(MochiKit.Base.bind(function(_408){ return this.match(_408); },this),_3fe); return _407; },repr:function(){ return "Selector("+this.expression+")"; },toString:MochiKit.Base.forwardCall("repr")}; MochiKit.Base.update(MochiKit.Selector,{findChildElements:function(_409,_40a){ var uniq=function(arr){ var res=[]; for(var i=0;i+~]$/)){ _410=match[0]; return _412; }else{ var _414=new MochiKit.Selector.Selector(expr); var _415=MochiKit.Iter.reduce(function(_416,_417){ return MochiKit.Base.extend(_416,_414.findElements(_417||_409,_410)); },_412,[]); _410=""; return _415; } }; var _418=_40f.replace(/(^\s+|\s+$)/g,"").split(/\s+/); return uniq(MochiKit.Iter.reduce(_411,_418,[null])); },_40a)); },findDocElements:function(){ return MochiKit.Selector.findChildElements(MochiKit.DOM.currentDocument(),arguments); },__new__:function(){ var m=MochiKit.Base; this.$$=this.findDocElements; this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; m.nameFunctions(this); }}); MochiKit.Selector.__new__(); MochiKit.Base._exportSymbols(this,MochiKit.Selector); MochiKit.Base._deps("Style",["Base","DOM"]); MochiKit.Style.NAME="MochiKit.Style"; MochiKit.Style.VERSION="1.4.2"; MochiKit.Style.__repr__=function(){ return "["+this.NAME+" "+this.VERSION+"]"; }; MochiKit.Style.toString=function(){ return this.__repr__(); }; MochiKit.Style.EXPORT_OK=[]; MochiKit.Style.EXPORT=["setStyle","setOpacity","getStyle","getElementDimensions","elementDimensions","setElementDimensions","getElementPosition","elementPosition","setElementPosition","makePositioned","undoPositioned","makeClipping","undoClipping","setDisplayForElement","hideElement","showElement","getViewportDimensions","getViewportPosition","Dimensions","Coordinates"]; MochiKit.Style.Dimensions=function(w,h){ this.w=w; this.h=h; }; MochiKit.Style.Dimensions.prototype.__repr__=function(){ var repr=MochiKit.Base.repr; return "{w: "+repr(this.w)+", h: "+repr(this.h)+"}"; }; MochiKit.Style.Dimensions.prototype.toString=function(){ return this.__repr__(); }; MochiKit.Style.Coordinates=function(x,y){ this.x=x; this.y=y; }; MochiKit.Style.Coordinates.prototype.__repr__=function(){ var repr=MochiKit.Base.repr; return "{x: "+repr(this.x)+", y: "+repr(this.y)+"}"; }; MochiKit.Style.Coordinates.prototype.toString=function(){ return this.__repr__(); }; MochiKit.Base.update(MochiKit.Style,{getStyle:function(elem,_421){ var dom=MochiKit.DOM; var d=dom._document; elem=dom.getElement(elem); _421=MochiKit.Base.camelize(_421); if(!elem||elem==d){ return undefined; } if(_421=="opacity"&&typeof (elem.filters)!="undefined"){ var _424=(MochiKit.Style.getStyle(elem,"filter")||"").match(/alpha\(opacity=(.*)\)/); if(_424&&_424[1]){ return parseFloat(_424[1])/100; } return 1; } if(_421=="float"||_421=="cssFloat"||_421=="styleFloat"){ if(elem.style["float"]){ return elem.style["float"]; }else{ if(elem.style.cssFloat){ return elem.style.cssFloat; }else{ if(elem.style.styleFloat){ return elem.style.styleFloat; }else{ return "none"; } } } } var _425=elem.style?elem.style[_421]:null; if(!_425){ if(d.defaultView&&d.defaultView.getComputedStyle){ var css=d.defaultView.getComputedStyle(elem,null); _421=_421.replace(/([A-Z])/g,"-$1").toLowerCase(); _425=css?css.getPropertyValue(_421):null; }else{ if(elem.currentStyle){ _425=elem.currentStyle[_421]; if(/^\d/.test(_425)&&!/px$/.test(_425)&&_421!="fontWeight"){ var left=elem.style.left; var _428=elem.runtimeStyle.left; elem.runtimeStyle.left=elem.currentStyle.left; elem.style.left=_425||0; _425=elem.style.pixelLeft+"px"; elem.style.left=left; elem.runtimeStyle.left=_428; } } } } if(_421=="opacity"){ _425=parseFloat(_425); } if(/Opera/.test(navigator.userAgent)&&(MochiKit.Base.findValue(["left","top","right","bottom"],_421)!=-1)){ if(MochiKit.Style.getStyle(elem,"position")=="static"){ _425="auto"; } } return _425=="auto"?null:_425; },setStyle:function(elem,_42a){ elem=MochiKit.DOM.getElement(elem); for(var name in _42a){ switch(name){ case "opacity": MochiKit.Style.setOpacity(elem,_42a[name]); break; case "float": case "cssFloat": case "styleFloat": if(typeof (elem.style["float"])!="undefined"){ elem.style["float"]=_42a[name]; }else{ if(typeof (elem.style.cssFloat)!="undefined"){ elem.style.cssFloat=_42a[name]; }else{ elem.style.styleFloat=_42a[name]; } } break; default: elem.style[MochiKit.Base.camelize(name)]=_42a[name]; } } },setOpacity:function(elem,o){ elem=MochiKit.DOM.getElement(elem); var self=MochiKit.Style; if(o==1){ var _42f=/Gecko/.test(navigator.userAgent)&&!(/Konqueror|AppleWebKit|KHTML/.test(navigator.userAgent)); elem.style["opacity"]=_42f?0.999999:1; if(/MSIE/.test(navigator.userAgent)){ elem.style["filter"]=self.getStyle(elem,"filter").replace(/alpha\([^\)]*\)/gi,""); } }else{ if(o<0.00001){ o=0; } elem.style["opacity"]=o; if(/MSIE/.test(navigator.userAgent)){ elem.style["filter"]=self.getStyle(elem,"filter").replace(/alpha\([^\)]*\)/gi,"")+"alpha(opacity="+o*100+")"; } } },getElementPosition:function(elem,_431){ var self=MochiKit.Style; var dom=MochiKit.DOM; elem=dom.getElement(elem); if(!elem||(!(elem.x&&elem.y)&&(!elem.parentNode===null||self.getStyle(elem,"display")=="none"))){ return undefined; } var c=new self.Coordinates(0,0); var box=null; var _436=null; var d=MochiKit.DOM._document; var de=d.documentElement; var b=d.body; if(!elem.parentNode&&elem.x&&elem.y){ c.x+=elem.x||0; c.y+=elem.y||0; }else{ if(elem.getBoundingClientRect){ box=elem.getBoundingClientRect(); c.x+=box.left+(de.scrollLeft||b.scrollLeft)-(de.clientLeft||0); c.y+=box.top+(de.scrollTop||b.scrollTop)-(de.clientTop||0); }else{ if(elem.offsetParent){ c.x+=elem.offsetLeft; c.y+=elem.offsetTop; _436=elem.offsetParent; if(_436!=elem){ while(_436){ c.x+=parseInt(_436.style.borderLeftWidth)||0; c.y+=parseInt(_436.style.borderTopWidth)||0; c.x+=_436.offsetLeft; c.y+=_436.offsetTop; _436=_436.offsetParent; } } var ua=navigator.userAgent.toLowerCase(); if((typeof (opera)!="undefined"&&parseFloat(opera.version())<9)||(ua.indexOf("AppleWebKit")!=-1&&self.getStyle(elem,"position")=="absolute")){ c.x-=b.offsetLeft; c.y-=b.offsetTop; } if(elem.parentNode){ _436=elem.parentNode; }else{ _436=null; } while(_436){ var _43b=_436.tagName.toUpperCase(); if(_43b==="BODY"||_43b==="HTML"){ break; } var disp=self.getStyle(_436,"display"); if(disp.search(/^inline|table-row.*$/i)){ c.x-=_436.scrollLeft; c.y-=_436.scrollTop; } if(_436.parentNode){ _436=_436.parentNode; }else{ _436=null; } } } } } if(typeof (_431)!="undefined"){ _431=arguments.callee(_431); if(_431){ c.x-=(_431.x||0); c.y-=(_431.y||0); } } return c; },setElementPosition:function(elem,_43e,_43f){ elem=MochiKit.DOM.getElement(elem); if(typeof (_43f)=="undefined"){ _43f="px"; } var _440={}; var _441=MochiKit.Base.isUndefinedOrNull; if(!_441(_43e.x)){ _440["left"]=_43e.x+_43f; } if(!_441(_43e.y)){ _440["top"]=_43e.y+_43f; } MochiKit.DOM.updateNodeAttributes(elem,{"style":_440}); },makePositioned:function(_442){ _442=MochiKit.DOM.getElement(_442); var pos=MochiKit.Style.getStyle(_442,"position"); if(pos=="static"||!pos){ _442.style.position="relative"; if(/Opera/.test(navigator.userAgent)){ _442.style.top=0; _442.style.left=0; } } },undoPositioned:function(_444){ _444=MochiKit.DOM.getElement(_444); if(_444.style.position=="relative"){ _444.style.position=_444.style.top=_444.style.left=_444.style.bottom=_444.style.right=""; } },makeClipping:function(_445){ _445=MochiKit.DOM.getElement(_445); var s=_445.style; var _447={"overflow":s.overflow,"overflow-x":s.overflowX,"overflow-y":s.overflowY}; if((MochiKit.Style.getStyle(_445,"overflow")||"visible")!="hidden"){ _445.style.overflow="hidden"; _445.style.overflowX="hidden"; _445.style.overflowY="hidden"; } return _447; },undoClipping:function(_448,_449){ _448=MochiKit.DOM.getElement(_448); if(typeof (_449)=="string"){ _448.style.overflow=_449; }else{ if(_449!=null){ _448.style.overflow=_449["overflow"]; _448.style.overflowX=_449["overflow-x"]; _448.style.overflowY=_449["overflow-y"]; } } },getElementDimensions:function(elem,_44b){ var self=MochiKit.Style; var dom=MochiKit.DOM; if(typeof (elem.w)=="number"||typeof (elem.h)=="number"){ return new self.Dimensions(elem.w||0,elem.h||0); } elem=dom.getElement(elem); if(!elem){ return undefined; } var disp=self.getStyle(elem,"display"); if(disp=="none"||disp==""||typeof (disp)=="undefined"){ var s=elem.style; var _450=s.visibility; var _451=s.position; var _452=s.display; s.visibility="hidden"; s.position="absolute"; s.display=self._getDefaultDisplay(elem); var _453=elem.offsetWidth; var _454=elem.offsetHeight; s.display=_452; s.position=_451; s.visibility=_450; }else{ _453=elem.offsetWidth||0; _454=elem.offsetHeight||0; } if(_44b){ var _455="colSpan" in elem&&"rowSpan" in elem; var _456=(_455&&elem.parentNode&&self.getStyle(elem.parentNode,"borderCollapse")=="collapse"); if(_456){ if(/MSIE/.test(navigator.userAgent)){ var _457=elem.previousSibling?0.5:1; var _458=elem.nextSibling?0.5:1; }else{ var _457=0.5; var _458=0.5; } }else{ var _457=1; var _458=1; } _453-=Math.round((parseFloat(self.getStyle(elem,"paddingLeft"))||0)+(parseFloat(self.getStyle(elem,"paddingRight"))||0)+_457*(parseFloat(self.getStyle(elem,"borderLeftWidth"))||0)+_458*(parseFloat(self.getStyle(elem,"borderRightWidth"))||0)); if(_455){ if(/Gecko|Opera/.test(navigator.userAgent)&&!/Konqueror|AppleWebKit|KHTML/.test(navigator.userAgent)){ var _459=0; }else{ if(/MSIE/.test(navigator.userAgent)){ var _459=1; }else{ var _459=_456?0.5:1; } } }else{ var _459=1; } _454-=Math.round((parseFloat(self.getStyle(elem,"paddingTop"))||0)+(parseFloat(self.getStyle(elem,"paddingBottom"))||0)+_459*((parseFloat(self.getStyle(elem,"borderTopWidth"))||0)+(parseFloat(self.getStyle(elem,"borderBottomWidth"))||0))); } return new self.Dimensions(_453,_454); },setElementDimensions:function(elem,_45b,_45c){ elem=MochiKit.DOM.getElement(elem); if(typeof (_45c)=="undefined"){ _45c="px"; } var _45d={}; var _45e=MochiKit.Base.isUndefinedOrNull; if(!_45e(_45b.w)){ _45d["width"]=_45b.w+_45c; } if(!_45e(_45b.h)){ _45d["height"]=_45b.h+_45c; } MochiKit.DOM.updateNodeAttributes(elem,{"style":_45d}); },_getDefaultDisplay:function(elem){ var self=MochiKit.Style; var dom=MochiKit.DOM; elem=dom.getElement(elem); if(!elem){ return undefined; } var _462=elem.tagName.toUpperCase(); return self._defaultDisplay[_462]||"block"; },setDisplayForElement:function(_463,_464){ var _465=MochiKit.Base.extend(null,arguments,1); var _466=MochiKit.DOM.getElement; for(var i=0;i<_465.length;i++){ _464=_466(_465[i]); if(_464){ _464.style.display=_463; } } },getViewportDimensions:function(){ var d=new MochiKit.Style.Dimensions(); var w=MochiKit.DOM._window; var b=MochiKit.DOM._document.body; if(w.innerWidth){ d.w=w.innerWidth; d.h=w.innerHeight; }else{ if(b&&b.parentElement&&b.parentElement.clientWidth){ d.w=b.parentElement.clientWidth; d.h=b.parentElement.clientHeight; }else{ if(b&&b.clientWidth){ d.w=b.clientWidth; d.h=b.clientHeight; } } } return d; },getViewportPosition:function(){ var c=new MochiKit.Style.Coordinates(0,0); var d=MochiKit.DOM._document; var de=d.documentElement; var db=d.body; if(de&&(de.scrollTop||de.scrollLeft)){ c.x=de.scrollLeft; c.y=de.scrollTop; }else{ if(db){ c.x=db.scrollLeft; c.y=db.scrollTop; } } return c; },__new__:function(){ var m=MochiKit.Base; var _470=["A","ABBR","ACRONYM","B","BASEFONT","BDO","BIG","BR","CITE","CODE","DFN","EM","FONT","I","IMG","KBD","LABEL","Q","S","SAMP","SMALL","SPAN","STRIKE","STRONG","SUB","SUP","TEXTAREA","TT","U","VAR"]; this._defaultDisplay={"TABLE":"table","THEAD":"table-header-group","TBODY":"table-row-group","TFOOT":"table-footer-group","COLGROUP":"table-column-group","COL":"table-column","TR":"table-row","TD":"table-cell","TH":"table-cell","CAPTION":"table-caption","LI":"list-item","INPUT":"inline-block","SELECT":"inline-block"}; if(/MSIE/.test(navigator.userAgent)){ for(var k in this._defaultDisplay){ var v=this._defaultDisplay[k]; if(v.indexOf("table")==0){ this._defaultDisplay[k]="block"; } } } for(var i=0;i<_470.length;i++){ this._defaultDisplay[_470[i]]="inline"; } this.elementPosition=this.getElementPosition; this.elementDimensions=this.getElementDimensions; this.hideElement=m.partial(this.setDisplayForElement,"none"); this.showElement=m.partial(this.setDisplayForElement,"block"); this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; m.nameFunctions(this); }}); MochiKit.Style.__new__(); MochiKit.Base._exportSymbols(this,MochiKit.Style); MochiKit.Base._deps("LoggingPane",["Base","Logging"]); MochiKit.LoggingPane.NAME="MochiKit.LoggingPane"; MochiKit.LoggingPane.VERSION="1.4.2"; MochiKit.LoggingPane.__repr__=function(){ return "["+this.NAME+" "+this.VERSION+"]"; }; MochiKit.LoggingPane.toString=function(){ return this.__repr__(); }; MochiKit.LoggingPane.createLoggingPane=function(_474){ var m=MochiKit.LoggingPane; _474=!(!_474); if(m._loggingPane&&m._loggingPane.inline!=_474){ m._loggingPane.closePane(); m._loggingPane=null; } if(!m._loggingPane||m._loggingPane.closed){ m._loggingPane=new m.LoggingPane(_474,MochiKit.Logging.logger); } return m._loggingPane; }; MochiKit.LoggingPane.LoggingPane=function(_476,_477){ if(typeof (_477)=="undefined"||_477===null){ _477=MochiKit.Logging.logger; } this.logger=_477; var _478=MochiKit.Base.update; var _479=MochiKit.Base.updatetree; var bind=MochiKit.Base.bind; var _47b=MochiKit.Base.clone; var win=window; var uid="_MochiKit_LoggingPane"; if(typeof (MochiKit.DOM)!="undefined"){ win=MochiKit.DOM.currentWindow(); } if(!_476){ var url=win.location.href.split("?")[0].replace(/[#:\/.><&%-]/g,"_"); var name=uid+"_"+url; var nwin=win.open("",name,"dependent,resizable,height=200"); if(!nwin){ alert("Not able to open debugging window due to pop-up blocking."); return undefined; } nwin.document.write(""+"[MochiKit.LoggingPane]"+""); nwin.document.close(); nwin.document.title+=" "+win.document.title; win=nwin; } var doc=win.document; this.doc=doc; var _482=doc.getElementById(uid); var _483=!!_482; if(_482&&typeof (_482.loggingPane)!="undefined"){ _482.loggingPane.logger=this.logger; _482.loggingPane.buildAndApplyFilter(); return _482.loggingPane; } if(_483){ var _484; while((_484=_482.firstChild)){ _482.removeChild(_484); } }else{ _482=doc.createElement("div"); _482.id=uid; } _482.loggingPane=this; var _485=doc.createElement("input"); var _486=doc.createElement("input"); var _487=doc.createElement("button"); var _488=doc.createElement("button"); var _489=doc.createElement("button"); var _48a=doc.createElement("button"); var _48b=doc.createElement("div"); var _48c=doc.createElement("div"); var _48d=uid+"_Listener"; this.colorTable=_47b(this.colorTable); var _48e=[]; var _48f=null; var _490=function(msg){ var _492=msg.level; if(typeof (_492)=="number"){ _492=MochiKit.Logging.LogLevel[_492]; } return _492; }; var _493=function(msg){ return msg.info.join(" "); }; var _495=bind(function(msg){ var _497=_490(msg); var text=_493(msg); var c=this.colorTable[_497]; var p=doc.createElement("span"); p.className="MochiKit-LogMessage MochiKit-LogLevel-"+_497; p.style.cssText="margin: 0px; white-space: -moz-pre-wrap; white-space: -o-pre-wrap; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; wrap-option: emergency; color: "+c; p.appendChild(doc.createTextNode(_497+": "+text)); _48c.appendChild(p); _48c.appendChild(doc.createElement("br")); if(_48b.offsetHeight>_48b.scrollHeight){ _48b.scrollTop=0; }else{ _48b.scrollTop=_48b.scrollHeight; } },this); var _49b=function(msg){ _48e[_48e.length]=msg; _495(msg); }; var _49d=function(){ var _49e,_49f; try{ _49e=new RegExp(_485.value); _49f=new RegExp(_486.value); } catch(e){ logDebug("Error in filter regex: "+e.message); return null; } return function(msg){ return (_49e.test(_490(msg))&&_49f.test(_493(msg))); }; }; var _4a1=function(){ while(_48c.firstChild){ _48c.removeChild(_48c.firstChild); } }; var _4a2=function(){ _48e=[]; _4a1(); }; var _4a3=bind(function(){ if(this.closed){ return; } this.closed=true; if(MochiKit.LoggingPane._loggingPane==this){ MochiKit.LoggingPane._loggingPane=null; } this.logger.removeListener(_48d); try{ try{ _482.loggingPane=null; } catch(e){ logFatal("Bookmarklet was closed incorrectly."); } if(_476){ _482.parentNode.removeChild(_482); }else{ this.win.close(); } } catch(e){ } },this); var _4a4=function(){ _4a1(); for(var i=0;i<_48e.length;i++){ var msg=_48e[i]; if(_48f===null||_48f(msg)){ _495(msg); } } }; this.buildAndApplyFilter=function(){ _48f=_49d(); _4a4(); this.logger.removeListener(_48d); this.logger.addListener(_48d,_48f,_49b); }; var _4a7=bind(function(){ _48e=this.logger.getMessages(); _4a4(); },this); var _4a8=bind(function(_4a9){ _4a9=_4a9||window.event; key=_4a9.which||_4a9.keyCode; if(key==13){ this.buildAndApplyFilter(); } },this); var _4aa="display: block; z-index: 1000; left: 0px; bottom: 0px; position: fixed; width: 100%; background-color: white; font: "+this.logFont; if(_476){ _4aa+="; height: 10em; border-top: 2px solid black"; }else{ _4aa+="; height: 100%;"; } _482.style.cssText=_4aa; if(!_483){ doc.body.appendChild(_482); } _4aa={"cssText":"width: 33%; display: inline; font: "+this.logFont}; _479(_485,{"value":"FATAL|ERROR|WARNING|INFO|DEBUG","onkeypress":_4a8,"style":_4aa}); _482.appendChild(_485); _479(_486,{"value":".*","onkeypress":_4a8,"style":_4aa}); _482.appendChild(_486); _4aa="width: 8%; display:inline; font: "+this.logFont; _487.appendChild(doc.createTextNode("Filter")); _487.onclick=bind("buildAndApplyFilter",this); _487.style.cssText=_4aa; _482.appendChild(_487); _488.appendChild(doc.createTextNode("Load")); _488.onclick=_4a7; _488.style.cssText=_4aa; _482.appendChild(_488); _489.appendChild(doc.createTextNode("Clear")); _489.onclick=_4a2; _489.style.cssText=_4aa; _482.appendChild(_489); _48a.appendChild(doc.createTextNode("Close")); _48a.onclick=_4a3; _48a.style.cssText=_4aa; _482.appendChild(_48a); _48b.style.cssText="overflow: auto; width: 100%"; _48c.style.cssText="width: 100%; height: "+(_476?"8em":"100%"); _48b.appendChild(_48c); _482.appendChild(_48b); this.buildAndApplyFilter(); _4a7(); if(_476){ this.win=undefined; }else{ this.win=win; } this.inline=_476; this.closePane=_4a3; this.closed=false; return this; }; MochiKit.LoggingPane.LoggingPane.prototype={"logFont":"8pt Verdana,sans-serif","colorTable":{"ERROR":"red","FATAL":"darkred","WARNING":"blue","INFO":"black","DEBUG":"green"}}; MochiKit.LoggingPane.EXPORT_OK=["LoggingPane"]; MochiKit.LoggingPane.EXPORT=["createLoggingPane"]; MochiKit.LoggingPane.__new__=function(){ this.EXPORT_TAGS={":common":this.EXPORT,":all":MochiKit.Base.concat(this.EXPORT,this.EXPORT_OK)}; MochiKit.Base.nameFunctions(this); MochiKit.LoggingPane._loggingPane=null; }; MochiKit.LoggingPane.__new__(); MochiKit.Base._exportSymbols(this,MochiKit.LoggingPane); MochiKit.Base._deps("Color",["Base","DOM","Style"]); MochiKit.Color.NAME="MochiKit.Color"; MochiKit.Color.VERSION="1.4.2"; MochiKit.Color.__repr__=function(){ return "["+this.NAME+" "+this.VERSION+"]"; }; MochiKit.Color.toString=function(){ return this.__repr__(); }; MochiKit.Color.Color=function(red,_4ac,blue,_4ae){ if(typeof (_4ae)=="undefined"||_4ae===null){ _4ae=1; } this.rgb={r:red,g:_4ac,b:blue,a:_4ae}; }; MochiKit.Color.Color.prototype={__class__:MochiKit.Color.Color,colorWithAlpha:function(_4af){ var rgb=this.rgb; var m=MochiKit.Color; return m.Color.fromRGB(rgb.r,rgb.g,rgb.b,_4af); },colorWithHue:function(hue){ var hsl=this.asHSL(); hsl.h=hue; var m=MochiKit.Color; return m.Color.fromHSL(hsl); },colorWithSaturation:function(_4b5){ var hsl=this.asHSL(); hsl.s=_4b5; var m=MochiKit.Color; return m.Color.fromHSL(hsl); },colorWithLightness:function(_4b8){ var hsl=this.asHSL(); hsl.l=_4b8; var m=MochiKit.Color; return m.Color.fromHSL(hsl); },darkerColorWithLevel:function(_4bb){ var hsl=this.asHSL(); hsl.l=Math.max(hsl.l-_4bb,0); var m=MochiKit.Color; return m.Color.fromHSL(hsl); },lighterColorWithLevel:function(_4be){ var hsl=this.asHSL(); hsl.l=Math.min(hsl.l+_4be,1); var m=MochiKit.Color; return m.Color.fromHSL(hsl); },blendedColor:function(_4c1,_4c2){ if(typeof (_4c2)=="undefined"||_4c2===null){ _4c2=0.5; } var sf=1-_4c2; var s=this.rgb; var d=_4c1.rgb; var df=_4c2; return MochiKit.Color.Color.fromRGB((s.r*sf)+(d.r*df),(s.g*sf)+(d.g*df),(s.b*sf)+(d.b*df),(s.a*sf)+(d.a*df)); },compareRGB:function(_4c7){ var a=this.asRGB(); var b=_4c7.asRGB(); return MochiKit.Base.compare([a.r,a.g,a.b,a.a],[b.r,b.g,b.b,b.a]); },isLight:function(){ return this.asHSL().b>0.5; },isDark:function(){ return (!this.isLight()); },toHSLString:function(){ var c=this.asHSL(); var ccc=MochiKit.Color.clampColorComponent; var rval=this._hslString; if(!rval){ var mid=(ccc(c.h,360).toFixed(0)+","+ccc(c.s,100).toPrecision(4)+"%"+","+ccc(c.l,100).toPrecision(4)+"%"); var a=c.a; if(a>=1){ a=1; rval="hsl("+mid+")"; }else{ if(a<=0){ a=0; } rval="hsla("+mid+","+a+")"; } this._hslString=rval; } return rval; },toRGBString:function(){ var c=this.rgb; var ccc=MochiKit.Color.clampColorComponent; var rval=this._rgbString; if(!rval){ var mid=(ccc(c.r,255).toFixed(0)+","+ccc(c.g,255).toFixed(0)+","+ccc(c.b,255).toFixed(0)); if(c.a!=1){ rval="rgba("+mid+","+c.a+")"; }else{ rval="rgb("+mid+")"; } this._rgbString=rval; } return rval; },asRGB:function(){ return MochiKit.Base.clone(this.rgb); },toHexString:function(){ var m=MochiKit.Color; var c=this.rgb; var ccc=MochiKit.Color.clampColorComponent; var rval=this._hexString; if(!rval){ rval=("#"+m.toColorPart(ccc(c.r,255))+m.toColorPart(ccc(c.g,255))+m.toColorPart(ccc(c.b,255))); this._hexString=rval; } return rval; },asHSV:function(){ var hsv=this.hsv; var c=this.rgb; if(typeof (hsv)=="undefined"||hsv===null){ hsv=MochiKit.Color.rgbToHSV(this.rgb); this.hsv=hsv; } return MochiKit.Base.clone(hsv); },asHSL:function(){ var hsl=this.hsl; var c=this.rgb; if(typeof (hsl)=="undefined"||hsl===null){ hsl=MochiKit.Color.rgbToHSL(this.rgb); this.hsl=hsl; } return MochiKit.Base.clone(hsl); },toString:function(){ return this.toRGBString(); },repr:function(){ var c=this.rgb; var col=[c.r,c.g,c.b,c.a]; return this.__class__.NAME+"("+col.join(", ")+")"; }}; MochiKit.Base.update(MochiKit.Color.Color,{fromRGB:function(red,_4de,blue,_4e0){ var _4e1=MochiKit.Color.Color; if(arguments.length==1){ var rgb=red; red=rgb.r; _4de=rgb.g; blue=rgb.b; if(typeof (rgb.a)=="undefined"){ _4e0=undefined; }else{ _4e0=rgb.a; } } return new _4e1(red,_4de,blue,_4e0); },fromHSL:function(hue,_4e4,_4e5,_4e6){ var m=MochiKit.Color; return m.Color.fromRGB(m.hslToRGB.apply(m,arguments)); },fromHSV:function(hue,_4e9,_4ea,_4eb){ var m=MochiKit.Color; return m.Color.fromRGB(m.hsvToRGB.apply(m,arguments)); },fromName:function(name){ var _4ee=MochiKit.Color.Color; if(name.charAt(0)=="\""){ name=name.substr(1,name.length-2); } var _4ef=_4ee._namedColors[name.toLowerCase()]; if(typeof (_4ef)=="string"){ return _4ee.fromHexString(_4ef); }else{ if(name=="transparent"){ return _4ee.transparentColor(); } } return null; },fromString:function(_4f0){ var self=MochiKit.Color.Color; var _4f2=_4f0.substr(0,3); if(_4f2=="rgb"){ return self.fromRGBString(_4f0); }else{ if(_4f2=="hsl"){ return self.fromHSLString(_4f0); }else{ if(_4f0.charAt(0)=="#"){ return self.fromHexString(_4f0); } } } return self.fromName(_4f0); },fromHexString:function(_4f3){ if(_4f3.charAt(0)=="#"){ _4f3=_4f3.substring(1); } var _4f4=[]; var i,hex; if(_4f3.length==3){ for(i=0;i<3;i++){ hex=_4f3.substr(i,1); _4f4.push(parseInt(hex+hex,16)/255); } }else{ for(i=0;i<6;i+=2){ hex=_4f3.substr(i,2); _4f4.push(parseInt(hex,16)/255); } } var _4f7=MochiKit.Color.Color; return _4f7.fromRGB.apply(_4f7,_4f4); },_fromColorString:function(pre,_4f9,_4fa,_4fb){ if(_4fb.indexOf(pre)===0){ _4fb=_4fb.substring(_4fb.indexOf("(",3)+1,_4fb.length-1); } var _4fc=_4fb.split(/\s*,\s*/); var _4fd=[]; for(var i=0;i<_4fc.length;i++){ var c=_4fc[i]; var val; var _501=c.substring(c.length-3); if(c.charAt(c.length-1)=="%"){ val=0.01*parseFloat(c.substring(0,c.length-1)); }else{ if(_501=="deg"){ val=parseFloat(c)/360; }else{ if(_501=="rad"){ val=parseFloat(c)/(Math.PI*2); }else{ val=_4fa[i]*parseFloat(c); } } } _4fd.push(val); } return this[_4f9].apply(this,_4fd); },fromComputedStyle:function(elem,_503){ var d=MochiKit.DOM; var cls=MochiKit.Color.Color; for(elem=d.getElement(elem);elem;elem=elem.parentNode){ var _506=MochiKit.Style.getStyle.apply(d,arguments); if(!_506){ continue; } var _507=cls.fromString(_506); if(!_507){ break; } if(_507.asRGB().a>0){ return _507; } } return null; },fromBackground:function(elem){ var cls=MochiKit.Color.Color; return cls.fromComputedStyle(elem,"backgroundColor","background-color")||cls.whiteColor(); },fromText:function(elem){ var cls=MochiKit.Color.Color; return cls.fromComputedStyle(elem,"color","color")||cls.blackColor(); },namedColors:function(){ return MochiKit.Base.clone(MochiKit.Color.Color._namedColors); }}); MochiKit.Base.update(MochiKit.Color,{clampColorComponent:function(v,_50d){ v*=_50d; if(v<0){ return 0; }else{ if(v>_50d){ return _50d; }else{ return v; } } },_hslValue:function(n1,n2,hue){ if(hue>6){ hue-=6; }else{ if(hue<0){ hue+=6; } } var val; if(hue<1){ val=n1+(n2-n1)*hue; }else{ if(hue<3){ val=n2; }else{ if(hue<4){ val=n1+(n2-n1)*(4-hue); }else{ val=n1; } } } return val; },hsvToRGB:function(hue,_513,_514,_515){ if(arguments.length==1){ var hsv=hue; hue=hsv.h; _513=hsv.s; _514=hsv.v; _515=hsv.a; } var red; var _518; var blue; if(_513===0){ red=_514; _518=_514; blue=_514; }else{ var i=Math.floor(hue*6); var f=(hue*6)-i; var p=_514*(1-_513); var q=_514*(1-(_513*f)); var t=_514*(1-(_513*(1-f))); switch(i){ case 1: red=q; _518=_514; blue=p; break; case 2: red=p; _518=_514; blue=t; break; case 3: red=p; _518=q; blue=_514; break; case 4: red=t; _518=p; blue=_514; break; case 5: red=_514; _518=p; blue=q; break; case 6: case 0: red=_514; _518=t; blue=p; break; } } return {r:red,g:_518,b:blue,a:_515}; },hslToRGB:function(hue,_520,_521,_522){ if(arguments.length==1){ var hsl=hue; hue=hsl.h; _520=hsl.s; _521=hsl.l; _522=hsl.a; } var red; var _525; var blue; if(_520===0){ red=_521; _525=_521; blue=_521; }else{ var m2; if(_521<=0.5){ m2=_521*(1+_520); }else{ m2=_521+_520-(_521*_520); } var m1=(2*_521)-m2; var f=MochiKit.Color._hslValue; var h6=hue*6; red=f(m1,m2,h6+2); _525=f(m1,m2,h6); blue=f(m1,m2,h6-2); } return {r:red,g:_525,b:blue,a:_522}; },rgbToHSV:function(red,_52c,blue,_52e){ if(arguments.length==1){ var rgb=red; red=rgb.r; _52c=rgb.g; blue=rgb.b; _52e=rgb.a; } var max=Math.max(Math.max(red,_52c),blue); var min=Math.min(Math.min(red,_52c),blue); var hue; var _533; var _534=max; if(min==max){ hue=0; _533=0; }else{ var _535=(max-min); _533=_535/max; if(red==max){ hue=(_52c-blue)/_535; }else{ if(_52c==max){ hue=2+((blue-red)/_535); }else{ hue=4+((red-_52c)/_535); } } hue/=6; if(hue<0){ hue+=1; } if(hue>1){ hue-=1; } } return {h:hue,s:_533,v:_534,a:_52e}; },rgbToHSL:function(red,_537,blue,_539){ if(arguments.length==1){ var rgb=red; red=rgb.r; _537=rgb.g; blue=rgb.b; _539=rgb.a; } var max=Math.max(red,Math.max(_537,blue)); var min=Math.min(red,Math.min(_537,blue)); var hue; var _53e; var _53f=(max+min)/2; var _540=max-min; if(_540===0){ hue=0; _53e=0; }else{ if(_53f<=0.5){ _53e=_540/(max+min); }else{ _53e=_540/(2-max-min); } if(red==max){ hue=(_537-blue)/_540; }else{ if(_537==max){ hue=2+((blue-red)/_540); }else{ hue=4+((red-_537)/_540); } } hue/=6; if(hue<0){ hue+=1; } if(hue>1){ hue-=1; } } return {h:hue,s:_53e,l:_53f,a:_539}; },toColorPart:function(num){ num=Math.round(num); var _542=num.toString(16); if(num<16){ return "0"+_542; } return _542; },__new__:function(){ var m=MochiKit.Base; this.Color.fromRGBString=m.bind(this.Color._fromColorString,this.Color,"rgb","fromRGB",[1/255,1/255,1/255,1]); this.Color.fromHSLString=m.bind(this.Color._fromColorString,this.Color,"hsl","fromHSL",[1/360,0.01,0.01,1]); var _544=1/3; var _545={black:[0,0,0],blue:[0,0,1],brown:[0.6,0.4,0.2],cyan:[0,1,1],darkGray:[_544,_544,_544],gray:[0.5,0.5,0.5],green:[0,1,0],lightGray:[2*_544,2*_544,2*_544],magenta:[1,0,1],orange:[1,0.5,0],purple:[0.5,0,0.5],red:[1,0,0],transparent:[0,0,0,0],white:[1,1,1],yellow:[1,1,0]}; var _546=function(name,r,g,b,a){ var rval=this.fromRGB(r,g,b,a); this[name]=function(){ return rval; }; return rval; }; for(var k in _545){ var name=k+"Color"; var _54f=m.concat([_546,this.Color,name],_545[k]); this.Color[name]=m.bind.apply(null,_54f); } var _550=function(){ for(var i=0;i1){ var src=MochiKit.DOM.getElement(arguments[0]); var sig=arguments[1]; var obj=arguments[2]; var func=arguments[3]; for(var i=_592.length-1;i>=0;i--){ var o=_592[i]; if(o.source===src&&o.signal===sig&&o.objOrFunc===obj&&o.funcOrStr===func){ self._disconnect(o); if(!self._lock){ _592.splice(i,1); }else{ self._dirty=true; } return true; } } }else{ var idx=m.findIdentical(_592,_590); if(idx>=0){ self._disconnect(_590); if(!self._lock){ _592.splice(idx,1); }else{ self._dirty=true; } return true; } } return false; },disconnectAllTo:function(_59b,_59c){ var self=MochiKit.Signal; var _59e=self._observers; var _59f=self._disconnect; var _5a0=self._lock; var _5a1=self._dirty; if(typeof (_59c)==="undefined"){ _59c=null; } for(var i=_59e.length-1;i>=0;i--){ var _5a3=_59e[i]; if(_5a3.objOrFunc===_59b&&(_59c===null||_5a3.funcOrStr===_59c)){ _59f(_5a3); if(_5a0){ _5a1=true; }else{ _59e.splice(i,1); } } } self._dirty=_5a1; },disconnectAll:function(src,sig){ src=MochiKit.DOM.getElement(src); var m=MochiKit.Base; var _5a7=m.flattenArguments(m.extend(null,arguments,1)); var self=MochiKit.Signal; var _5a9=self._disconnect; var _5aa=self._observers; var i,_5ac; var _5ad=self._lock; var _5ae=self._dirty; if(_5a7.length===0){ for(i=_5aa.length-1;i>=0;i--){ _5ac=_5aa[i]; if(_5ac.source===src){ _5a9(_5ac); if(!_5ad){ _5aa.splice(i,1); }else{ _5ae=true; } } } }else{ var sigs={}; for(i=0;i<_5a7.length;i++){ sigs[_5a7[i]]=true; } for(i=_5aa.length-1;i>=0;i--){ _5ac=_5aa[i]; if(_5ac.source===src&&_5ac.signal in sigs){ _5a9(_5ac); if(!_5ad){ _5aa.splice(i,1); }else{ _5ae=true; } } } } self._dirty=_5ae; },signal:function(src,sig){ var self=MochiKit.Signal; var _5b3=self._observers; src=MochiKit.DOM.getElement(src); var args=MochiKit.Base.extend(null,arguments,2); var _5b5=[]; self._lock=true; for(var i=0;i<_5b3.length;i++){ var _5b7=_5b3[i]; if(_5b7.source===src&&_5b7.signal===sig&&_5b7.connected){ try{ _5b7.listener.apply(src,args); } catch(e){ _5b5.push(e); } } } self._lock=false; if(self._dirty){ self._dirty=false; for(var i=_5b3.length-1;i>=0;i--){ if(!_5b3[i].connected){ _5b3.splice(i,1); } } } if(_5b5.length==1){ throw _5b5[0]; }else{ if(_5b5.length>1){ var e=new Error("Multiple errors thrown in handling 'sig', see errors property"); e.errors=_5b5; throw e; } } }}); MochiKit.Signal.EXPORT_OK=[]; MochiKit.Signal.EXPORT=["connect","disconnect","signal","disconnectAll","disconnectAllTo"]; MochiKit.Signal.__new__=function(win){ var m=MochiKit.Base; this._document=document; this._window=win; this._lock=false; this._dirty=false; try{ this.connect(window,"onunload",this._unloadCache); } catch(e){ } this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; m.nameFunctions(this); }; MochiKit.Signal.__new__(this); if(MochiKit.__export__){ connect=MochiKit.Signal.connect; disconnect=MochiKit.Signal.disconnect; disconnectAll=MochiKit.Signal.disconnectAll; signal=MochiKit.Signal.signal; } MochiKit.Base._exportSymbols(this,MochiKit.Signal); MochiKit.Base._deps("Position",["Base","DOM","Style"]); MochiKit.Position.NAME="MochiKit.Position"; MochiKit.Position.VERSION="1.4.2"; MochiKit.Position.__repr__=function(){ return "["+this.NAME+" "+this.VERSION+"]"; }; MochiKit.Position.toString=function(){ return this.__repr__(); }; MochiKit.Position.EXPORT_OK=[]; MochiKit.Position.EXPORT=[]; MochiKit.Base.update(MochiKit.Position,{includeScrollOffsets:false,prepare:function(){ var _5bb=window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0; var _5bc=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0; this.windowOffset=new MochiKit.Style.Coordinates(_5bb,_5bc); },cumulativeOffset:function(_5bd){ var _5be=0; var _5bf=0; do{ _5be+=_5bd.offsetTop||0; _5bf+=_5bd.offsetLeft||0; _5bd=_5bd.offsetParent; }while(_5bd); return new MochiKit.Style.Coordinates(_5bf,_5be); },realOffset:function(_5c0){ var _5c1=0; var _5c2=0; do{ _5c1+=_5c0.scrollTop||0; _5c2+=_5c0.scrollLeft||0; _5c0=_5c0.parentNode; }while(_5c0); return new MochiKit.Style.Coordinates(_5c2,_5c1); },within:function(_5c3,x,y){ if(this.includeScrollOffsets){ return this.withinIncludingScrolloffsets(_5c3,x,y); } this.xcomp=x; this.ycomp=y; this.offset=this.cumulativeOffset(_5c3); if(_5c3.style.position=="fixed"){ this.offset.x+=this.windowOffset.x; this.offset.y+=this.windowOffset.y; } return (y>=this.offset.y&&y=this.offset.x&&x=this.offset.y&&this.ycomp=this.offset.x&&this.xcomp"+el.innerHTML+""; },_roundTopCorners:function(el,_5f5,_5f6){ var _5f7=this._createCorner(_5f6); for(var i=0;i=0;i--){ _5fc.appendChild(this._createCornerSlice(_5fa,_5fb,i,"bottom")); } el.style.paddingBottom=0; el.appendChild(_5fc); },_createCorner:function(_5fe){ var dom=MochiKit.DOM; return dom.DIV({style:{backgroundColor:_5fe.toString()}}); },_createCornerSlice:function(_600,_601,n,_603){ var _604=MochiKit.DOM.SPAN(); var _605=_604.style; _605.backgroundColor=_600.toString(); _605.display="block"; _605.height="1px"; _605.overflow="hidden"; _605.fontSize="1px"; var _606=this._borderColor(_600,_601); if(this.options.border&&n===0){ _605.borderTopStyle="solid"; _605.borderTopWidth="1px"; _605.borderLeftWidth="0px"; _605.borderRightWidth="0px"; _605.borderBottomWidth="0px"; _605.height="0px"; _605.borderColor=_606.toString(); }else{ if(_606){ _605.borderColor=_606.toString(); _605.borderStyle="solid"; _605.borderWidth="0px 1px"; } } if(!this.options.compact&&(n==(this.options.numSlices-1))){ _605.height="2px"; } this._setMargin(_604,n,_603); this._setBorder(_604,n,_603); return _604; },_setOptions:function(_607){ this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:true,border:false,compact:false,__unstable__wrapElement:false}; MochiKit.Base.update(this.options,_607); this.options.numSlices=(this.options.compact?2:4); },_whichSideTop:function(){ var _608=this.options.corners; if(this._hasString(_608,"all","top")){ return ""; } var _609=(_608.indexOf("tl")!=-1); var _60a=(_608.indexOf("tr")!=-1); if(_609&&_60a){ return ""; } if(_609){ return "left"; } if(_60a){ return "right"; } return ""; },_whichSideBottom:function(){ var _60b=this.options.corners; if(this._hasString(_60b,"all","bottom")){ return ""; } var _60c=(_60b.indexOf("bl")!=-1); var _60d=(_60b.indexOf("br")!=-1); if(_60c&&_60d){ return ""; } if(_60c){ return "left"; } if(_60d){ return "right"; } return ""; },_borderColor:function(_60e,_60f){ if(_60e=="transparent"){ return _60f; }else{ if(this.options.border){ return this.options.border; }else{ if(this.options.blend){ return _60f.blendedColor(_60e); } } } return ""; },_setMargin:function(el,n,_612){ var _613=this._marginSize(n)+"px"; var _614=(_612=="top"?this._whichSideTop():this._whichSideBottom()); var _615=el.style; if(_614=="left"){ _615.marginLeft=_613; _615.marginRight="0px"; }else{ if(_614=="right"){ _615.marginRight=_613; _615.marginLeft="0px"; }else{ _615.marginLeft=_613; _615.marginRight=_613; } } },_setBorder:function(el,n,_618){ var _619=this._borderSize(n)+"px"; var _61a=(_618=="top"?this._whichSideTop():this._whichSideBottom()); var _61b=el.style; if(_61a=="left"){ _61b.borderLeftWidth=_619; _61b.borderRightWidth="0px"; }else{ if(_61a=="right"){ _61b.borderRightWidth=_619; _61b.borderLeftWidth="0px"; }else{ _61b.borderLeftWidth=_619; _61b.borderRightWidth=_619; } } },_marginSize:function(n){ if(this.isTransparent){ return 0; } var o=this.options; if(o.compact&&o.blend){ var _61e=[1,0]; return _61e[n]; }else{ if(o.compact){ var _61f=[2,1]; return _61f[n]; }else{ if(o.blend){ var _620=[3,2,1,0]; return _620[n]; }else{ var _621=[5,3,2,1]; return _621[n]; } } } },_borderSize:function(n){ var o=this.options; var _624; if(o.compact&&(o.blend||this.isTransparent)){ return 1; }else{ if(o.compact){ _624=[1,0]; }else{ if(o.blend){ _624=[2,1,1,1]; }else{ if(o.border){ _624=[0,2,0,0]; }else{ if(this.isTransparent){ _624=[5,3,2,1]; }else{ return 0; } } } } } return _624[n]; },_hasString:function(str){ for(var i=1;i=(_651||i)){ _651=i; } },this.effects); _64d=_651||_64d; break; case "break": ma(function(e){ e.finalize(); },this.effects); break; } _64c.startOn+=_64d; _64c.finishOn+=_64d; if(!_64c.options.queue.limit||this.effects.length<_64c.options.queue.limit){ this.effects.push(_64c); } if(!this.interval){ this.interval=this.startLoop(MochiKit.Base.bind(this.loop,this),40); } },startLoop:function(func,_656){ return setInterval(func,_656); },remove:function(_657){ this.effects=MochiKit.Base.filter(function(e){ return e!=_657; },this.effects); if(!this.effects.length){ this.stopLoop(this.interval); this.interval=null; } },stopLoop:function(_659){ clearInterval(_659); },loop:function(){ var _65a=new Date().getTime(); MochiKit.Base.map(function(_65b){ _65b.loop(_65a); },this.effects); }}); MochiKit.Visual.Queues={instances:{},get:function(_65c){ if(typeof (_65c)!="string"){ return _65c; } if(!this.instances[_65c]){ this.instances[_65c]=new MochiKit.Visual.ScopedQueue(); } return this.instances[_65c]; }}; MochiKit.Visual.Queue=MochiKit.Visual.Queues.get("global"); MochiKit.Visual.DefaultOptions={transition:MochiKit.Visual.Transitions.sinoidal,duration:1,fps:25,sync:false,from:0,to:1,delay:0,queue:"parallel"}; MochiKit.Visual.Base=function(){ }; MochiKit.Visual.Base.prototype={__class__:MochiKit.Visual.Base,start:function(_65d){ var v=MochiKit.Visual; this.options=MochiKit.Base.setdefault(_65d,v.DefaultOptions); this.currentFrame=0; this.state="idle"; this.startOn=this.options.delay*1000; this.finishOn=this.startOn+(this.options.duration*1000); this.event("beforeStart"); if(!this.options.sync){ v.Queues.get(typeof (this.options.queue)=="string"?"global":this.options.queue.scope).add(this); } },loop:function(_65f){ if(_65f>=this.startOn){ if(_65f>=this.finishOn){ return this.finalize(); } var pos=(_65f-this.startOn)/(this.finishOn-this.startOn); var _661=Math.round(pos*this.options.fps*this.options.duration); if(_661>this.currentFrame){ this.render(pos); this.currentFrame=_661; } } },render:function(pos){ if(this.state=="idle"){ this.state="running"; this.event("beforeSetup"); this.setup(); this.event("afterSetup"); } if(this.state=="running"){ if(this.options.transition){ pos=this.options.transition(pos); } pos*=(this.options.to-this.options.from); pos+=this.options.from; this.event("beforeUpdate"); this.update(pos); this.event("afterUpdate"); } },cancel:function(){ if(!this.options.sync){ MochiKit.Visual.Queues.get(typeof (this.options.queue)=="string"?"global":this.options.queue.scope).remove(this); } this.state="finished"; },finalize:function(){ this.render(1); this.cancel(); this.event("beforeFinish"); this.finish(); this.event("afterFinish"); },setup:function(){ },finish:function(){ },update:function(_663){ },event:function(_664){ if(this.options[_664+"Internal"]){ this.options[_664+"Internal"](this); } if(this.options[_664]){ this.options[_664](this); } },repr:function(){ return "["+this.__class__.NAME+", options:"+MochiKit.Base.repr(this.options)+"]"; }}; MochiKit.Visual.Parallel=function(_665,_666){ var cls=arguments.callee; if(!(this instanceof cls)){ return new cls(_665,_666); } this.__init__(_665,_666); }; MochiKit.Visual.Parallel.prototype=new MochiKit.Visual.Base(); MochiKit.Base.update(MochiKit.Visual.Parallel.prototype,{__class__:MochiKit.Visual.Parallel,__init__:function(_668,_669){ this.effects=_668||[]; this.start(_669); },update:function(_66a){ MochiKit.Base.map(function(_66b){ _66b.render(_66a); },this.effects); },finish:function(){ MochiKit.Base.map(function(_66c){ _66c.finalize(); },this.effects); }}); MochiKit.Visual.Sequence=function(_66d,_66e){ var cls=arguments.callee; if(!(this instanceof cls)){ return new cls(_66d,_66e); } this.__init__(_66d,_66e); }; MochiKit.Visual.Sequence.prototype=new MochiKit.Visual.Base(); MochiKit.Base.update(MochiKit.Visual.Sequence.prototype,{__class__:MochiKit.Visual.Sequence,__init__:function(_670,_671){ var defs={transition:MochiKit.Visual.Transitions.linear,duration:0}; this.effects=_670||[]; MochiKit.Base.map(function(_673){ defs.duration+=_673.options.duration; },this.effects); MochiKit.Base.setdefault(_671,defs); this.start(_671); },update:function(_674){ var time=_674*this.options.duration; for(var i=0;i0){ this.fontSize=parseFloat(_694); this.fontSizeType=_695; } },this),["em","px","%"]); this.factor=(this.options.scaleTo-this.options.scaleFrom)/100; if(/^content/.test(this.options.scaleMode)){ this.dims=[this.element.scrollHeight,this.element.scrollWidth]; }else{ if(this.options.scaleMode=="box"){ this.dims=[this.element.offsetHeight,this.element.offsetWidth]; }else{ this.dims=[this.options.scaleMode.originalHeight,this.options.scaleMode.originalWidth]; } } },update:function(_696){ var _697=(this.options.scaleFrom/100)+(this.factor*_696); if(this.options.scaleContent&&this.fontSize){ MochiKit.Style.setStyle(this.element,{fontSize:this.fontSize*_697+this.fontSizeType}); } this.setDimensions(this.dims[0]*_697,this.dims[1]*_697); },finish:function(){ if(this.restoreAfterFinish){ MochiKit.Style.setStyle(this.element,this.originalStyle); } },setDimensions:function(_698,_699){ var d={}; var r=Math.round; if(/MSIE/.test(navigator.userAgent)){ r=Math.ceil; } if(this.options.scaleX){ d.width=r(_699)+"px"; } if(this.options.scaleY){ d.height=r(_698)+"px"; } if(this.options.scaleFromCenter){ var topd=(_698-this.dims[0])/2; var _69d=(_699-this.dims[1])/2; if(this.elementPositioning=="absolute"){ if(this.options.scaleY){ d.top=this.originalTop-topd+"px"; } if(this.options.scaleX){ d.left=this.originalLeft-_69d+"px"; } }else{ if(this.options.scaleY){ d.top=-topd+"px"; } if(this.options.scaleX){ d.left=-_69d+"px"; } } } MochiKit.Style.setStyle(this.element,d); }}); MochiKit.Visual.Highlight=function(_69e,_69f){ var cls=arguments.callee; if(!(this instanceof cls)){ return new cls(_69e,_69f); } this.__init__(_69e,_69f); }; MochiKit.Visual.Highlight.prototype=new MochiKit.Visual.Base(); MochiKit.Base.update(MochiKit.Visual.Highlight.prototype,{__class__:MochiKit.Visual.Highlight,__init__:function(_6a1,_6a2){ this.element=MochiKit.DOM.getElement(_6a1); _6a2=MochiKit.Base.update({startcolor:"#ffff99"},_6a2); this.start(_6a2); },setup:function(){ var b=MochiKit.Base; var s=MochiKit.Style; if(s.getStyle(this.element,"display")=="none"){ this.cancel(); return; } this.oldStyle={backgroundImage:s.getStyle(this.element,"background-image")}; s.setStyle(this.element,{backgroundImage:"none"}); if(!this.options.endcolor){ this.options.endcolor=MochiKit.Color.Color.fromBackground(this.element).toHexString(); } if(b.isUndefinedOrNull(this.options.restorecolor)){ this.options.restorecolor=s.getStyle(this.element,"background-color"); } this._base=b.map(b.bind(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16); },this),[0,1,2]); this._delta=b.map(b.bind(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i]; },this),[0,1,2]); },update:function(_6a7){ var m="#"; MochiKit.Base.map(MochiKit.Base.bind(function(i){ m+=MochiKit.Color.toColorPart(Math.round(this._base[i]+this._delta[i]*_6a7)); },this),[0,1,2]); MochiKit.Style.setStyle(this.element,{backgroundColor:m}); },finish:function(){ MochiKit.Style.setStyle(this.element,MochiKit.Base.update(this.oldStyle,{backgroundColor:this.options.restorecolor})); }}); MochiKit.Visual.ScrollTo=function(_6aa,_6ab){ var cls=arguments.callee; if(!(this instanceof cls)){ return new cls(_6aa,_6ab); } this.__init__(_6aa,_6ab); }; MochiKit.Visual.ScrollTo.prototype=new MochiKit.Visual.Base(); MochiKit.Base.update(MochiKit.Visual.ScrollTo.prototype,{__class__:MochiKit.Visual.ScrollTo,__init__:function(_6ad,_6ae){ this.element=MochiKit.DOM.getElement(_6ad); this.start(_6ae); },setup:function(){ var p=MochiKit.Position; p.prepare(); var _6b0=p.cumulativeOffset(this.element); if(this.options.offset){ _6b0.y+=this.options.offset; } var max; if(window.innerHeight){ max=window.innerHeight-window.height; }else{ if(document.documentElement&&document.documentElement.clientHeight){ max=document.documentElement.clientHeight-document.body.scrollHeight; }else{ if(document.body){ max=document.body.clientHeight-document.body.scrollHeight; } } } this.scrollStart=p.windowOffset.y; this.delta=(_6b0.y>max?max:_6b0.y)-this.scrollStart; },update:function(_6b2){ var p=MochiKit.Position; p.prepare(); window.scrollTo(p.windowOffset.x,this.scrollStart+(_6b2*this.delta)); }}); MochiKit.Visual.CSS_LENGTH=/^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; MochiKit.Visual.Morph=function(_6b4,_6b5){ var cls=arguments.callee; if(!(this instanceof cls)){ return new cls(_6b4,_6b5); } this.__init__(_6b4,_6b5); }; MochiKit.Visual.Morph.prototype=new MochiKit.Visual.Base(); MochiKit.Base.update(MochiKit.Visual.Morph.prototype,{__class__:MochiKit.Visual.Morph,__init__:function(_6b7,_6b8){ this.element=MochiKit.DOM.getElement(_6b7); this.start(_6b8); },setup:function(){ var b=MochiKit.Base; var _6ba=this.options.style; this.styleStart={}; this.styleEnd={}; this.units={}; var _6bb,unit; for(var s in _6ba){ _6bb=_6ba[s]; s=b.camelize(s); if(MochiKit.Visual.CSS_LENGTH.test(_6bb)){ var _6be=_6bb.match(/^([\+\-]?[0-9\.]+)(.*)$/); _6bb=parseFloat(_6be[1]); unit=(_6be.length==3)?_6be[2]:null; this.styleEnd[s]=_6bb; this.units[s]=unit; _6bb=MochiKit.Style.getStyle(this.element,s); _6be=_6bb.match(/^([\+\-]?[0-9\.]+)(.*)$/); _6bb=parseFloat(_6be[1]); this.styleStart[s]=_6bb; }else{ if(/[Cc]olor$/.test(s)){ var c=MochiKit.Color.Color; _6bb=c.fromString(_6bb); if(_6bb){ this.units[s]="color"; this.styleEnd[s]=_6bb.toHexString(); _6bb=MochiKit.Style.getStyle(this.element,s); this.styleStart[s]=c.fromString(_6bb).toHexString(); this.styleStart[s]=b.map(b.bind(function(i){ return parseInt(this.styleStart[s].slice(i*2+1,i*2+3),16); },this),[0,1,2]); this.styleEnd[s]=b.map(b.bind(function(i){ return parseInt(this.styleEnd[s].slice(i*2+1,i*2+3),16); },this),[0,1,2]); } }else{ this.element.style[s]=_6bb; } } } },update:function(_6c2){ var _6c3; for(var s in this.styleStart){ if(this.units[s]=="color"){ var m="#"; var _6c6=this.styleStart[s]; var end=this.styleEnd[s]; MochiKit.Base.map(MochiKit.Base.bind(function(i){ m+=MochiKit.Color.toColorPart(Math.round(_6c6[i]+(end[i]-_6c6[i])*_6c2)); },this),[0,1,2]); this.element.style[s]=m; }else{ _6c3=this.styleStart[s]+Math.round((this.styleEnd[s]-this.styleStart[s])*_6c2*1000)/1000+this.units[s]; this.element.style[s]=_6c3; } } }}); MochiKit.Visual.fade=function(_6c9,_6ca){ var s=MochiKit.Style; var _6cc=s.getStyle(_6c9,"opacity"); _6ca=MochiKit.Base.update({from:s.getStyle(_6c9,"opacity")||1,to:0,afterFinishInternal:function(_6cd){ if(_6cd.options.to!==0){ return; } s.hideElement(_6cd.element); s.setStyle(_6cd.element,{"opacity":_6cc}); }},_6ca); return new MochiKit.Visual.Opacity(_6c9,_6ca); }; MochiKit.Visual.appear=function(_6ce,_6cf){ var s=MochiKit.Style; var v=MochiKit.Visual; _6cf=MochiKit.Base.update({from:(s.getStyle(_6ce,"display")=="none"?0:s.getStyle(_6ce,"opacity")||0),to:1,afterFinishInternal:function(_6d2){ v.forceRerendering(_6d2.element); },beforeSetupInternal:function(_6d3){ s.setStyle(_6d3.element,{"opacity":_6d3.options.from}); s.showElement(_6d3.element); }},_6cf); return new v.Opacity(_6ce,_6cf); }; MochiKit.Visual.puff=function(_6d4,_6d5){ var s=MochiKit.Style; var v=MochiKit.Visual; _6d4=MochiKit.DOM.getElement(_6d4); var _6d8=MochiKit.Style.getElementDimensions(_6d4,true); var _6d9={position:s.getStyle(_6d4,"position"),top:_6d4.style.top,left:_6d4.style.left,width:_6d4.style.width,height:_6d4.style.height,opacity:s.getStyle(_6d4,"opacity")}; _6d5=MochiKit.Base.update({beforeSetupInternal:function(_6da){ MochiKit.Position.absolutize(_6da.effects[0].element); },afterFinishInternal:function(_6db){ s.hideElement(_6db.effects[0].element); s.setStyle(_6db.effects[0].element,_6d9); },scaleContent:true,scaleFromCenter:true},_6d5); return new v.Parallel([new v.Scale(_6d4,200,{sync:true,scaleFromCenter:_6d5.scaleFromCenter,scaleMode:{originalHeight:_6d8.h,originalWidth:_6d8.w},scaleContent:_6d5.scaleContent,restoreAfterFinish:true}),new v.Opacity(_6d4,{sync:true,to:0})],_6d5); }; MochiKit.Visual.blindUp=function(_6dc,_6dd){ var d=MochiKit.DOM; var s=MochiKit.Style; _6dc=d.getElement(_6dc); var _6e0=s.getElementDimensions(_6dc,true); var _6e1=s.makeClipping(_6dc); _6dd=MochiKit.Base.update({scaleContent:false,scaleX:false,scaleMode:{originalHeight:_6e0.h,originalWidth:_6e0.w},restoreAfterFinish:true,afterFinishInternal:function(_6e2){ s.hideElement(_6e2.element); s.undoClipping(_6e2.element,_6e1); }},_6dd); return new MochiKit.Visual.Scale(_6dc,0,_6dd); }; MochiKit.Visual.blindDown=function(_6e3,_6e4){ var d=MochiKit.DOM; var s=MochiKit.Style; _6e3=d.getElement(_6e3); var _6e7=s.getElementDimensions(_6e3,true); var _6e8; _6e4=MochiKit.Base.update({scaleContent:false,scaleX:false,scaleFrom:0,scaleMode:{originalHeight:_6e7.h,originalWidth:_6e7.w},restoreAfterFinish:true,afterSetupInternal:function(_6e9){ _6e8=s.makeClipping(_6e9.element); s.setStyle(_6e9.element,{height:"0px"}); s.showElement(_6e9.element); },afterFinishInternal:function(_6ea){ s.undoClipping(_6ea.element,_6e8); }},_6e4); return new MochiKit.Visual.Scale(_6e3,100,_6e4); }; MochiKit.Visual.switchOff=function(_6eb,_6ec){ var d=MochiKit.DOM; var s=MochiKit.Style; _6eb=d.getElement(_6eb); var _6ef=s.getElementDimensions(_6eb,true); var _6f0=s.getStyle(_6eb,"opacity"); var _6f1; _6ec=MochiKit.Base.update({duration:0.7,restoreAfterFinish:true,beforeSetupInternal:function(_6f2){ s.makePositioned(_6eb); _6f1=s.makeClipping(_6eb); },afterFinishInternal:function(_6f3){ s.hideElement(_6eb); s.undoClipping(_6eb,_6f1); s.undoPositioned(_6eb); s.setStyle(_6eb,{"opacity":_6f0}); }},_6ec); var v=MochiKit.Visual; return new v.Sequence([new v.appear(_6eb,{sync:true,duration:0.57*_6ec.duration,from:0,transition:v.Transitions.flicker}),new v.Scale(_6eb,1,{sync:true,duration:0.43*_6ec.duration,scaleFromCenter:true,scaleX:false,scaleMode:{originalHeight:_6ef.h,originalWidth:_6ef.w},scaleContent:false,restoreAfterFinish:true})],_6ec); }; MochiKit.Visual.dropOut=function(_6f5,_6f6){ var d=MochiKit.DOM; var s=MochiKit.Style; _6f5=d.getElement(_6f5); var _6f9={top:s.getStyle(_6f5,"top"),left:s.getStyle(_6f5,"left"),opacity:s.getStyle(_6f5,"opacity")}; _6f6=MochiKit.Base.update({duration:0.5,distance:100,beforeSetupInternal:function(_6fa){ s.makePositioned(_6fa.effects[0].element); },afterFinishInternal:function(_6fb){ s.hideElement(_6fb.effects[0].element); s.undoPositioned(_6fb.effects[0].element); s.setStyle(_6fb.effects[0].element,_6f9); }},_6f6); var v=MochiKit.Visual; return new v.Parallel([new v.Move(_6f5,{x:0,y:_6f6.distance,sync:true}),new v.Opacity(_6f5,{sync:true,to:0})],_6f6); }; MochiKit.Visual.shake=function(_6fd,_6fe){ var d=MochiKit.DOM; var v=MochiKit.Visual; var s=MochiKit.Style; _6fd=d.getElement(_6fd); var _702={top:s.getStyle(_6fd,"top"),left:s.getStyle(_6fd,"left")}; _6fe=MochiKit.Base.update({duration:0.5,afterFinishInternal:function(_703){ s.undoPositioned(_6fd); s.setStyle(_6fd,_702); }},_6fe); return new v.Sequence([new v.Move(_6fd,{sync:true,duration:0.1*_6fe.duration,x:20,y:0}),new v.Move(_6fd,{sync:true,duration:0.2*_6fe.duration,x:-40,y:0}),new v.Move(_6fd,{sync:true,duration:0.2*_6fe.duration,x:40,y:0}),new v.Move(_6fd,{sync:true,duration:0.2*_6fe.duration,x:-40,y:0}),new v.Move(_6fd,{sync:true,duration:0.2*_6fe.duration,x:40,y:0}),new v.Move(_6fd,{sync:true,duration:0.1*_6fe.duration,x:-20,y:0})],_6fe); }; MochiKit.Visual.slideDown=function(_704,_705){ var d=MochiKit.DOM; var b=MochiKit.Base; var s=MochiKit.Style; _704=d.getElement(_704); if(!_704.firstChild){ throw new Error("MochiKit.Visual.slideDown must be used on a element with a child"); } d.removeEmptyTextNodes(_704); var _709=s.getStyle(_704.firstChild,"bottom")||0; var _70a=s.getElementDimensions(_704,true); var _70b; _705=b.update({scaleContent:false,scaleX:false,scaleFrom:0,scaleMode:{originalHeight:_70a.h,originalWidth:_70a.w},restoreAfterFinish:true,afterSetupInternal:function(_70c){ s.makePositioned(_70c.element); s.makePositioned(_70c.element.firstChild); if(/Opera/.test(navigator.userAgent)){ s.setStyle(_70c.element,{top:""}); } _70b=s.makeClipping(_70c.element); s.setStyle(_70c.element,{height:"0px"}); s.showElement(_70c.element); },afterUpdateInternal:function(_70d){ var _70e=s.getElementDimensions(_70d.element,true); s.setStyle(_70d.element.firstChild,{bottom:(_70d.dims[0]-_70e.h)+"px"}); },afterFinishInternal:function(_70f){ s.undoClipping(_70f.element,_70b); if(/MSIE/.test(navigator.userAgent)){ s.undoPositioned(_70f.element); s.undoPositioned(_70f.element.firstChild); }else{ s.undoPositioned(_70f.element.firstChild); s.undoPositioned(_70f.element); } s.setStyle(_70f.element.firstChild,{bottom:_709}); }},_705); return new MochiKit.Visual.Scale(_704,100,_705); }; MochiKit.Visual.slideUp=function(_710,_711){ var d=MochiKit.DOM; var b=MochiKit.Base; var s=MochiKit.Style; _710=d.getElement(_710); if(!_710.firstChild){ throw new Error("MochiKit.Visual.slideUp must be used on a element with a child"); } d.removeEmptyTextNodes(_710); var _715=s.getStyle(_710.firstChild,"bottom"); var _716=s.getElementDimensions(_710,true); var _717; _711=b.update({scaleContent:false,scaleX:false,scaleMode:{originalHeight:_716.h,originalWidth:_716.w},scaleFrom:100,restoreAfterFinish:true,beforeStartInternal:function(_718){ s.makePositioned(_718.element); s.makePositioned(_718.element.firstChild); if(/Opera/.test(navigator.userAgent)){ s.setStyle(_718.element,{top:""}); } _717=s.makeClipping(_718.element); s.showElement(_718.element); },afterUpdateInternal:function(_719){ var _71a=s.getElementDimensions(_719.element,true); s.setStyle(_719.element.firstChild,{bottom:(_719.dims[0]-_71a.h)+"px"}); },afterFinishInternal:function(_71b){ s.hideElement(_71b.element); s.undoClipping(_71b.element,_717); s.undoPositioned(_71b.element.firstChild); s.undoPositioned(_71b.element); s.setStyle(_71b.element.firstChild,{bottom:_715}); }},_711); return new MochiKit.Visual.Scale(_710,0,_711); }; MochiKit.Visual.squish=function(_71c,_71d){ var d=MochiKit.DOM; var b=MochiKit.Base; var s=MochiKit.Style; var _721=s.getElementDimensions(_71c,true); var _722; _71d=b.update({restoreAfterFinish:true,scaleMode:{originalHeight:_721.w,originalWidth:_721.h},beforeSetupInternal:function(_723){ _722=s.makeClipping(_723.element); },afterFinishInternal:function(_724){ s.hideElement(_724.element); s.undoClipping(_724.element,_722); }},_71d); return new MochiKit.Visual.Scale(_71c,/Opera/.test(navigator.userAgent)?1:0,_71d); }; MochiKit.Visual.grow=function(_725,_726){ var d=MochiKit.DOM; var v=MochiKit.Visual; var s=MochiKit.Style; _725=d.getElement(_725); _726=MochiKit.Base.update({direction:"center",moveTransition:v.Transitions.sinoidal,scaleTransition:v.Transitions.sinoidal,opacityTransition:v.Transitions.full,scaleContent:true,scaleFromCenter:false},_726); var _72a={top:_725.style.top,left:_725.style.left,height:_725.style.height,width:_725.style.width,opacity:s.getStyle(_725,"opacity")}; var dims=s.getElementDimensions(_725,true); var _72c,_72d; var _72e,_72f; switch(_726.direction){ case "top-left": _72c=_72d=_72e=_72f=0; break; case "top-right": _72c=dims.w; _72d=_72f=0; _72e=-dims.w; break; case "bottom-left": _72c=_72e=0; _72d=dims.h; _72f=-dims.h; break; case "bottom-right": _72c=dims.w; _72d=dims.h; _72e=-dims.w; _72f=-dims.h; break; case "center": _72c=dims.w/2; _72d=dims.h/2; _72e=-dims.w/2; _72f=-dims.h/2; break; } var _730=MochiKit.Base.update({beforeSetupInternal:function(_731){ s.setStyle(_731.effects[0].element,{height:"0px"}); s.showElement(_731.effects[0].element); },afterFinishInternal:function(_732){ s.undoClipping(_732.effects[0].element); s.undoPositioned(_732.effects[0].element); s.setStyle(_732.effects[0].element,_72a); }},_726); return new v.Move(_725,{x:_72c,y:_72d,duration:0.01,beforeSetupInternal:function(_733){ s.hideElement(_733.element); s.makeClipping(_733.element); s.makePositioned(_733.element); },afterFinishInternal:function(_734){ new v.Parallel([new v.Opacity(_734.element,{sync:true,to:1,from:0,transition:_726.opacityTransition}),new v.Move(_734.element,{x:_72e,y:_72f,sync:true,transition:_726.moveTransition}),new v.Scale(_734.element,100,{scaleMode:{originalHeight:dims.h,originalWidth:dims.w},sync:true,scaleFrom:/Opera/.test(navigator.userAgent)?1:0,transition:_726.scaleTransition,scaleContent:_726.scaleContent,scaleFromCenter:_726.scaleFromCenter,restoreAfterFinish:true})],_730); }}); }; MochiKit.Visual.shrink=function(_735,_736){ var d=MochiKit.DOM; var v=MochiKit.Visual; var s=MochiKit.Style; _735=d.getElement(_735); _736=MochiKit.Base.update({direction:"center",moveTransition:v.Transitions.sinoidal,scaleTransition:v.Transitions.sinoidal,opacityTransition:v.Transitions.none,scaleContent:true,scaleFromCenter:false},_736); var _73a={top:_735.style.top,left:_735.style.left,height:_735.style.height,width:_735.style.width,opacity:s.getStyle(_735,"opacity")}; var dims=s.getElementDimensions(_735,true); var _73c,_73d; switch(_736.direction){ case "top-left": _73c=_73d=0; break; case "top-right": _73c=dims.w; _73d=0; break; case "bottom-left": _73c=0; _73d=dims.h; break; case "bottom-right": _73c=dims.w; _73d=dims.h; break; case "center": _73c=dims.w/2; _73d=dims.h/2; break; } var _73e; var _73f=MochiKit.Base.update({beforeStartInternal:function(_740){ s.makePositioned(_740.effects[0].element); _73e=s.makeClipping(_740.effects[0].element); },afterFinishInternal:function(_741){ s.hideElement(_741.effects[0].element); s.undoClipping(_741.effects[0].element,_73e); s.undoPositioned(_741.effects[0].element); s.setStyle(_741.effects[0].element,_73a); }},_736); return new v.Parallel([new v.Opacity(_735,{sync:true,to:0,from:1,transition:_736.opacityTransition}),new v.Scale(_735,/Opera/.test(navigator.userAgent)?1:0,{scaleMode:{originalHeight:dims.h,originalWidth:dims.w},sync:true,transition:_736.scaleTransition,scaleContent:_736.scaleContent,scaleFromCenter:_736.scaleFromCenter,restoreAfterFinish:true}),new v.Move(_735,{x:_73c,y:_73d,sync:true,transition:_736.moveTransition})],_73f); }; MochiKit.Visual.pulsate=function(_742,_743){ var d=MochiKit.DOM; var v=MochiKit.Visual; var b=MochiKit.Base; var _747=MochiKit.Style.getStyle(_742,"opacity"); _743=b.update({duration:3,from:0,afterFinishInternal:function(_748){ MochiKit.Style.setStyle(_748.element,{"opacity":_747}); }},_743); var _749=_743.transition||v.Transitions.sinoidal; _743.transition=function(pos){ return _749(1-v.Transitions.pulse(pos,_743.pulses)); }; return new v.Opacity(_742,_743); }; MochiKit.Visual.fold=function(_74b,_74c){ var d=MochiKit.DOM; var v=MochiKit.Visual; var s=MochiKit.Style; _74b=d.getElement(_74b); var _750=s.getElementDimensions(_74b,true); var _751={top:_74b.style.top,left:_74b.style.left,width:_74b.style.width,height:_74b.style.height}; var _752=s.makeClipping(_74b); _74c=MochiKit.Base.update({scaleContent:false,scaleX:false,scaleMode:{originalHeight:_750.h,originalWidth:_750.w},afterFinishInternal:function(_753){ new v.Scale(_74b,1,{scaleContent:false,scaleY:false,scaleMode:{originalHeight:_750.h,originalWidth:_750.w},afterFinishInternal:function(_754){ s.hideElement(_754.element); s.undoClipping(_754.element,_752); s.setStyle(_754.element,_751); }}); }},_74c); return new v.Scale(_74b,5,_74c); }; MochiKit.Visual.Color=MochiKit.Color.Color; MochiKit.Visual.getElementsComputedStyle=MochiKit.DOM.computedStyle; MochiKit.Visual.__new__=function(){ var m=MochiKit.Base; m.nameFunctions(this); this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)}; }; MochiKit.Visual.EXPORT=["roundElement","roundClass","tagifyText","multiple","toggle","Parallel","Sequence","Opacity","Move","Scale","Highlight","ScrollTo","Morph","fade","appear","puff","blindUp","blindDown","switchOff","dropOut","shake","slideDown","slideUp","squish","grow","shrink","pulsate","fold"]; MochiKit.Visual.EXPORT_OK=["Base","PAIRS"]; MochiKit.Visual.__new__(); MochiKit.Base._exportSymbols(this,MochiKit.Visual); MochiKit.Base._deps("DragAndDrop",["Base","Iter","DOM","Signal","Visual","Position"]); MochiKit.DragAndDrop.NAME="MochiKit.DragAndDrop"; MochiKit.DragAndDrop.VERSION="1.4.2"; MochiKit.DragAndDrop.__repr__=function(){ return "["+this.NAME+" "+this.VERSION+"]"; }; MochiKit.DragAndDrop.toString=function(){ return this.__repr__(); }; MochiKit.DragAndDrop.EXPORT=["Droppable","Draggable"]; MochiKit.DragAndDrop.EXPORT_OK=["Droppables","Draggables"]; MochiKit.DragAndDrop.Droppables={drops:[],remove:function(_756){ this.drops=MochiKit.Base.filter(function(d){ return d.element!=MochiKit.DOM.getElement(_756); },this.drops); },register:function(drop){ this.drops.push(drop); },unregister:function(drop){ this.drops=MochiKit.Base.filter(function(d){ return d!=drop; },this.drops); },prepare:function(_75b){ MochiKit.Base.map(function(drop){ if(drop.isAccepted(_75b)){ if(drop.options.activeclass){ MochiKit.DOM.addElementClass(drop.element,drop.options.activeclass); } drop.options.onactive(drop.element,_75b); } },this.drops); },findDeepestChild:function(_75d){ deepest=_75d[0]; for(i=1;i<_75d.length;++i){ if(MochiKit.DOM.isChildNode(_75d[i].element,deepest.element)){ deepest=_75d[i]; } } return deepest; },show:function(_75e,_75f){ if(!this.drops.length){ return; } var _760=[]; if(this.last_active){ this.last_active.deactivate(); } MochiKit.Iter.forEach(this.drops,function(drop){ if(drop.isAffected(_75e,_75f)){ _760.push(drop); } }); if(_760.length>0){ drop=this.findDeepestChild(_760); MochiKit.Position.within(drop.element,_75e.page.x,_75e.page.y); drop.options.onhover(_75f,drop.element,MochiKit.Position.overlap(drop.options.overlap,drop.element)); drop.activate(); } },fire:function(_762,_763){ if(!this.last_active){ return; } MochiKit.Position.prepare(); if(this.last_active.isAffected(_762.mouse(),_763)){ this.last_active.options.ondrop(_763,this.last_active.element,_762); } },reset:function(_764){ MochiKit.Base.map(function(drop){ if(drop.options.activeclass){ MochiKit.DOM.removeElementClass(drop.element,drop.options.activeclass); } drop.options.ondesactive(drop.element,_764); },this.drops); if(this.last_active){ this.last_active.deactivate(); } }}; MochiKit.DragAndDrop.Droppable=function(_766,_767){ var cls=arguments.callee; if(!(this instanceof cls)){ return new cls(_766,_767); } this.__init__(_766,_767); }; MochiKit.DragAndDrop.Droppable.prototype={__class__:MochiKit.DragAndDrop.Droppable,__init__:function(_769,_76a){ var d=MochiKit.DOM; var b=MochiKit.Base; this.element=d.getElement(_769); this.options=b.update({greedy:true,hoverclass:null,activeclass:null,hoverfunc:b.noop,accept:null,onactive:b.noop,ondesactive:b.noop,onhover:b.noop,ondrop:b.noop,containment:[],tree:false},_76a); this.options._containers=[]; b.map(MochiKit.Base.bind(function(c){ this.options._containers.push(d.getElement(c)); },this),this.options.containment); MochiKit.Style.makePositioned(this.element); MochiKit.DragAndDrop.Droppables.register(this); },isContained:function(_76e){ if(this.options._containers.length){ var _76f; if(this.options.tree){ _76f=_76e.treeNode; }else{ _76f=_76e.parentNode; } return MochiKit.Iter.some(this.options._containers,function(c){ return _76f==c; }); }else{ return true; } },isAccepted:function(_771){ return ((!this.options.accept)||MochiKit.Iter.some(this.options.accept,function(c){ return MochiKit.DOM.hasElementClass(_771,c); })); },isAffected:function(_773,_774){ return ((this.element!=_774)&&this.isContained(_774)&&this.isAccepted(_774)&&MochiKit.Position.within(this.element,_773.page.x,_773.page.y)); },deactivate:function(){ if(this.options.hoverclass){ MochiKit.DOM.removeElementClass(this.element,this.options.hoverclass); } this.options.hoverfunc(this.element,false); MochiKit.DragAndDrop.Droppables.last_active=null; },activate:function(){ if(this.options.hoverclass){ MochiKit.DOM.addElementClass(this.element,this.options.hoverclass); } this.options.hoverfunc(this.element,true); MochiKit.DragAndDrop.Droppables.last_active=this; },destroy:function(){ MochiKit.DragAndDrop.Droppables.unregister(this); },repr:function(){ return "["+this.__class__.NAME+", options:"+MochiKit.Base.repr(this.options)+"]"; }}; MochiKit.DragAndDrop.Draggables={drags:[],register:function(_775){ if(this.drags.length===0){ var conn=MochiKit.Signal.connect; this.eventMouseUp=conn(document,"onmouseup",this,this.endDrag); this.eventMouseMove=conn(document,"onmousemove",this,this.updateDrag); this.eventKeypress=conn(document,"onkeypress",this,this.keyPress); } this.drags.push(_775); },unregister:function(_777){ this.drags=MochiKit.Base.filter(function(d){ return d!=_777; },this.drags); if(this.drags.length===0){ var disc=MochiKit.Signal.disconnect; disc(this.eventMouseUp); disc(this.eventMouseMove); disc(this.eventKeypress); } },activate:function(_77a){ window.focus(); this.activeDraggable=_77a; },deactivate:function(){ this.activeDraggable=null; },updateDrag:function(_77b){ if(!this.activeDraggable){ return; } var _77c=_77b.mouse(); if(this._lastPointer&&(MochiKit.Base.repr(this._lastPointer.page)==MochiKit.Base.repr(_77c.page))){ return; } this._lastPointer=_77c; this.activeDraggable.updateDrag(_77b,_77c); },endDrag:function(_77d){ if(!this.activeDraggable){ return; } this._lastPointer=null; this.activeDraggable.endDrag(_77d); this.activeDraggable=null; },keyPress:function(_77e){ if(this.activeDraggable){ this.activeDraggable.keyPress(_77e); } },notify:function(_77f,_780,_781){ MochiKit.Signal.signal(this,_77f,_780,_781); }}; MochiKit.DragAndDrop.Draggable=function(_782,_783){ var cls=arguments.callee; if(!(this instanceof cls)){ return new cls(_782,_783); } this.__init__(_782,_783); }; MochiKit.DragAndDrop.Draggable.prototype={__class__:MochiKit.DragAndDrop.Draggable,__init__:function(_785,_786){ var v=MochiKit.Visual; var b=MochiKit.Base; _786=b.update({handle:false,starteffect:function(_789){ this._savedOpacity=MochiKit.Style.getStyle(_789,"opacity")||1; new v.Opacity(_789,{duration:0.2,from:this._savedOpacity,to:0.7}); },reverteffect:function(_78a,_78b,_78c){ var dur=Math.sqrt(Math.abs(_78b^2)+Math.abs(_78c^2))*0.02; return new v.Move(_78a,{x:-_78c,y:-_78b,duration:dur}); },endeffect:function(_78e){ new v.Opacity(_78e,{duration:0.2,from:0.7,to:this._savedOpacity}); },onchange:b.noop,zindex:1000,revert:false,scroll:false,scrollSensitivity:20,scrollSpeed:15,snap:false},_786); var d=MochiKit.DOM; this.element=d.getElement(_785); if(_786.handle&&(typeof (_786.handle)=="string")){ this.handle=d.getFirstElementByTagAndClassName(null,_786.handle,this.element); } if(!this.handle){ this.handle=d.getElement(_786.handle); } if(!this.handle){ this.handle=this.element; } if(_786.scroll&&!_786.scroll.scrollTo&&!_786.scroll.outerHTML){ _786.scroll=d.getElement(_786.scroll); this._isScrollChild=MochiKit.DOM.isChildNode(this.element,_786.scroll); } MochiKit.Style.makePositioned(this.element); this.delta=this.currentDelta(); this.options=_786; this.dragging=false; this.eventMouseDown=MochiKit.Signal.connect(this.handle,"onmousedown",this,this.initDrag); MochiKit.DragAndDrop.Draggables.register(this); },destroy:function(){ MochiKit.Signal.disconnect(this.eventMouseDown); MochiKit.DragAndDrop.Draggables.unregister(this); },currentDelta:function(){ var s=MochiKit.Style.getStyle; return [parseInt(s(this.element,"left")||"0"),parseInt(s(this.element,"top")||"0")]; },initDrag:function(_791){ if(!_791.mouse().button.left){ return; } var src=_791.target(); var _793=(src.tagName||"").toUpperCase(); if(_793==="INPUT"||_793==="SELECT"||_793==="OPTION"||_793==="BUTTON"||_793==="TEXTAREA"){ return; } if(this._revert){ this._revert.cancel(); this._revert=null; } var _794=_791.mouse(); var pos=MochiKit.Position.cumulativeOffset(this.element); this.offset=[_794.page.x-pos.x,_794.page.y-pos.y]; MochiKit.DragAndDrop.Draggables.activate(this); _791.stop(); },startDrag:function(_796){ this.dragging=true; if(this.options.selectclass){ MochiKit.DOM.addElementClass(this.element,this.options.selectclass); } if(this.options.zindex){ this.originalZ=parseInt(MochiKit.Style.getStyle(this.element,"z-index")||"0"); this.element.style.zIndex=this.options.zindex; } if(this.options.ghosting){ this._clone=this.element.cloneNode(true); this.ghostPosition=MochiKit.Position.absolutize(this.element); this.element.parentNode.insertBefore(this._clone,this.element); } if(this.options.scroll){ if(this.options.scroll==window){ var _797=this._getWindowScroll(this.options.scroll); this.originalScrollLeft=_797.left; this.originalScrollTop=_797.top; }else{ this.originalScrollLeft=this.options.scroll.scrollLeft; this.originalScrollTop=this.options.scroll.scrollTop; } } MochiKit.DragAndDrop.Droppables.prepare(this.element); MochiKit.DragAndDrop.Draggables.notify("start",this,_796); if(this.options.starteffect){ this.options.starteffect(this.element); } },updateDrag:function(_798,_799){ if(!this.dragging){ this.startDrag(_798); } MochiKit.Position.prepare(); MochiKit.DragAndDrop.Droppables.show(_799,this.element); MochiKit.DragAndDrop.Draggables.notify("drag",this,_798); this.draw(_799); this.options.onchange(this); if(this.options.scroll){ this.stopScrolling(); var p,q; if(this.options.scroll==window){ var s=this._getWindowScroll(this.options.scroll); p=new MochiKit.Style.Coordinates(s.left,s.top); q=new MochiKit.Style.Coordinates(s.left+s.width,s.top+s.height); }else{ p=MochiKit.Position.page(this.options.scroll); p.x+=this.options.scroll.scrollLeft; p.y+=this.options.scroll.scrollTop; p.x+=(window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0); p.y+=(window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0); q=new MochiKit.Style.Coordinates(p.x+this.options.scroll.offsetWidth,p.y+this.options.scroll.offsetHeight); } var _79d=[0,0]; if(_799.page.x>(q.x-this.options.scrollSensitivity)){ _79d[0]=_799.page.x-(q.x-this.options.scrollSensitivity); }else{ if(_799.page.x<(p.x+this.options.scrollSensitivity)){ _79d[0]=_799.page.x-(p.x+this.options.scrollSensitivity); } } if(_799.page.y>(q.y-this.options.scrollSensitivity)){ _79d[1]=_799.page.y-(q.y-this.options.scrollSensitivity); }else{ if(_799.page.y<(p.y+this.options.scrollSensitivity)){ _79d[1]=_799.page.y-(p.y+this.options.scrollSensitivity); } } this.startScrolling(_79d); } if(/AppleWebKit/.test(navigator.appVersion)){ window.scrollBy(0,0); } _798.stop(); },finishDrag:function(_79e,_79f){ var dr=MochiKit.DragAndDrop; this.dragging=false; if(this.options.selectclass){ MochiKit.DOM.removeElementClass(this.element,this.options.selectclass); } if(this.options.ghosting){ MochiKit.Position.relativize(this.element,this.ghostPosition); MochiKit.DOM.removeElement(this._clone); this._clone=null; } if(_79f){ dr.Droppables.fire(_79e,this.element); } dr.Draggables.notify("end",this,_79e); var _7a1=this.options.revert; if(_7a1&&typeof (_7a1)=="function"){ _7a1=_7a1(this.element); } var d=this.currentDelta(); if(_7a1&&this.options.reverteffect){ this._revert=this.options.reverteffect(this.element,d[1]-this.delta[1],d[0]-this.delta[0]); }else{ this.delta=d; } if(this.options.zindex){ this.element.style.zIndex=this.originalZ; } if(this.options.endeffect){ this.options.endeffect(this.element); } dr.Draggables.deactivate(); dr.Droppables.reset(this.element); },keyPress:function(_7a3){ if(_7a3.key().string!="KEY_ESCAPE"){ return; } this.finishDrag(_7a3,false); _7a3.stop(); },endDrag:function(_7a4){ if(!this.dragging){ return; } this.stopScrolling(); this.finishDrag(_7a4,true); _7a4.stop(); },draw:function(_7a5){ var pos=MochiKit.Position.cumulativeOffset(this.element); var d=this.currentDelta(); pos.x-=d[0]; pos.y-=d[1]; if(this.options.scroll&&(this.options.scroll!=window&&this._isScrollChild)){ pos.x-=this.options.scroll.scrollLeft-this.originalScrollLeft; pos.y-=this.options.scroll.scrollTop-this.originalScrollTop; } var p=[_7a5.page.x-pos.x-this.offset[0],_7a5.page.y-pos.y-this.offset[1]]; if(this.options.snap){ if(typeof (this.options.snap)=="function"){ p=this.options.snap(p[0],p[1]); }else{ if(this.options.snap instanceof Array){ var i=-1; p=MochiKit.Base.map(MochiKit.Base.bind(function(v){ i+=1; return Math.round(v/this.options.snap[i])*this.options.snap[i]; },this),p); }else{ p=MochiKit.Base.map(MochiKit.Base.bind(function(v){ return Math.round(v/this.options.snap)*this.options.snap; },this),p); } } } var _7ac=this.element.style; if((!this.options.constraint)||(this.options.constraint=="horizontal")){ _7ac.left=p[0]+"px"; } if((!this.options.constraint)||(this.options.constraint=="vertical")){ _7ac.top=p[1]+"px"; } if(_7ac.visibility=="hidden"){ _7ac.visibility=""; } },stopScrolling:function(){ if(this.scrollInterval){ clearInterval(this.scrollInterval); this.scrollInterval=null; MochiKit.DragAndDrop.Draggables._lastScrollPointer=null; } },startScrolling:function(_7ad){ if(!_7ad[0]&&!_7ad[1]){ return; } this.scrollSpeed=[_7ad[0]*this.options.scrollSpeed,_7ad[1]*this.options.scrollSpeed]; this.lastScrolled=new Date(); this.scrollInterval=setInterval(MochiKit.Base.bind(this.scroll,this),10); },scroll:function(){ var _7ae=new Date(); var _7af=_7ae-this.lastScrolled; this.lastScrolled=_7ae; if(this.options.scroll==window){ var s=this._getWindowScroll(this.options.scroll); if(this.scrollSpeed[0]||this.scrollSpeed[1]){ var dm=_7af/1000; this.options.scroll.scrollTo(s.left+dm*this.scrollSpeed[0],s.top+dm*this.scrollSpeed[1]); } }else{ this.options.scroll.scrollLeft+=this.scrollSpeed[0]*_7af/1000; this.options.scroll.scrollTop+=this.scrollSpeed[1]*_7af/1000; } var d=MochiKit.DragAndDrop; MochiKit.Position.prepare(); d.Droppables.show(d.Draggables._lastPointer,this.element); d.Draggables.notify("drag",this); if(this._isScrollChild){ d.Draggables._lastScrollPointer=d.Draggables._lastScrollPointer||d.Draggables._lastPointer; d.Draggables._lastScrollPointer.x+=this.scrollSpeed[0]*_7af/1000; d.Draggables._lastScrollPointer.y+=this.scrollSpeed[1]*_7af/1000; if(d.Draggables._lastScrollPointer.x<0){ d.Draggables._lastScrollPointer.x=0; } if(d.Draggables._lastScrollPointer.y<0){ d.Draggables._lastScrollPointer.y=0; } this.draw(d.Draggables._lastScrollPointer); } this.options.onchange(this); },_getWindowScroll:function(win){ var vp,w,h; MochiKit.DOM.withWindow(win,function(){ vp=MochiKit.Style.getViewportPosition(win.document); }); if(win.innerWidth){ w=win.innerWidth; h=win.innerHeight; }else{ if(win.document.documentElement&&win.document.documentElement.clientWidth){ w=win.document.documentElement.clientWidth; h=win.document.documentElement.clientHeight; }else{ w=win.document.body.offsetWidth; h=win.document.body.offsetHeight; } } return {top:vp.y,left:vp.x,width:w,height:h}; },repr:function(){ return "["+this.__class__.NAME+", options:"+MochiKit.Base.repr(this.options)+"]"; }}; MochiKit.DragAndDrop.__new__=function(){ MochiKit.Base.nameFunctions(this); this.EXPORT_TAGS={":common":this.EXPORT,":all":MochiKit.Base.concat(this.EXPORT,this.EXPORT_OK)}; }; MochiKit.DragAndDrop.__new__(); MochiKit.Base._exportSymbols(this,MochiKit.DragAndDrop); MochiKit.Base._deps("Sortable",["Base","Iter","DOM","Position","DragAndDrop"]); MochiKit.Sortable.NAME="MochiKit.Sortable"; MochiKit.Sortable.VERSION="1.4.2"; MochiKit.Sortable.__repr__=function(){ return "["+this.NAME+" "+this.VERSION+"]"; }; MochiKit.Sortable.toString=function(){ return this.__repr__(); }; MochiKit.Sortable.EXPORT=[]; MochiKit.Sortable.EXPORT_OK=[]; MochiKit.Base.update(MochiKit.Sortable,{sortables:{},_findRootElement:function(_7b7){ while(_7b7.tagName.toUpperCase()!="BODY"){ if(_7b7.id&&MochiKit.Sortable.sortables[_7b7.id]){ return _7b7; } _7b7=_7b7.parentNode; } },_createElementId:function(_7b8){ if(_7b8.id==null||_7b8.id==""){ var d=MochiKit.DOM; var id; var _7bb=1; while(d.getElement(id="sortable"+_7bb)!=null){ _7bb+=1; } d.setNodeAttribute(_7b8,"id",id); } },options:function(_7bc){ _7bc=MochiKit.Sortable._findRootElement(MochiKit.DOM.getElement(_7bc)); if(!_7bc){ return; } return MochiKit.Sortable.sortables[_7bc.id]; },destroy:function(_7bd){ var s=MochiKit.Sortable.options(_7bd); var b=MochiKit.Base; var d=MochiKit.DragAndDrop; if(s){ MochiKit.Signal.disconnect(s.startHandle); MochiKit.Signal.disconnect(s.endHandle); b.map(function(dr){ d.Droppables.remove(dr); },s.droppables); b.map(function(dr){ dr.destroy(); },s.draggables); delete MochiKit.Sortable.sortables[s.element.id]; } },create:function(_7c3,_7c4){ _7c3=MochiKit.DOM.getElement(_7c3); var self=MochiKit.Sortable; self._createElementId(_7c3); _7c4=MochiKit.Base.update({element:_7c3,tag:"li",dropOnEmpty:false,tree:false,treeTag:"ul",overlap:"vertical",constraint:"vertical",containment:[_7c3],handle:false,only:false,hoverclass:null,ghosting:false,scroll:false,scrollSensitivity:20,scrollSpeed:15,format:/^[^_]*_(.*)$/,onChange:MochiKit.Base.noop,onUpdate:MochiKit.Base.noop,accept:null},_7c4); self.destroy(_7c3); var _7c6={revert:true,ghosting:_7c4.ghosting,scroll:_7c4.scroll,scrollSensitivity:_7c4.scrollSensitivity,scrollSpeed:_7c4.scrollSpeed,constraint:_7c4.constraint,handle:_7c4.handle}; if(_7c4.starteffect){ _7c6.starteffect=_7c4.starteffect; } if(_7c4.reverteffect){ _7c6.reverteffect=_7c4.reverteffect; }else{ if(_7c4.ghosting){ _7c6.reverteffect=function(_7c7){ _7c7.style.top=0; _7c7.style.left=0; }; } } if(_7c4.endeffect){ _7c6.endeffect=_7c4.endeffect; } if(_7c4.zindex){ _7c6.zindex=_7c4.zindex; } var _7c8={overlap:_7c4.overlap,containment:_7c4.containment,hoverclass:_7c4.hoverclass,onhover:self.onHover,tree:_7c4.tree,accept:_7c4.accept}; var _7c9={onhover:self.onEmptyHover,overlap:_7c4.overlap,containment:_7c4.containment,hoverclass:_7c4.hoverclass,accept:_7c4.accept}; MochiKit.DOM.removeEmptyTextNodes(_7c3); _7c4.draggables=[]; _7c4.droppables=[]; if(_7c4.dropOnEmpty||_7c4.tree){ new MochiKit.DragAndDrop.Droppable(_7c3,_7c9); _7c4.droppables.push(_7c3); } MochiKit.Base.map(function(e){ var _7cb=_7c4.handle?MochiKit.DOM.getFirstElementByTagAndClassName(null,_7c4.handle,e):e; _7c4.draggables.push(new MochiKit.DragAndDrop.Draggable(e,MochiKit.Base.update(_7c6,{handle:_7cb}))); new MochiKit.DragAndDrop.Droppable(e,_7c8); if(_7c4.tree){ e.treeNode=_7c3; } _7c4.droppables.push(e); },(self.findElements(_7c3,_7c4)||[])); if(_7c4.tree){ MochiKit.Base.map(function(e){ new MochiKit.DragAndDrop.Droppable(e,_7c9); e.treeNode=_7c3; _7c4.droppables.push(e); },(self.findTreeElements(_7c3,_7c4)||[])); } self.sortables[_7c3.id]=_7c4; _7c4.lastValue=self.serialize(_7c3); _7c4.startHandle=MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables,"start",MochiKit.Base.partial(self.onStart,_7c3)); _7c4.endHandle=MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables,"end",MochiKit.Base.partial(self.onEnd,_7c3)); },onStart:function(_7cd,_7ce){ var self=MochiKit.Sortable; var _7d0=self.options(_7cd); _7d0.lastValue=self.serialize(_7d0.element); },onEnd:function(_7d1,_7d2){ var self=MochiKit.Sortable; self.unmark(); var _7d4=self.options(_7d1); if(_7d4.lastValue!=self.serialize(_7d4.element)){ _7d4.onUpdate(_7d4.element); } },findElements:function(_7d5,_7d6){ return MochiKit.Sortable.findChildren(_7d5,_7d6.only,_7d6.tree,_7d6.tag); },findTreeElements:function(_7d7,_7d8){ return MochiKit.Sortable.findChildren(_7d7,_7d8.only,_7d8.tree?true:false,_7d8.treeTag); },findChildren:function(_7d9,only,_7db,_7dc){ if(!_7d9.hasChildNodes()){ return null; } _7dc=_7dc.toUpperCase(); if(only){ only=MochiKit.Base.flattenArray([only]); } var _7dd=[]; MochiKit.Base.map(function(e){ if(e.tagName&&e.tagName.toUpperCase()==_7dc&&(!only||MochiKit.Iter.some(only,function(c){ return MochiKit.DOM.hasElementClass(e,c); }))){ _7dd.push(e); } if(_7db){ var _7e0=MochiKit.Sortable.findChildren(e,only,_7db,_7dc); if(_7e0&&_7e0.length>0){ _7dd=_7dd.concat(_7e0); } } },_7d9.childNodes); return _7dd; },onHover:function(_7e1,_7e2,_7e3){ if(MochiKit.DOM.isChildNode(_7e2,_7e1)){ return; } var self=MochiKit.Sortable; if(_7e3>0.33&&_7e3<0.66&&self.options(_7e2).tree){ return; }else{ if(_7e3>0.5){ self.mark(_7e2,"before"); if(_7e2.previousSibling!=_7e1){ var _7e5=_7e1.parentNode; _7e1.style.visibility="hidden"; _7e2.parentNode.insertBefore(_7e1,_7e2); if(_7e2.parentNode!=_7e5){ self.options(_7e5).onChange(_7e1); } self.options(_7e2.parentNode).onChange(_7e1); } }else{ self.mark(_7e2,"after"); var _7e6=_7e2.nextSibling||null; if(_7e6!=_7e1){ var _7e5=_7e1.parentNode; _7e1.style.visibility="hidden"; _7e2.parentNode.insertBefore(_7e1,_7e6); if(_7e2.parentNode!=_7e5){ self.options(_7e5).onChange(_7e1); } self.options(_7e2.parentNode).onChange(_7e1); } } } },_offsetSize:function(_7e7,type){ if(type=="vertical"||type=="height"){ return _7e7.offsetHeight; }else{ return _7e7.offsetWidth; } },onEmptyHover:function(_7e9,_7ea,_7eb){ var _7ec=_7e9.parentNode; var self=MochiKit.Sortable; var _7ee=self.options(_7ea); if(!MochiKit.DOM.isChildNode(_7ea,_7e9)){ var _7ef; var _7f0=self.findElements(_7ea,{tag:_7ee.tag,only:_7ee.only}); var _7f1=null; if(_7f0){ var _7f2=self._offsetSize(_7ea,_7ee.overlap)*(1-_7eb); for(_7ef=0;_7ef<_7f0.length;_7ef+=1){ if(_7f2-self._offsetSize(_7f0[_7ef],_7ee.overlap)>=0){ _7f2-=self._offsetSize(_7f0[_7ef],_7ee.overlap); }else{ if(_7f2-(self._offsetSize(_7f0[_7ef],_7ee.overlap)/2)>=0){ _7f1=_7ef+1<_7f0.length?_7f0[_7ef+1]:null; break; }else{ _7f1=_7f0[_7ef]; break; } } } } _7ea.insertBefore(_7e9,_7f1); self.options(_7ec).onChange(_7e9); _7ee.onChange(_7e9); } },unmark:function(){ var m=MochiKit.Sortable._marker; if(m){ MochiKit.Style.hideElement(m); } },mark:function(_7f4,_7f5){ var d=MochiKit.DOM; var self=MochiKit.Sortable; var _7f8=self.options(_7f4.parentNode); if(_7f8&&!_7f8.ghosting){ return; } if(!self._marker){ self._marker=d.getElement("dropmarker")||document.createElement("DIV"); MochiKit.Style.hideElement(self._marker); d.addElementClass(self._marker,"dropmarker"); self._marker.style.position="absolute"; document.getElementsByTagName("body").item(0).appendChild(self._marker); } var _7f9=MochiKit.Position.cumulativeOffset(_7f4); self._marker.style.left=_7f9.x+"px"; self._marker.style.top=_7f9.y+"px"; if(_7f5=="after"){ if(_7f8.overlap=="horizontal"){ self._marker.style.left=(_7f9.x+_7f4.clientWidth)+"px"; }else{ self._marker.style.top=(_7f9.y+_7f4.clientHeight)+"px"; } } MochiKit.Style.showElement(self._marker); },_tree:function(_7fa,_7fb,_7fc){ var self=MochiKit.Sortable; var _7fe=self.findElements(_7fa,_7fb)||[]; for(var i=0;i<_7fe.length;++i){ var _800=_7fe[i].id.match(_7fb.format); if(!_800){ continue; } var _801={id:encodeURIComponent(_800?_800[1]:null),element:_7fa,parent:_7fc,children:[],position:_7fc.children.length,container:self._findChildrenElement(_7fe[i],_7fb.treeTag.toUpperCase())}; if(_801.container){ self._tree(_801.container,_7fb,_801); } _7fc.children.push(_801); } return _7fc; },_findChildrenElement:function(_802,_803){ if(_802&&_802.hasChildNodes){ _803=_803.toUpperCase(); for(var i=0;i<_802.childNodes.length;++i){ if(_802.childNodes[i].tagName.toUpperCase()==_803){ return _802.childNodes[i]; } } } return null; },tree:function(_805,_806){ _805=MochiKit.DOM.getElement(_805); var _807=MochiKit.Sortable.options(_805); _806=MochiKit.Base.update({tag:_807.tag,treeTag:_807.treeTag,only:_807.only,name:_805.id,format:_807.format},_806||{}); var root={id:null,parent:null,children:new Array,container:_805,position:0}; return MochiKit.Sortable._tree(_805,_806,root); },setSequence:function(_809,_80a,_80b){ var self=MochiKit.Sortable; var b=MochiKit.Base; _809=MochiKit.DOM.getElement(_809); _80b=b.update(self.options(_809),_80b||{}); var _80e={}; b.map(function(n){ var m=n.id.match(_80b.format); if(m){ _80e[m[1]]=[n,n.parentNode]; } n.parentNode.removeChild(n); },self.findElements(_809,_80b)); b.map(function(_811){ var n=_80e[_811]; if(n){ n[1].appendChild(n[0]); delete _80e[_811]; } },_80a); },_constructIndex:function(node){ var _814=""; do{ if(node.id){ _814="["+node.position+"]"+_814; } }while((node=node.parent)!=null); return _814; },sequence:function(_815,_816){ _815=MochiKit.DOM.getElement(_815); var self=MochiKit.Sortable; var _816=MochiKit.Base.update(self.options(_815),_816||{}); return MochiKit.Base.map(function(item){ return item.id.match(_816.format)?item.id.match(_816.format)[1]:""; },MochiKit.DOM.getElement(self.findElements(_815,_816)||[])); },serialize:function(_819,_81a){ _819=MochiKit.DOM.getElement(_819); var self=MochiKit.Sortable; _81a=MochiKit.Base.update(self.options(_819),_81a||{}); var name=encodeURIComponent(_81a.name||_819.id); if(_81a.tree){ return MochiKit.Base.flattenArray(MochiKit.Base.map(function(item){ return [name+self._constructIndex(item)+"[id]="+encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); },self.tree(_819,_81a).children)).join("&"); }else{ return MochiKit.Base.map(function(item){ return name+"[]="+encodeURIComponent(item); },self.sequence(_819,_81a)).join("&"); } }}); MochiKit.Sortable.Sortable=MochiKit.Sortable; MochiKit.Sortable.__new__=function(){ MochiKit.Base.nameFunctions(this); this.EXPORT_TAGS={":common":this.EXPORT,":all":MochiKit.Base.concat(this.EXPORT,this.EXPORT_OK)}; }; MochiKit.Sortable.__new__(); MochiKit.Base._exportSymbols(this,MochiKit.Sortable); if(typeof (MochiKit)=="undefined"){ MochiKit={}; } if(typeof (MochiKit.MochiKit)=="undefined"){ MochiKit.MochiKit={}; } MochiKit.MochiKit.NAME="MochiKit.MochiKit"; MochiKit.MochiKit.VERSION="1.4.2"; MochiKit.MochiKit.__repr__=function(){ return "["+this.NAME+" "+this.VERSION+"]"; }; MochiKit.MochiKit.toString=function(){ return this.__repr__(); }; MochiKit.MochiKit.SUBMODULES=["Base","Iter","Logging","DateTime","Format","Async","DOM","Selector","Style","LoggingPane","Color","Signal","Position","Visual","DragAndDrop","Sortable"]; if(typeof (JSAN)!="undefined"||typeof (dojo)!="undefined"){ if(typeof (dojo)!="undefined"){ dojo.provide("MochiKit.MochiKit"); (function(lst){ for(var i=0;i"); } } })(); } Paste-1.7.5.1/paste/evalexception/media/minus.jpg0000664000175000017500000000054711354703364020257 0ustar ianbianbÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ "ÿÄÿÄ$!4BTs’±ÿÄÿÄ!ÿÚ ?ÐZ”êTŒz9m¬ò’CÆéÔQ A7¿^ö‹ìg‹“Ü;îj—Î?äWÚQJ­M{Oh,ŸÿÙPaste-1.7.5.1/paste/evalexception/media/debug.js0000664000175000017500000001024111354703364020036 0ustar ianbianbfunction showFrame(anchor) { var tbid = anchor.getAttribute('tbid'); var expanded = anchor.expanded; if (expanded) { MochiKit.DOM.hideElement(anchor.expandedElement); anchor.expanded = false; _swapImage(anchor); return false; } anchor.expanded = true; if (anchor.expandedElement) { MochiKit.DOM.showElement(anchor.expandedElement); _swapImage(anchor); $('debug_input_'+tbid).focus(); return false; } var url = debug_base + '/show_frame?tbid=' + tbid + '&debugcount=' + debug_count; var d = MochiKit.Async.doSimpleXMLHttpRequest(url); d.addCallbacks(function (data) { var el = MochiKit.DOM.DIV({}); anchor.parentNode.insertBefore(el, anchor.nextSibling); el.innerHTML = data.responseText; anchor.expandedElement = el; _swapImage(anchor); $('debug_input_'+tbid).focus(); }, function (error) { showError(error.req.responseText); }); return false; } function _swapImage(anchor) { var el = anchor.getElementsByTagName('IMG')[0]; if (anchor.expanded) { var img = 'minus.jpg'; } else { var img = 'plus.jpg'; } el.src = debug_base + '/media/' + img; } function submitInput(button, tbid) { var input = $(button.getAttribute('input-from')); var output = $(button.getAttribute('output-to')); var url = debug_base + '/exec_input'; var history = input.form.history; input.historyPosition = 0; if (! history) { history = input.form.history = []; } history.push(input.value); var vars = { tbid: tbid, debugcount: debug_count, input: input.value }; MochiKit.DOM.showElement(output); var d = MochiKit.Async.doSimpleXMLHttpRequest(url, vars); d.addCallbacks(function (data) { var result = data.responseText; output.innerHTML += result; input.value = ''; input.focus(); }, function (error) { showError(error.req.responseText); }); return false; } function showError(msg) { var el = $('error-container'); if (el.innerHTML) { el.innerHTML += '
\n' + msg; } else { el.innerHTML = msg; } MochiKit.DOM.showElement('error-area'); } function clearError() { var el = $('error-container'); el.innerHTML = ''; MochiKit.DOM.hideElement('error-area'); } function expandInput(button) { var input = button.form.elements.input; stdops = { name: 'input', style: 'width: 100%', autocomplete: 'off' }; if (input.tagName == 'INPUT') { var newEl = MochiKit.DOM.TEXTAREA(stdops); var text = 'Contract'; } else { stdops['type'] = 'text'; stdops['onkeypress'] = 'upArrow(this)'; var newEl = MochiKit.DOM.INPUT(stdops); var text = 'Expand'; } newEl.value = input.value; newEl.id = input.id; MochiKit.DOM.swapDOM(input, newEl); newEl.focus(); button.value = text; return false; } function upArrow(input, event) { if (window.event) { event = window.event; } if (event.keyCode != 38 && event.keyCode != 40) { // not an up- or down-arrow return true; } var dir = event.keyCode == 38 ? 1 : -1; var history = input.form.history; if (! history) { history = input.form.history = []; } var pos = input.historyPosition || 0; if (! pos && dir == -1) { return true; } if (! pos && input.value) { history.push(input.value); pos = 1; } pos += dir; if (history.length-pos < 0) { pos = 1; } if (history.length-pos > history.length-1) { input.value = ''; return true; } input.historyPosition = pos; var line = history[history.length-pos]; input.value = line; } function expandLong(anchor) { var span = anchor; while (span) { if (span.style && span.style.display == 'none') { break; } span = span.nextSibling; } if (! span) { return false; } MochiKit.DOM.showElement(span); MochiKit.DOM.hideElement(anchor); return false; } Paste-1.7.5.1/paste/evalexception/media/plus.jpg0000664000175000017500000000055111354703364020102 0ustar ianbianbÿØÿàJFIFÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ "ÿÄÿÄ#"#4Ts’±ÿÄÿÄ!ÿÚ ?g®n½Ieº=%êiÈ‘>󧼎ìn.±¥ÜÄs±¥ÕU1Œ}\ÏpïºÅ7Ö?ä/°¢Zš2öžÐY?ÿÙPaste-1.7.5.1/paste/cascade.py0000664000175000017500000001063411354703364014430 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Cascades through several applications, so long as applications return ``404 Not Found``. """ from paste import httpexceptions from paste.util import converters import tempfile from cStringIO import StringIO __all__ = ['Cascade'] def make_cascade(loader, global_conf, catch='404', **local_conf): """ Entry point for Paste Deploy configuration Expects configuration like:: [composit:cascade] use = egg:Paste#cascade # all start with 'app' and are sorted alphabetically app1 = foo app2 = bar ... catch = 404 500 ... """ catch = map(int, converters.aslist(catch)) apps = [] for name, value in local_conf.items(): if not name.startswith('app'): raise ValueError( "Bad configuration key %r (=%r); all configuration keys " "must start with 'app'" % (name, value)) app = loader.get_app(value, global_conf=global_conf) apps.append((name, app)) apps.sort() apps = [app for name, app in apps] return Cascade(apps, catch=catch) class Cascade(object): """ Passed a list of applications, ``Cascade`` will try each of them in turn. If one returns a status code listed in ``catch`` (by default just ``404 Not Found``) then the next application is tried. If all applications fail, then the last application's failure response is used. Instances of this class are WSGI applications. """ def __init__(self, applications, catch=(404,)): self.apps = applications self.catch_codes = {} self.catch_exceptions = [] for error in catch: if isinstance(error, str): error = int(error.split(None, 1)[0]) if isinstance(error, httpexceptions.HTTPException): exc = error code = error.code else: exc = httpexceptions.get_exception(error) code = error self.catch_codes[code] = exc self.catch_exceptions.append(exc) self.catch_exceptions = tuple(self.catch_exceptions) def __call__(self, environ, start_response): """ WSGI application interface """ failed = [] def repl_start_response(status, headers, exc_info=None): code = int(status.split(None, 1)[0]) if code in self.catch_codes: failed.append(None) return _consuming_writer return start_response(status, headers, exc_info) try: length = int(environ.get('CONTENT_LENGTH', 0) or 0) except ValueError: length = 0 if length > 0: # We have to copy wsgi.input copy_wsgi_input = True if length > 4096 or length < 0: f = tempfile.TemporaryFile() if length < 0: f.write(environ['wsgi.input'].read()) else: copy_len = length while copy_len > 0: chunk = environ['wsgi.input'].read(min(copy_len, 4096)) if not chunk: raise IOError("Request body truncated") f.write(chunk) copy_len -= len(chunk) f.seek(0) else: f = StringIO(environ['wsgi.input'].read(length)) environ['wsgi.input'] = f else: copy_wsgi_input = False for app in self.apps[:-1]: environ_copy = environ.copy() if copy_wsgi_input: environ_copy['wsgi.input'].seek(0) failed = [] try: v = app(environ_copy, repl_start_response) if not failed: return v else: if hasattr(v, 'close'): # Exhaust the iterator first: list(v) # then close: v.close() except self.catch_exceptions, e: pass if copy_wsgi_input: environ['wsgi.input'].seek(0) return self.apps[-1](environ, start_response) def _consuming_writer(s): pass Paste-1.7.5.1/paste/modpython.py0000664000175000017500000001745011354703364015071 0ustar ianbianb"""WSGI Paste wrapper for mod_python. Requires Python 2.2 or greater. Example httpd.conf section for a Paste app with an ini file:: SetHandler python-program PythonHandler paste.modpython PythonOption paste.ini /some/location/your/pasteconfig.ini Or if you want to load a WSGI application under /your/homedir in the module ``startup`` and the WSGI app is ``app``:: SetHandler python-program PythonHandler paste.modpython PythonPath "['/virtual/project/directory'] + sys.path" PythonOption wsgi.application startup::app If you'd like to use a virtual installation, make sure to add it in the path like so:: SetHandler python-program PythonHandler paste.modpython PythonPath "['/virtual/project/directory', '/virtual/lib/python2.4/'] + sys.path" PythonOption paste.ini /virtual/project/directory/pasteconfig.ini Some WSGI implementations assume that the SCRIPT_NAME environ variable will always be equal to "the root URL of the app"; Apache probably won't act as you expect in that case. You can add another PythonOption directive to tell modpython_gateway to force that behavior: PythonOption SCRIPT_NAME /mcontrol Some WSGI applications need to be cleaned up when Apache exits. You can register a cleanup handler with yet another PythonOption directive: PythonOption wsgi.cleanup module::function The module.function will be called with no arguments on server shutdown, once for each child process or thread. This module highly based on Robert Brewer's, here: http://projects.amor.org/misc/svn/modpython_gateway.py """ import traceback try: from mod_python import apache except: pass from paste.deploy import loadapp class InputWrapper(object): def __init__(self, req): self.req = req def close(self): pass def read(self, size=-1): return self.req.read(size) def readline(self, size=-1): return self.req.readline(size) def readlines(self, hint=-1): return self.req.readlines(hint) def __iter__(self): line = self.readline() while line: yield line # Notice this won't prefetch the next line; it only # gets called if the generator is resumed. line = self.readline() class ErrorWrapper(object): def __init__(self, req): self.req = req def flush(self): pass def write(self, msg): self.req.log_error(msg) def writelines(self, seq): self.write(''.join(seq)) bad_value = ("You must provide a PythonOption '%s', either 'on' or 'off', " "when running a version of mod_python < 3.1") class Handler(object): def __init__(self, req): self.started = False options = req.get_options() # Threading and forking try: q = apache.mpm_query threaded = q(apache.AP_MPMQ_IS_THREADED) forked = q(apache.AP_MPMQ_IS_FORKED) except AttributeError: threaded = options.get('multithread', '').lower() if threaded == 'on': threaded = True elif threaded == 'off': threaded = False else: raise ValueError(bad_value % "multithread") forked = options.get('multiprocess', '').lower() if forked == 'on': forked = True elif forked == 'off': forked = False else: raise ValueError(bad_value % "multiprocess") env = self.environ = dict(apache.build_cgi_env(req)) if 'SCRIPT_NAME' in options: # Override SCRIPT_NAME and PATH_INFO if requested. env['SCRIPT_NAME'] = options['SCRIPT_NAME'] env['PATH_INFO'] = req.uri[len(options['SCRIPT_NAME']):] else: env['SCRIPT_NAME'] = '' env['PATH_INFO'] = req.uri env['wsgi.input'] = InputWrapper(req) env['wsgi.errors'] = ErrorWrapper(req) env['wsgi.version'] = (1, 0) env['wsgi.run_once'] = False if env.get("HTTPS") in ('yes', 'on', '1'): env['wsgi.url_scheme'] = 'https' else: env['wsgi.url_scheme'] = 'http' env['wsgi.multithread'] = threaded env['wsgi.multiprocess'] = forked self.request = req def run(self, application): try: result = application(self.environ, self.start_response) for data in result: self.write(data) if not self.started: self.request.set_content_length(0) if hasattr(result, 'close'): result.close() except: traceback.print_exc(None, self.environ['wsgi.errors']) if not self.started: self.request.status = 500 self.request.content_type = 'text/plain' data = "A server error occurred. Please contact the administrator." self.request.set_content_length(len(data)) self.request.write(data) def start_response(self, status, headers, exc_info=None): if exc_info: try: if self.started: raise exc_info[0], exc_info[1], exc_info[2] finally: exc_info = None self.request.status = int(status[:3]) for key, val in headers: if key.lower() == 'content-length': self.request.set_content_length(int(val)) elif key.lower() == 'content-type': self.request.content_type = val else: self.request.headers_out.add(key, val) return self.write def write(self, data): if not self.started: self.started = True self.request.write(data) startup = None cleanup = None wsgiapps = {} def handler(req): options = req.get_options() # Run a startup function if requested. global startup if 'wsgi.startup' in options and not startup: func = options['wsgi.startup'] if func: module_name, object_str = func.split('::', 1) module = __import__(module_name, globals(), locals(), ['']) startup = apache.resolve_object(module, object_str) startup(req) # Register a cleanup function if requested. global cleanup if 'wsgi.cleanup' in options and not cleanup: func = options['wsgi.cleanup'] if func: module_name, object_str = func.split('::', 1) module = __import__(module_name, globals(), locals(), ['']) cleanup = apache.resolve_object(module, object_str) def cleaner(data): cleanup() try: # apache.register_cleanup wasn't available until 3.1.4. apache.register_cleanup(cleaner) except AttributeError: req.server.register_cleanup(req, cleaner) # Import the wsgi 'application' callable and pass it to Handler.run global wsgiapps appini = options.get('paste.ini') app = None if appini: if appini not in wsgiapps: wsgiapps[appini] = loadapp("config:%s" % appini) app = wsgiapps[appini] # Import the wsgi 'application' callable and pass it to Handler.run appwsgi = options.get('wsgi.application') if appwsgi and not appini: modname, objname = appwsgi.split('::', 1) module = __import__(modname, globals(), locals(), ['']) app = getattr(module, objname) Handler(req).run(app) # status was set in Handler; always return apache.OK return apache.OK Paste-1.7.5.1/paste/util/0000775000175000017500000000000011354703364013444 5ustar ianbianbPaste-1.7.5.1/paste/util/PySourceColor.py0000664000175000017500000024755111354703364016604 0ustar ianbianb# -*- coding: Latin-1 -*- """ PySourceColor: color Python source code """ """ PySourceColor.py ---------------------------------------------------------------------------- A python source to colorized html/css/xhtml converter. Hacked by M.E.Farmer Jr. 2004, 2005 Python license ---------------------------------------------------------------------------- - HTML markup does not create w3c valid html, but it works on every browser i've tried so far.(I.E.,Mozilla/Firefox,Opera,Konqueror,wxHTML). - CSS markup is w3c validated html 4.01 strict, but will not render correctly on all browsers. - XHTML markup is w3c validated xhtml 1.0 strict, like html 4.01, will not render correctly on all browsers. ---------------------------------------------------------------------------- Features: -Three types of markup: html (default) css/html 4.01 strict xhtml 1.0 strict -Can tokenize and colorize: 12 types of strings 2 comment types numbers operators brackets math operators class / name def / name decorator / name keywords arguments class/def/decorator linenumbers names text -Eight colorschemes built-in: null mono lite (default) dark dark2 idle viewcvs pythonwin -Header and footer set to '' for builtin header / footer. give path to a file containing the html you want added as header or footer. -Arbitrary text and html html markup converts all to raw (TEXT token) #@# for raw -> send raw text. #$# for span -> inline html and text. #%# for div -> block level html and text. -Linenumbers Supports all styles. New token is called LINENUMBER. Defaults to NAME if not defined. Style options -ALL markups support these text styles: b = bold i = italic u = underline -CSS and XHTML has limited support for borders: HTML markup functions will ignore these. Optional: Border color in RGB hex Defaults to the text forecolor. #rrggbb = border color Border size: l = thick m = medium t = thin Border type: - = dashed . = dotted s = solid d = double g = groove r = ridge n = inset o = outset You can specify multiple sides, they will all use the same style. Optional: Default is full border. v = bottom < = left > = right ^ = top NOTE: Specify the styles you want. The markups will ignore unsupported styles Also note not all browsers can show these options -All tokens default to NAME if not defined so the only absolutely critical ones to define are: NAME, ERRORTOKEN, PAGEBACKGROUND ---------------------------------------------------------------------------- Example usage:: # import import PySourceColor as psc psc.convert('c:/Python22/PySourceColor.py', colors=psc.idle, show=1) # from module import * from PySourceColor import * convert('c:/Python22/Lib', colors=lite, markup="css", header='#$#This is a simpe heading
') # How to use a custom colorscheme, and most of the 'features' from PySourceColor import * new = { ERRORTOKEN: ('bui','#FF8080',''), DECORATOR_NAME: ('s','#AACBBC',''), DECORATOR: ('n','#333333',''), NAME: ('t.','#000000','#FFFFFF'), TRIPLEDOUBLEQUOTE_R: ('tub','#000000','#FFFFFF'), TRIPLEDOUBLEQUOTE_U: ('-', '#CCAABB','#FFFAFF'), LINENUMBER: ('ib-','#ff66aa','#7733FF'),] TEXT: ('','#546634',''), PAGEBACKGROUND: '#FFFAAA', } if __name__ == '__main__': import sys convert(sys.argv[1], './xhtml.html', colors=new, markup='xhtml', show=1, linenumbers=1) convert(sys.argv[1], './html.html', colors=new, markup='html', show=1, linenumbers=1) """ __all__ = ['ERRORTOKEN','DECORATOR_NAME', 'DECORATOR', 'ARGS', 'EXTRASPACE', 'NAME', 'NUMBER', 'OPERATOR', 'COMMENT', 'MATH_OPERATOR', 'DOUBLECOMMENT', 'CLASS_NAME', 'DEF_NAME', 'KEYWORD', 'BRACKETS', 'SINGLEQUOTE','SINGLEQUOTE_R','SINGLEQUOTE_U','DOUBLEQUOTE', 'DOUBLEQUOTE_R', 'DOUBLEQUOTE_U', 'TRIPLESINGLEQUOTE', 'TEXT', 'TRIPLESINGLEQUOTE_R', 'TRIPLESINGLEQUOTE_U', 'TRIPLEDOUBLEQUOTE', 'TRIPLEDOUBLEQUOTE_R', 'TRIPLEDOUBLEQUOTE_U', 'PAGEBACKGROUND', 'LINENUMBER', 'CODESTART', 'CODEEND', 'PY', 'TOKEN_NAMES', 'CSSHOOK', 'null', 'mono', 'lite', 'dark','dark2', 'pythonwin','idle', 'viewcvs', 'Usage', 'cli', 'str2stdout', 'path2stdout', 'Parser', 'str2file', 'str2html', 'str2css', 'str2markup', 'path2file', 'path2html', 'convert', 'walkdir', 'defaultColors', 'showpage', 'pageconvert','tagreplace', 'MARKUPDICT'] __title__ = 'PySourceColor' __version__ = "2.1a" __date__ = '25 April 2005' __author__ = "M.E.Farmer Jr." __credits__ = '''This was originally based on a python recipe submitted by Jürgen Hermann to ASPN. Now based on the voices in my head. M.E.Farmer 2004, 2005 Python license ''' import os import sys import time import glob import getopt import keyword import token import tokenize import traceback try : import cStringIO as StringIO except: import StringIO # Do not edit NAME = token.NAME NUMBER = token.NUMBER COMMENT = tokenize.COMMENT OPERATOR = token.OP ERRORTOKEN = token.ERRORTOKEN ARGS = token.NT_OFFSET + 1 DOUBLECOMMENT = token.NT_OFFSET + 2 CLASS_NAME = token.NT_OFFSET + 3 DEF_NAME = token.NT_OFFSET + 4 KEYWORD = token.NT_OFFSET + 5 SINGLEQUOTE = token.NT_OFFSET + 6 SINGLEQUOTE_R = token.NT_OFFSET + 7 SINGLEQUOTE_U = token.NT_OFFSET + 8 DOUBLEQUOTE = token.NT_OFFSET + 9 DOUBLEQUOTE_R = token.NT_OFFSET + 10 DOUBLEQUOTE_U = token.NT_OFFSET + 11 TRIPLESINGLEQUOTE = token.NT_OFFSET + 12 TRIPLESINGLEQUOTE_R = token.NT_OFFSET + 13 TRIPLESINGLEQUOTE_U = token.NT_OFFSET + 14 TRIPLEDOUBLEQUOTE = token.NT_OFFSET + 15 TRIPLEDOUBLEQUOTE_R = token.NT_OFFSET + 16 TRIPLEDOUBLEQUOTE_U = token.NT_OFFSET + 17 PAGEBACKGROUND = token.NT_OFFSET + 18 DECORATOR = token.NT_OFFSET + 19 DECORATOR_NAME = token.NT_OFFSET + 20 BRACKETS = token.NT_OFFSET + 21 MATH_OPERATOR = token.NT_OFFSET + 22 LINENUMBER = token.NT_OFFSET + 23 TEXT = token.NT_OFFSET + 24 PY = token.NT_OFFSET + 25 CODESTART = token.NT_OFFSET + 26 CODEEND = token.NT_OFFSET + 27 CSSHOOK = token.NT_OFFSET + 28 EXTRASPACE = token.NT_OFFSET + 29 # markup classname lookup MARKUPDICT = { ERRORTOKEN: 'py_err', DECORATOR_NAME: 'py_decn', DECORATOR: 'py_dec', ARGS: 'py_args', NAME: 'py_name', NUMBER: 'py_num', OPERATOR: 'py_op', COMMENT: 'py_com', DOUBLECOMMENT: 'py_dcom', CLASS_NAME: 'py_clsn', DEF_NAME: 'py_defn', KEYWORD: 'py_key', SINGLEQUOTE: 'py_sq', SINGLEQUOTE_R: 'py_sqr', SINGLEQUOTE_U: 'py_squ', DOUBLEQUOTE: 'py_dq', DOUBLEQUOTE_R: 'py_dqr', DOUBLEQUOTE_U: 'py_dqu', TRIPLESINGLEQUOTE: 'py_tsq', TRIPLESINGLEQUOTE_R: 'py_tsqr', TRIPLESINGLEQUOTE_U: 'py_tsqu', TRIPLEDOUBLEQUOTE: 'py_tdq', TRIPLEDOUBLEQUOTE_R: 'py_tdqr', TRIPLEDOUBLEQUOTE_U: 'py_tdqu', BRACKETS: 'py_bra', MATH_OPERATOR: 'py_mop', LINENUMBER: 'py_lnum', TEXT: 'py_text', } # might help users that want to create custom schemes TOKEN_NAMES= { ERRORTOKEN:'ERRORTOKEN', DECORATOR_NAME:'DECORATOR_NAME', DECORATOR:'DECORATOR', ARGS:'ARGS', NAME:'NAME', NUMBER:'NUMBER', OPERATOR:'OPERATOR', COMMENT:'COMMENT', DOUBLECOMMENT:'DOUBLECOMMENT', CLASS_NAME:'CLASS_NAME', DEF_NAME:'DEF_NAME', KEYWORD:'KEYWORD', SINGLEQUOTE:'SINGLEQUOTE', SINGLEQUOTE_R:'SINGLEQUOTE_R', SINGLEQUOTE_U:'SINGLEQUOTE_U', DOUBLEQUOTE:'DOUBLEQUOTE', DOUBLEQUOTE_R:'DOUBLEQUOTE_R', DOUBLEQUOTE_U:'DOUBLEQUOTE_U', TRIPLESINGLEQUOTE:'TRIPLESINGLEQUOTE', TRIPLESINGLEQUOTE_R:'TRIPLESINGLEQUOTE_R', TRIPLESINGLEQUOTE_U:'TRIPLESINGLEQUOTE_U', TRIPLEDOUBLEQUOTE:'TRIPLEDOUBLEQUOTE', TRIPLEDOUBLEQUOTE_R:'TRIPLEDOUBLEQUOTE_R', TRIPLEDOUBLEQUOTE_U:'TRIPLEDOUBLEQUOTE_U', BRACKETS:'BRACKETS', MATH_OPERATOR:'MATH_OPERATOR', LINENUMBER:'LINENUMBER', TEXT:'TEXT', PAGEBACKGROUND:'PAGEBACKGROUND', } ###################################################################### # Edit colors and styles to taste # Create your own scheme, just copy one below , rename and edit. # Custom styles must at least define NAME, ERRORTOKEN, PAGEBACKGROUND, # all missing elements will default to NAME. # See module docstring for details on style attributes. ###################################################################### # Copy null and use it as a starter colorscheme. null = {# tokentype: ('tags border_color', 'textforecolor', 'textbackcolor') ERRORTOKEN: ('','#000000',''),# Error token DECORATOR_NAME: ('','#000000',''),# Decorator name DECORATOR: ('','#000000',''),# @ symbol ARGS: ('','#000000',''),# class,def,deco arguments NAME: ('','#000000',''),# All other python text NUMBER: ('','#000000',''),# 0->10 OPERATOR: ('','#000000',''),# ':','<=',';',',','.','==', etc MATH_OPERATOR: ('','#000000',''),# '+','-','=','','**',etc BRACKETS: ('','#000000',''),# '[',']','(',')','{','}' COMMENT: ('','#000000',''),# Single comment DOUBLECOMMENT: ('','#000000',''),## Double comment CLASS_NAME: ('','#000000',''),# Class name DEF_NAME: ('','#000000',''),# Def name KEYWORD: ('','#000000',''),# Python keywords SINGLEQUOTE: ('','#000000',''),# 'SINGLEQUOTE' SINGLEQUOTE_R: ('','#000000',''),# r'SINGLEQUOTE' SINGLEQUOTE_U: ('','#000000',''),# u'SINGLEQUOTE' DOUBLEQUOTE: ('','#000000',''),# "DOUBLEQUOTE" DOUBLEQUOTE_R: ('','#000000',''),# r"DOUBLEQUOTE" DOUBLEQUOTE_U: ('','#000000',''),# u"DOUBLEQUOTE" TRIPLESINGLEQUOTE: ('','#000000',''),# '''TRIPLESINGLEQUOTE''' TRIPLESINGLEQUOTE_R: ('','#000000',''),# r'''TRIPLESINGLEQUOTE''' TRIPLESINGLEQUOTE_U: ('','#000000',''),# u'''TRIPLESINGLEQUOTE''' TRIPLEDOUBLEQUOTE: ('','#000000',''),# """TRIPLEDOUBLEQUOTE""" TRIPLEDOUBLEQUOTE_R: ('','#000000',''),# r"""TRIPLEDOUBLEQUOTE""" TRIPLEDOUBLEQUOTE_U: ('','#000000',''),# u"""TRIPLEDOUBLEQUOTE""" TEXT: ('','#000000',''),# non python text LINENUMBER: ('>ti#555555','#000000',''),# Linenumbers PAGEBACKGROUND: '#FFFFFF'# set the page background } mono = { ERRORTOKEN: ('s#FF0000','#FF8080',''), DECORATOR_NAME: ('bu','#000000',''), DECORATOR: ('b','#000000',''), ARGS: ('b','#555555',''), NAME: ('','#000000',''), NUMBER: ('b','#000000',''), OPERATOR: ('b','#000000',''), MATH_OPERATOR: ('b','#000000',''), BRACKETS: ('b','#000000',''), COMMENT: ('i','#999999',''), DOUBLECOMMENT: ('b','#999999',''), CLASS_NAME: ('bu','#000000',''), DEF_NAME: ('b','#000000',''), KEYWORD: ('b','#000000',''), SINGLEQUOTE: ('','#000000',''), SINGLEQUOTE_R: ('','#000000',''), SINGLEQUOTE_U: ('','#000000',''), DOUBLEQUOTE: ('','#000000',''), DOUBLEQUOTE_R: ('','#000000',''), DOUBLEQUOTE_U: ('','#000000',''), TRIPLESINGLEQUOTE: ('','#000000',''), TRIPLESINGLEQUOTE_R: ('','#000000',''), TRIPLESINGLEQUOTE_U: ('','#000000',''), TRIPLEDOUBLEQUOTE: ('i','#000000',''), TRIPLEDOUBLEQUOTE_R: ('i','#000000',''), TRIPLEDOUBLEQUOTE_U: ('i','#000000',''), TEXT: ('','#000000',''), LINENUMBER: ('>ti#555555','#000000',''), PAGEBACKGROUND: '#FFFFFF' } dark = { ERRORTOKEN: ('s#FF0000','#FF8080',''), DECORATOR_NAME: ('b','#FFBBAA',''), DECORATOR: ('b','#CC5511',''), ARGS: ('b','#DDDDFF',''), NAME: ('','#DDDDDD',''), NUMBER: ('','#FF0000',''), OPERATOR: ('b','#FAF785',''), MATH_OPERATOR: ('b','#FAF785',''), BRACKETS: ('b','#FAF785',''), COMMENT: ('','#45FCA0',''), DOUBLECOMMENT: ('i','#A7C7A9',''), CLASS_NAME: ('b','#B666FD',''), DEF_NAME: ('b','#EBAE5C',''), KEYWORD: ('b','#8680FF',''), SINGLEQUOTE: ('','#F8BAFE',''), SINGLEQUOTE_R: ('','#F8BAFE',''), SINGLEQUOTE_U: ('','#F8BAFE',''), DOUBLEQUOTE: ('','#FF80C0',''), DOUBLEQUOTE_R: ('','#FF80C0',''), DOUBLEQUOTE_U: ('','#FF80C0',''), TRIPLESINGLEQUOTE: ('','#FF9595',''), TRIPLESINGLEQUOTE_R: ('','#FF9595',''), TRIPLESINGLEQUOTE_U: ('','#FF9595',''), TRIPLEDOUBLEQUOTE: ('','#B3FFFF',''), TRIPLEDOUBLEQUOTE_R: ('','#B3FFFF',''), TRIPLEDOUBLEQUOTE_U: ('','#B3FFFF',''), TEXT: ('','#FFFFFF',''), LINENUMBER: ('>mi#555555','#bbccbb','#333333'), PAGEBACKGROUND: '#000000' } dark2 = { ERRORTOKEN: ('','#FF0000',''), DECORATOR_NAME: ('b','#FFBBAA',''), DECORATOR: ('b','#CC5511',''), ARGS: ('b','#DDDDDD',''), NAME: ('','#C0C0C0',''), NUMBER: ('b','#00FF00',''), OPERATOR: ('b','#FF090F',''), MATH_OPERATOR: ('b','#EE7020',''), BRACKETS: ('b','#FFB90F',''), COMMENT: ('i','#D0D000','#522000'),#'#88AA88','#11111F'), DOUBLECOMMENT: ('i','#D0D000','#522000'),#'#77BB77','#11111F'), CLASS_NAME: ('b','#DD4080',''), DEF_NAME: ('b','#FF8040',''), KEYWORD: ('b','#4726d1',''), SINGLEQUOTE: ('','#8080C0',''), SINGLEQUOTE_R: ('','#8080C0',''), SINGLEQUOTE_U: ('','#8080C0',''), DOUBLEQUOTE: ('','#ADB9F1',''), DOUBLEQUOTE_R: ('','#ADB9F1',''), DOUBLEQUOTE_U: ('','#ADB9F1',''), TRIPLESINGLEQUOTE: ('','#00C1C1',''),#A050C0 TRIPLESINGLEQUOTE_R: ('','#00C1C1',''),#A050C0 TRIPLESINGLEQUOTE_U: ('','#00C1C1',''),#A050C0 TRIPLEDOUBLEQUOTE: ('','#33E3E3',''),#B090E0 TRIPLEDOUBLEQUOTE_R: ('','#33E3E3',''),#B090E0 TRIPLEDOUBLEQUOTE_U: ('','#33E3E3',''),#B090E0 TEXT: ('','#C0C0C0',''), LINENUMBER: ('>mi#555555','#bbccbb','#333333'), PAGEBACKGROUND: '#000000' } lite = { ERRORTOKEN: ('s#FF0000','#FF8080',''), DECORATOR_NAME: ('b','#BB4422',''), DECORATOR: ('b','#3333AF',''), ARGS: ('b','#000000',''), NAME: ('','#333333',''), NUMBER: ('b','#DD2200',''), OPERATOR: ('b','#000000',''), MATH_OPERATOR: ('b','#000000',''), BRACKETS: ('b','#000000',''), COMMENT: ('','#007F00',''), DOUBLECOMMENT: ('','#608060',''), CLASS_NAME: ('b','#0000DF',''), DEF_NAME: ('b','#9C7A00',''),#f09030 KEYWORD: ('b','#0000AF',''), SINGLEQUOTE: ('','#600080',''), SINGLEQUOTE_R: ('','#600080',''), SINGLEQUOTE_U: ('','#600080',''), DOUBLEQUOTE: ('','#A0008A',''), DOUBLEQUOTE_R: ('','#A0008A',''), DOUBLEQUOTE_U: ('','#A0008A',''), TRIPLESINGLEQUOTE: ('','#337799',''), TRIPLESINGLEQUOTE_R: ('','#337799',''), TRIPLESINGLEQUOTE_U: ('','#337799',''), TRIPLEDOUBLEQUOTE: ('','#1166AA',''), TRIPLEDOUBLEQUOTE_R: ('','#1166AA',''), TRIPLEDOUBLEQUOTE_U: ('','#1166AA',''), TEXT: ('','#000000',''), LINENUMBER: ('>ti#555555','#000000',''), PAGEBACKGROUND: '#FFFFFF' } idle = { ERRORTOKEN: ('s#FF0000','#FF8080',''), DECORATOR_NAME: ('','#900090',''), DECORATOR: ('','#FF7700',''), NAME: ('','#000000',''), NUMBER: ('','#000000',''), OPERATOR: ('','#000000',''), MATH_OPERATOR: ('','#000000',''), BRACKETS: ('','#000000',''), COMMENT: ('','#DD0000',''), DOUBLECOMMENT: ('','#DD0000',''), CLASS_NAME: ('','#0000FF',''), DEF_NAME: ('','#0000FF',''), KEYWORD: ('','#FF7700',''), SINGLEQUOTE: ('','#00AA00',''), SINGLEQUOTE_R: ('','#00AA00',''), SINGLEQUOTE_U: ('','#00AA00',''), DOUBLEQUOTE: ('','#00AA00',''), DOUBLEQUOTE_R: ('','#00AA00',''), DOUBLEQUOTE_U: ('','#00AA00',''), TRIPLESINGLEQUOTE: ('','#00AA00',''), TRIPLESINGLEQUOTE_R: ('','#00AA00',''), TRIPLESINGLEQUOTE_U: ('','#00AA00',''), TRIPLEDOUBLEQUOTE: ('','#00AA00',''), TRIPLEDOUBLEQUOTE_R: ('','#00AA00',''), TRIPLEDOUBLEQUOTE_U: ('','#00AA00',''), TEXT: ('','#000000',''), LINENUMBER: ('>ti#555555','#000000',''), PAGEBACKGROUND: '#FFFFFF' } pythonwin = { ERRORTOKEN: ('s#FF0000','#FF8080',''), DECORATOR_NAME: ('b','#DD0080',''), DECORATOR: ('b','#000080',''), ARGS: ('','#000000',''), NAME: ('','#303030',''), NUMBER: ('','#008080',''), OPERATOR: ('','#000000',''), MATH_OPERATOR: ('','#000000',''), BRACKETS: ('','#000000',''), COMMENT: ('','#007F00',''), DOUBLECOMMENT: ('','#7F7F7F',''), CLASS_NAME: ('b','#0000FF',''), DEF_NAME: ('b','#007F7F',''), KEYWORD: ('b','#000080',''), SINGLEQUOTE: ('','#808000',''), SINGLEQUOTE_R: ('','#808000',''), SINGLEQUOTE_U: ('','#808000',''), DOUBLEQUOTE: ('','#808000',''), DOUBLEQUOTE_R: ('','#808000',''), DOUBLEQUOTE_U: ('','#808000',''), TRIPLESINGLEQUOTE: ('','#808000',''), TRIPLESINGLEQUOTE_R: ('','#808000',''), TRIPLESINGLEQUOTE_U: ('','#808000',''), TRIPLEDOUBLEQUOTE: ('','#808000',''), TRIPLEDOUBLEQUOTE_R: ('','#808000',''), TRIPLEDOUBLEQUOTE_U: ('','#808000',''), TEXT: ('','#303030',''), LINENUMBER: ('>ti#555555','#000000',''), PAGEBACKGROUND: '#FFFFFF' } viewcvs = { ERRORTOKEN: ('s#FF0000','#FF8080',''), DECORATOR_NAME: ('','#000000',''), DECORATOR: ('','#000000',''), ARGS: ('','#000000',''), NAME: ('','#000000',''), NUMBER: ('','#000000',''), OPERATOR: ('','#000000',''), MATH_OPERATOR: ('','#000000',''), BRACKETS: ('','#000000',''), COMMENT: ('i','#b22222',''), DOUBLECOMMENT: ('i','#b22222',''), CLASS_NAME: ('','#000000',''), DEF_NAME: ('b','#0000ff',''), KEYWORD: ('b','#a020f0',''), SINGLEQUOTE: ('b','#bc8f8f',''), SINGLEQUOTE_R: ('b','#bc8f8f',''), SINGLEQUOTE_U: ('b','#bc8f8f',''), DOUBLEQUOTE: ('b','#bc8f8f',''), DOUBLEQUOTE_R: ('b','#bc8f8f',''), DOUBLEQUOTE_U: ('b','#bc8f8f',''), TRIPLESINGLEQUOTE: ('b','#bc8f8f',''), TRIPLESINGLEQUOTE_R: ('b','#bc8f8f',''), TRIPLESINGLEQUOTE_U: ('b','#bc8f8f',''), TRIPLEDOUBLEQUOTE: ('b','#bc8f8f',''), TRIPLEDOUBLEQUOTE_R: ('b','#bc8f8f',''), TRIPLEDOUBLEQUOTE_U: ('b','#bc8f8f',''), TEXT: ('','#000000',''), LINENUMBER: ('>ti#555555','#000000',''), PAGEBACKGROUND: '#FFFFFF' } defaultColors = lite def Usage(): doc = """ ----------------------------------------------------------------------------- PySourceColor.py ver: %s ----------------------------------------------------------------------------- Module summary: This module is designed to colorize python source code. Input--->python source Output-->colorized (html, html4.01/css, xhtml1.0) Standalone: This module will work from the command line with options. This module will work with redirected stdio. Imported: This module can be imported and used directly in your code. ----------------------------------------------------------------------------- Command line options: -h, --help Optional-> Display this help message. -t, --test Optional-> Will ignore all others flags but --profile test all schemes and markup combinations -p, --profile Optional-> Works only with --test or -t runs profile.py and makes the test work in quiet mode. -i, --in, --input Optional-> If you give input on stdin. Use any of these for the current dir (.,cwd) Input can be file or dir. Input from stdin use one of the following (-,stdin) If stdin is used as input stdout is output unless specified. -o, --out, --output Optional-> output dir for the colorized source. default: output dir is the input dir. To output html to stdout use one of the following (-,stdout) Stdout can be used without stdin if you give a file as input. -c, --color Optional-> null, mono, dark, dark2, lite, idle, pythonwin, viewcvs default: dark -s, --show Optional-> Show page after creation. default: no show -m, --markup Optional-> html, css, xhtml css, xhtml also support external stylesheets (-e,--external) default: HTML -e, --external Optional-> use with css, xhtml Writes an style sheet instead of embedding it in the page saves it as pystyle.css in the same directory. html markup will silently ignore this flag. -H, --header Opional-> add a page header to the top of the output -H Builtin header (name,date,hrule) --header You must specify a filename. The header file must be valid html and must handle its own font colors. ex. --header c:/tmp/header.txt -F, --footer Opional-> add a page footer to the bottom of the output -F Builtin footer (hrule,name,date) --footer You must specify a filename. The footer file must be valid html and must handle its own font colors. ex. --footer c:/tmp/footer.txt -l, --linenumbers Optional-> default is no linenumbers Adds line numbers to the start of each line in the code. --convertpage Given a webpage that has code embedded in tags it will convert embedded code to colorized html. (see pageconvert for details) ----------------------------------------------------------------------------- Option usage: # Test and show pages python PySourceColor.py -t -s # Test and only show profile results python PySourceColor.py -t -p # Colorize all .py,.pyw files in cwdir you can also use: (.,cwd) python PySourceColor.py -i . # Using long options w/ = python PySourceColor.py --in=c:/myDir/my.py --color=lite --show # Using short options w/out = python PySourceColor.py -i c:/myDir/ -c idle -m css -e # Using any mix python PySourceColor.py --in . -o=c:/myDir --show # Place a custom header on your files python PySourceColor.py -i . -o c:/tmp -m xhtml --header c:/header.txt ----------------------------------------------------------------------------- Stdio usage: # Stdio using no options python PySourceColor.py < c:/MyFile.py > c:/tmp/MyFile.html # Using stdin alone automatically uses stdout for output: (stdin,-) python PySourceColor.py -i- < c:/MyFile.py > c:/tmp/myfile.html # Stdout can also be written to directly from a file instead of stdin python PySourceColor.py -i c:/MyFile.py -m css -o- > c:/tmp/myfile.html # Stdin can be used as input , but output can still be specified python PySourceColor.py -i- -o c:/pydoc.py.html -s < c:/Python22/my.py _____________________________________________________________________________ """ print doc % (__version__) sys.exit(1) ###################################################### Command line interface def cli(): """Handle command line args and redirections""" try: # try to get command line args opts, args = getopt.getopt(sys.argv[1:], "hseqtplHFi:o:c:m:h:f:",["help", "show", "quiet", "test", "external", "linenumbers", "convertpage", "profile", "input=", "output=", "color=", "markup=","header=", "footer="]) except getopt.GetoptError: # on error print help information and exit: Usage() # init some names input = None output = None colorscheme = None markup = 'html' header = None footer = None linenumbers = 0 show = 0 quiet = 0 test = 0 profile = 0 convertpage = 0 form = None # if we have args then process them for o, a in opts: if o in ["-h", "--help"]: Usage() sys.exit() if o in ["-o", "--output", "--out"]: output = a if o in ["-i", "--input", "--in"]: input = a if input in [".", "cwd"]: input = os.getcwd() if o in ["-s", "--show"]: show = 1 if o in ["-q", "--quiet"]: quiet = 1 if o in ["-t", "--test"]: test = 1 if o in ["--convertpage"]: convertpage = 1 if o in ["-p", "--profile"]: profile = 1 if o in ["-e", "--external"]: form = 'external' if o in ["-m", "--markup"]: markup = str(a) if o in ["-l", "--linenumbers"]: linenumbers = 1 if o in ["--header"]: header = str(a) elif o == "-H": header = '' if o in ["--footer"]: footer = str(a) elif o == "-F": footer = '' if o in ["-c", "--color"]: try: colorscheme = globals().get(a.lower()) except: traceback.print_exc() Usage() if test: if profile: import profile profile.run('_test(show=%s, quiet=%s)'%(show,quiet)) else: # Parse this script in every possible colorscheme and markup _test(show,quiet) elif input in [None, "-", "stdin"] or output in ["-", "stdout"]: # determine if we are going to use stdio if input not in [None, "-", "stdin"]: if os.path.isfile(input) : path2stdout(input, colors=colorscheme, markup=markup, linenumbers=linenumbers, header=header, footer=footer, form=form) else: raise PathError, 'File does not exists!' else: try: if sys.stdin.isatty(): raise InputError, 'Please check input!' else: if output in [None,"-","stdout"]: str2stdout(sys.stdin.read(), colors=colorscheme, markup=markup, header=header, footer=footer, linenumbers=linenumbers, form=form) else: str2file(sys.stdin.read(), outfile=output, show=show, markup=markup, header=header, footer=footer, linenumbers=linenumbers, form=form) except: traceback.print_exc() Usage() else: if os.path.exists(input): if convertpage: # if there was at least an input given we can proceed pageconvert(input, out=output, colors=colorscheme, show=show, markup=markup,linenumbers=linenumbers) else: # if there was at least an input given we can proceed convert(source=input, outdir=output, colors=colorscheme, show=show, markup=markup, quiet=quiet, header=header, footer=footer, linenumbers=linenumbers, form=form) else: raise PathError, 'File does not exists!' Usage() ######################################################### Simple markup tests def _test(show=0, quiet=0): """Test the parser and most of the functions. There are 19 test total(eight colorschemes in three diffrent markups, and a str2file test. Most functions are tested by this. """ fi = sys.argv[0] if not fi.endswith('.exe'):# Do not test if frozen as an archive # this is a collection of test, most things are covered. path2file(fi, '/tmp/null.html', null, show=show, quiet=quiet) path2file(fi, '/tmp/null_css.html', null, show=show, markup='css', quiet=quiet) path2file(fi, '/tmp/mono.html', mono, show=show, quiet=quiet) path2file(fi, '/tmp/mono_css.html', mono, show=show, markup='css', quiet=quiet) path2file(fi, '/tmp/lite.html', lite, show=show, quiet=quiet) path2file(fi, '/tmp/lite_css.html', lite, show=show, markup='css', quiet=quiet, header='', footer='', linenumbers=1) path2file(fi, '/tmp/lite_xhtml.html', lite, show=show, markup='xhtml', quiet=quiet) path2file(fi, '/tmp/dark.html', dark, show=show, quiet=quiet) path2file(fi, '/tmp/dark_css.html', dark, show=show, markup='css', quiet=quiet, linenumbers=1) path2file(fi, '/tmp/dark2.html', dark2, show=show, quiet=quiet) path2file(fi, '/tmp/dark2_css.html', dark2, show=show, markup='css', quiet=quiet) path2file(fi, '/tmp/dark2_xhtml.html', dark2, show=show, markup='xhtml', quiet=quiet, header='', footer='', linenumbers=1, form='external') path2file(fi, '/tmp/idle.html', idle, show=show, quiet=quiet) path2file(fi, '/tmp/idle_css.html', idle, show=show, markup='css', quiet=quiet) path2file(fi, '/tmp/viewcvs.html', viewcvs, show=show, quiet=quiet, linenumbers=1) path2file(fi, '/tmp/viewcvs_css.html', viewcvs, show=show, markup='css', linenumbers=1, quiet=quiet) path2file(fi, '/tmp/pythonwin.html', pythonwin, show=show, quiet=quiet) path2file(fi, '/tmp/pythonwin_css.html', pythonwin, show=show, markup='css', quiet=quiet) teststr=r'''"""This is a test of decorators and other things""" # This should be line 421... @whatever(arg,arg2) @A @B(arghh) @C def LlamaSaysNi(arg='Ni!',arg2="RALPH"): """This docstring is deeply disturbed by all the llama references""" print '%s The Wonder Llama says %s'% (arg2,arg) # So I was like duh!, and he was like ya know?!, # and so we were both like huh...wtf!? RTFM!! LOL!!;) @staticmethod## Double comments are KewL. def LlamasRLumpy(): """This docstring is too sexy to be here. """ u""" ============================= A Møøse once bit my sister... ============================= """ ## Relax, this won't hurt a bit, just a simple, painless procedure, ## hold still while I get the anesthetizing hammer. m = {'three':'1','won':'2','too':'3'} o = r'fishy\fishy\fishy/fish\oh/where/is\my/little\..' python = uR""" No realli! She was Karving her initials øn the møøse with the sharpened end of an interspace tøøthbrush given her by Svenge - her brother-in-law -an Oslo dentist and star of many Norwegian møvies: "The Høt Hands of an Oslo Dentist", "Fillings of Passion", "The Huge Mølars of Horst Nordfink"...""" RU"""142 MEXICAN WHOOPING LLAMAS"""#<-Can you fit 142 llamas in a red box? n = u' HERMSGERVØRDENBRØTBØRDA ' + """ YUTTE """ t = """SAMALLNIATNUOMNAIRODAUCE"""+"DENIARTYLLAICEPS04" ## We apologise for the fault in the ## comments. Those responsible have been ## sacked. y = '14 NORTH CHILEAN GUANACOS \ (CLOSELY RELATED TO THE LLAMA)' rules = [0,1,2,3,4,5] print y''' htmlPath = os.path.abspath('/tmp/strtest_lines.html') str2file(teststr, htmlPath, colors=dark, markup='xhtml', linenumbers=420, show=show) _printinfo(" wrote %s" % htmlPath, quiet) htmlPath = os.path.abspath('/tmp/strtest_nolines.html') str2file(teststr, htmlPath, colors=dark, markup='xhtml', show=show) _printinfo(" wrote %s" % htmlPath, quiet) else: Usage() return # emacs wants this: ' ####################################################### User funtctions def str2stdout(sourcestring, colors=None, title='', markup='html', header=None, footer=None, linenumbers=0, form=None): """Converts a code(string) to colorized HTML. Writes to stdout. form='code',or'snip' (for "
yourcode
" only) colors=null,mono,lite,dark,dark2,idle,or pythonwin """ Parser(sourcestring, colors=colors, title=title, markup=markup, header=header, footer=footer, linenumbers=linenumbers).format(form) def path2stdout(sourcepath, title='', colors=None, markup='html', header=None, footer=None, linenumbers=0, form=None): """Converts code(file) to colorized HTML. Writes to stdout. form='code',or'snip' (for "
yourcode
" only) colors=null,mono,lite,dark,dark2,idle,or pythonwin """ sourcestring = open(sourcepath).read() Parser(sourcestring, colors=colors, title=sourcepath, markup=markup, header=header, footer=footer, linenumbers=linenumbers).format(form) def str2html(sourcestring, colors=None, title='', markup='html', header=None, footer=None, linenumbers=0, form=None): """Converts a code(string) to colorized HTML. Returns an HTML string. form='code',or'snip' (for "
yourcode
" only) colors=null,mono,lite,dark,dark2,idle,or pythonwin """ stringIO = StringIO.StringIO() Parser(sourcestring, colors=colors, title=title, out=stringIO, markup=markup, header=header, footer=footer, linenumbers=linenumbers).format(form) stringIO.seek(0) return stringIO.read() def str2css(sourcestring, colors=None, title='', markup='css', header=None, footer=None, linenumbers=0, form=None): """Converts a code string to colorized CSS/HTML. Returns CSS/HTML string If form != None then this will return (stylesheet_str, code_str) colors=null,mono,lite,dark,dark2,idle,or pythonwin """ if markup.lower() not in ['css' ,'xhtml']: markup = 'css' stringIO = StringIO.StringIO() parse = Parser(sourcestring, colors=colors, title=title, out=stringIO, markup=markup, header=header, footer=footer, linenumbers=linenumbers) parse.format(form) stringIO.seek(0) if form != None: return parse._sendCSSStyle(external=1), stringIO.read() else: return None, stringIO.read() def str2markup(sourcestring, colors=None, title = '', markup='xhtml', header=None, footer=None, linenumbers=0, form=None): """ Convert code strings into ([stylesheet or None], colorized string) """ if markup.lower() == 'html': return None, str2html(sourcestring, colors=colors, title=title, header=header, footer=footer, markup=markup, linenumbers=linenumbers, form=form) else: return str2css(sourcestring, colors=colors, title=title, header=header, footer=footer, markup=markup, linenumbers=linenumbers, form=form) def str2file(sourcestring, outfile, colors=None, title='', markup='html', header=None, footer=None, linenumbers=0, show=0, dosheet=1, form=None): """Converts a code string to a file. makes no attempt at correcting bad pathnames """ css , html = str2markup(sourcestring, colors=colors, title='', markup=markup, header=header, footer=footer, linenumbers=linenumbers, form=form) # write html f = open(outfile,'wt') f.writelines(html) f.close() #write css if css != None and dosheet: dir = os.path.dirname(outfile) outcss = os.path.join(dir,'pystyle.css') f = open(outcss,'wt') f.writelines(css) f.close() if show: showpage(outfile) def path2html(sourcepath, colors=None, markup='html', header=None, footer=None, linenumbers=0, form=None): """Converts code(file) to colorized HTML. Returns an HTML string. form='code',or'snip' (for "
yourcode
" only) colors=null,mono,lite,dark,dark2,idle,or pythonwin """ stringIO = StringIO.StringIO() sourcestring = open(sourcepath).read() Parser(sourcestring, colors, title=sourcepath, out=stringIO, markup=markup, header=header, footer=footer, linenumbers=linenumbers).format(form) stringIO.seek(0) return stringIO.read() def convert(source, outdir=None, colors=None, show=0, markup='html', quiet=0, header=None, footer=None, linenumbers=0, form=None): """Takes a file or dir as input and places the html in the outdir. If outdir is none it defaults to the input dir """ count=0 # If it is a filename then path2file if not os.path.isdir(source): if os.path.isfile(source): count+=1 path2file(source, outdir, colors, show, markup, quiet, form, header, footer, linenumbers, count) else: raise PathError, 'File does not exist!' # If we pass in a dir we need to walkdir for files. # Then we need to colorize them with path2file else: fileList = walkdir(source) if fileList != None: # make sure outdir is a dir if outdir != None: if os.path.splitext(outdir)[1] != '': outdir = os.path.split(outdir)[0] for item in fileList: count+=1 path2file(item, outdir, colors, show, markup, quiet, form, header, footer, linenumbers, count) _printinfo('Completed colorizing %s files.'%str(count), quiet) else: _printinfo("No files to convert in dir.", quiet) def path2file(sourcePath, out=None, colors=None, show=0, markup='html', quiet=0, form=None, header=None, footer=None, linenumbers=0, count=1): """ Converts python source to html file""" # If no outdir is given we use the sourcePath if out == None:#this is a guess htmlPath = sourcePath + '.html' else: # If we do give an out_dir, and it does # not exist , it will be created. if os.path.splitext(out)[1] == '': if not os.path.isdir(out): os.makedirs(out) sourceName = os.path.basename(sourcePath) htmlPath = os.path.join(out,sourceName)+'.html' # If we do give an out_name, and its dir does # not exist , it will be created. else: outdir = os.path.split(out)[0] if not os.path.isdir(outdir): os.makedirs(outdir) htmlPath = out htmlPath = os.path.abspath(htmlPath) # Open the text and do the parsing. source = open(sourcePath).read() parse = Parser(source, colors, sourcePath, open(htmlPath, 'wt'), markup, header, footer, linenumbers) parse.format(form) _printinfo(" wrote %s" % htmlPath, quiet) # html markup will ignore the external flag, but # we need to stop the blank file from being written. if form == 'external' and count == 1 and markup != 'html': cssSheet = parse._sendCSSStyle(external=1) cssPath = os.path.join(os.path.dirname(htmlPath),'pystyle.css') css = open(cssPath, 'wt') css.write(cssSheet) css.close() _printinfo(" wrote %s" % cssPath, quiet) if show: # load HTML page into the default web browser. showpage(htmlPath) return htmlPath def tagreplace(sourcestr, colors=lite, markup='xhtml', linenumbers=0, dosheet=1, tagstart=''.lower(), tagend=''.lower(), stylesheet='pystyle.css'): """This is a helper function for pageconvert. Returns css, page. """ if markup.lower() != 'html': link = '' css = link%stylesheet if sourcestr.find(css) == -1: sourcestr = sourcestr.replace('', css, 1) starttags = sourcestr.count(tagstart) endtags = sourcestr.count(tagend) if starttags: if starttags == endtags: for _ in range(starttags): datastart = sourcestr.find(tagstart) dataend = sourcestr.find(tagend) data = sourcestr[datastart+len(tagstart):dataend] data = unescape(data) css , data = str2markup(data, colors=colors, linenumbers=linenumbers, markup=markup, form='embed') start = sourcestr[:datastart] end = sourcestr[dataend+len(tagend):] sourcestr = ''.join([start,data,end]) else: raise InputError,'Tag mismatch!\nCheck %s,%s tags'%tagstart,tagend if not dosheet: css = None return css, sourcestr def pageconvert(path, out=None, colors=lite, markup='xhtml', linenumbers=0, dosheet=1, tagstart=''.lower(), tagend=''.lower(), stylesheet='pystyle', show=1, returnstr=0): """This function can colorize Python source that is written in a webpage enclosed in tags. """ if out == None: out = os.path.dirname(path) infile = open(path, 'r').read() css,page = tagreplace(sourcestr=infile,colors=colors, markup=markup, linenumbers=linenumbers, dosheet=dosheet, tagstart=tagstart, tagend=tagend, stylesheet=stylesheet) if not returnstr: newpath = os.path.abspath(os.path.join( out,'tmp', os.path.basename(path))) if not os.path.exists(newpath): try: os.makedirs(os.path.dirname(newpath)) except: pass#traceback.print_exc() #Usage() y = open(newpath, 'w') y.write(page) y.close() if css: csspath = os.path.abspath(os.path.join( out,'tmp','%s.css'%stylesheet)) x = open(csspath,'w') x.write(css) x.close() if show: try: os.startfile(newpath) except: traceback.print_exc() return newpath else: return css, page ##################################################################### helpers def walkdir(dir): """Return a list of .py and .pyw files from a given directory. This function can be written as a generator Python 2.3, or a genexp in Python 2.4. But 2.2 and 2.1 would be left out.... """ # Get a list of files that match *.py* GLOB_PATTERN = os.path.join(dir, "*.[p][y]*") pathlist = glob.glob(GLOB_PATTERN) # Now filter out all but py and pyw filterlist = [x for x in pathlist if x.endswith('.py') or x.endswith('.pyw')] if filterlist != []: # if we have a list send it return filterlist else: return None def showpage(path): """Helper function to open webpages""" try: import webbrowser webbrowser.open_new(os.path.abspath(path)) except: traceback.print_exc() def _printinfo(message, quiet): """Helper to print messages""" if not quiet: print message def escape(text): """escape text for html. similar to cgi.escape""" text = text.replace("&", "&") text = text.replace("<", "<") text = text.replace(">", ">") return text def unescape(text): """unsecape escaped text""" text = text.replace(""", '"') text = text.replace(">", ">") text = text.replace("<", "<") text = text.replace("&", "&") return text ########################################################### Custom Exceptions class PySourceColorError(Exception): # Base for custom errors def __init__(self, msg=''): self._msg = msg Exception.__init__(self, msg) def __repr__(self): return self._msg __str__ = __repr__ class PathError(PySourceColorError): def __init__(self, msg): PySourceColorError.__init__(self, 'Path error! : %s'% msg) class InputError(PySourceColorError): def __init__(self, msg): PySourceColorError.__init__(self, 'Input error! : %s'% msg) ########################################################## Python code parser class Parser(object): """MoinMoin python parser heavily chopped :)""" def __init__(self, raw, colors=None, title='', out=sys.stdout, markup='html', header=None, footer=None, linenumbers=0): """Store the source text & set some flags""" if colors == None: colors = defaultColors self.raw = raw.expandtabs().rstrip() self.title = os.path.basename(title) self.out = out self.line = '' self.lasttext = '' self.argFlag = 0 self.classFlag = 0 self.defFlag = 0 self.decoratorFlag = 0 self.external = 0 self.markup = markup.upper() self.colors = colors self.header = header self.footer = footer self.doArgs = 1 # overrides the new tokens self.doNames = 1 # overrides the new tokens self.doMathOps = 1 # overrides the new tokens self.doBrackets = 1 # overrides the new tokens self.doURL = 1 # override url conversion self.LINENUMHOLDER = "___line___".upper() self.LINESTART = "___start___".upper() self.skip = 0 # add space left side of code for padding.Override in color dict. self.extraspace = self.colors.get(EXTRASPACE, '') # Linenumbers less then zero also have numberlinks self.dolinenums = self.linenum = abs(linenumbers) if linenumbers < 0: self.numberlinks = 1 else: self.numberlinks = 0 def format(self, form=None): """Parse and send the colorized source""" if form in ('snip','code'): self.addEnds = 0 elif form == 'embed': self.addEnds = 0 self.external = 1 else: if form == 'external': self.external = 1 self.addEnds = 1 # Store line offsets in self.lines self.lines = [0, 0] pos = 0 # Add linenumbers if self.dolinenums: start=self.LINENUMHOLDER+' '+self.extraspace else: start=''+self.extraspace newlines = [] lines = self.raw.splitlines(0) for l in lines: # span and div escape for customizing and embedding raw text if (l.startswith('#$#') or l.startswith('#%#') or l.startswith('#@#')): newlines.append(l) else: # kludge for line spans in css,xhtml if self.markup in ['XHTML','CSS']: newlines.append(self.LINESTART+' '+start+l) else: newlines.append(start+l) self.raw = "\n".join(newlines)+'\n'# plus an extra newline at the end # Gather lines while 1: pos = self.raw.find('\n', pos) + 1 if not pos: break self.lines.append(pos) self.lines.append(len(self.raw)) # Wrap text in a filelike object self.pos = 0 text = StringIO.StringIO(self.raw) # Markup start if self.addEnds: self._doPageStart() else: self._doSnippetStart() ## Tokenize calls the __call__ ## function for each token till done. # Parse the source and write out the results. try: tokenize.tokenize(text.readline, self) except tokenize.TokenError, ex: msg = ex[0] line = ex[1][0] self.out.write("

ERROR: %s

%s\n"% (msg, self.raw[self.lines[line]:])) #traceback.print_exc() # Markup end if self.addEnds: self._doPageEnd() else: self._doSnippetEnd() def __call__(self, toktype, toktext, (srow,scol), (erow,ecol), line): """Token handler. Order is important do not rearrange.""" self.line = line # Calculate new positions oldpos = self.pos newpos = self.lines[srow] + scol self.pos = newpos + len(toktext) # Handle newlines if toktype in (token.NEWLINE, tokenize.NL): self.decoratorFlag = self.argFlag = 0 # kludge for line spans in css,xhtml if self.markup in ['XHTML','CSS']: self.out.write('') self.out.write('\n') return # Send the original whitespace, and tokenize backslashes if present. # Tokenizer.py just sends continued line backslashes with whitespace. # This is a hack to tokenize continued line slashes as operators. # Should continued line backslashes be treated as operators # or some other token? if newpos > oldpos: if self.raw[oldpos:newpos].isspace(): # consume a single space after linestarts and linenumbers # had to have them so tokenizer could seperate them. # multiline strings are handled by do_Text functions if self.lasttext != self.LINESTART \ and self.lasttext != self.LINENUMHOLDER: self.out.write(self.raw[oldpos:newpos]) else: self.out.write(self.raw[oldpos+1:newpos]) else: slash = self.raw[oldpos:newpos].find('\\')+oldpos self.out.write(self.raw[oldpos:slash]) getattr(self, '_send%sText'%(self.markup))(OPERATOR, '\\') self.linenum+=1 # kludge for line spans in css,xhtml if self.markup in ['XHTML','CSS']: self.out.write('') self.out.write(self.raw[slash+1:newpos]) # Skip indenting tokens if toktype in (token.INDENT, token.DEDENT): self.pos = newpos return # Look for operators if token.LPAR <= toktype and toktype <= token.OP: # Trap decorators py2.4 > if toktext == '@': toktype = DECORATOR # Set a flag if this was the decorator start so # the decorator name and arguments can be identified self.decoratorFlag = self.argFlag = 1 else: if self.doArgs: # Find the start for arguments if toktext == '(' and self.argFlag: self.argFlag = 2 # Find the end for arguments elif toktext == ':': self.argFlag = 0 ## Seperate the diffrent operator types # Brackets if self.doBrackets and toktext in ['[',']','(',')','{','}']: toktype = BRACKETS # Math operators elif self.doMathOps and toktext in ['*=','**=','-=','+=','|=', '%=','>>=','<<=','=','^=', '/=', '+','-','**','*','/','%']: toktype = MATH_OPERATOR # Operator else: toktype = OPERATOR # example how flags should work. # def fun(arg=argvalue,arg2=argvalue2): # 0 1 2 A 1 N 2 A 1 N 0 if toktext == "=" and self.argFlag == 2: self.argFlag = 1 elif toktext == "," and self.argFlag == 1: self.argFlag = 2 # Look for keywords elif toktype == NAME and keyword.iskeyword(toktext): toktype = KEYWORD # Set a flag if this was the class / def start so # the class / def name and arguments can be identified if toktext in ['class', 'def']: if toktext =='class' and \ not line[:line.find('class')].endswith('.'): self.classFlag = self.argFlag = 1 elif toktext == 'def' and \ not line[:line.find('def')].endswith('.'): self.defFlag = self.argFlag = 1 else: # must have used a keyword as a name i.e. self.class toktype = ERRORTOKEN # Look for class, def, decorator name elif (self.classFlag or self.defFlag or self.decoratorFlag) \ and self.doNames: if self.classFlag: self.classFlag = 0 toktype = CLASS_NAME elif self.defFlag: self.defFlag = 0 toktype = DEF_NAME elif self.decoratorFlag: self.decoratorFlag = 0 toktype = DECORATOR_NAME # Look for strings # Order of evaluation is important do not change. elif toktype == token.STRING: text = toktext.lower() # TRIPLE DOUBLE QUOTE's if (text[:3] == '"""'): toktype = TRIPLEDOUBLEQUOTE elif (text[:4] == 'r"""'): toktype = TRIPLEDOUBLEQUOTE_R elif (text[:4] == 'u"""' or text[:5] == 'ur"""'): toktype = TRIPLEDOUBLEQUOTE_U # DOUBLE QUOTE's elif (text[:1] == '"'): toktype = DOUBLEQUOTE elif (text[:2] == 'r"'): toktype = DOUBLEQUOTE_R elif (text[:2] == 'u"' or text[:3] == 'ur"'): toktype = DOUBLEQUOTE_U # TRIPLE SINGLE QUOTE's elif (text[:3] == "'''"): toktype = TRIPLESINGLEQUOTE elif (text[:4] == "r'''"): toktype = TRIPLESINGLEQUOTE_R elif (text[:4] == "u'''" or text[:5] == "ur'''"): toktype = TRIPLESINGLEQUOTE_U # SINGLE QUOTE's elif (text[:1] == "'"): toktype = SINGLEQUOTE elif (text[:2] == "r'"): toktype = SINGLEQUOTE_R elif (text[:2] == "u'" or text[:3] == "ur'"): toktype = SINGLEQUOTE_U # test for invalid string declaration if self.lasttext.lower() == 'ru': toktype = ERRORTOKEN # Look for comments elif toktype == COMMENT: if toktext[:2] == "##": toktype = DOUBLECOMMENT elif toktext[:3] == '#$#': toktype = TEXT self.textFlag = 'SPAN' toktext = toktext[3:] elif toktext[:3] == '#%#': toktype = TEXT self.textFlag = 'DIV' toktext = toktext[3:] elif toktext[:3] == '#@#': toktype = TEXT self.textFlag = 'RAW' toktext = toktext[3:] if self.doURL: # this is a 'fake helper function' # url(URI,Alias_name) or url(URI) url_pos = toktext.find('url(') if url_pos != -1: before = toktext[:url_pos] url = toktext[url_pos+4:] splitpoint = url.find(',') endpoint = url.find(')') after = url[endpoint+1:] url = url[:endpoint] if splitpoint != -1: urlparts = url.split(',',1) toktext = '%s%s%s'%( before,urlparts[0],urlparts[1].lstrip(),after) else: toktext = '%s%s%s'%(before,url,url,after) # Seperate errors from decorators elif toktype == ERRORTOKEN: # Bug fix for < py2.4 # space between decorators if self.argFlag and toktext.isspace(): #toktype = NAME self.out.write(toktext) return # Bug fix for py2.2 linenumbers with decorators elif toktext.isspace(): # What if we have a decorator after a >>> or ... #p = line.find('@') #if p >= 0 and not line[:p].isspace(): #self.out.write(toktext) #return if self.skip: self.skip=0 return else: self.out.write(toktext) return # trap decorators < py2.4 elif toktext == '@': toktype = DECORATOR # Set a flag if this was the decorator start so # the decorator name and arguments can be identified self.decoratorFlag = self.argFlag = 1 # Seperate args from names elif (self.argFlag == 2 and toktype == NAME and toktext != 'None' and self.doArgs): toktype = ARGS # Look for line numbers # The conversion code for them is in the send_text functions. if toktext in [self.LINENUMHOLDER,self.LINESTART]: toktype = LINENUMBER # if we don't have linenumbers set flag # to skip the trailing space from linestart if toktext == self.LINESTART and not self.dolinenums \ or toktext == self.LINENUMHOLDER: self.skip=1 # Skip blank token that made it thru ## bugfix for the last empty tag. if toktext == '': return # Last token text history self.lasttext = toktext # escape all but the urls in the comments if toktype in (DOUBLECOMMENT, COMMENT): if toktext.find('\n') else: # Start of css/xhtml snippet self.out.write(self.colors.get(CODESTART,'
\n'))

    def _doSnippetEnd(self):
        # End of html snippet
        self.out.write(self.colors.get(CODEEND,'
\n')) ######################################################## markup selectors def _getFile(self, filepath): try: _file = open(filepath,'r') content = _file.read() _file.close() except: traceback.print_exc() content = '' return content def _doPageStart(self): getattr(self, '_do%sStart'%(self.markup))() def _doPageHeader(self): if self.header != None: if self.header.find('#$#') != -1 or \ self.header.find('#$#') != -1 or \ self.header.find('#%#') != -1: self.out.write(self.header[3:]) else: if self.header != '': self.header = self._getFile(self.header) getattr(self, '_do%sHeader'%(self.markup))() def _doPageFooter(self): if self.footer != None: if self.footer.find('#$#') != -1 or \ self.footer.find('#@#') != -1 or \ self.footer.find('#%#') != -1: self.out.write(self.footer[3:]) else: if self.footer != '': self.footer = self._getFile(self.footer) getattr(self, '_do%sFooter'%(self.markup))() def _doPageEnd(self): getattr(self, '_do%sEnd'%(self.markup))() ################################################### color/style retrieval ## Some of these are not used anymore but are kept for documentation def _getLineNumber(self): num = self.linenum self.linenum+=1 return str(num).rjust(5)+" " def _getTags(self, key): # style tags return self.colors.get(key, self.colors[NAME])[0] def _getForeColor(self, key): # get text foreground color, if not set to black color = self.colors.get(key, self.colors[NAME])[1] if color[:1] != '#': color = '#000000' return color def _getBackColor(self, key): # get text background color return self.colors.get(key, self.colors[NAME])[2] def _getPageColor(self): # get page background color return self.colors.get(PAGEBACKGROUND, '#FFFFFF') def _getStyle(self, key): # get the token style from the color dictionary return self.colors.get(key, self.colors[NAME]) def _getMarkupClass(self, key): # get the markup class name from the markup dictionary return MARKUPDICT.get(key, MARKUPDICT[NAME]) def _getDocumentCreatedBy(self): return '\n'%( __title__,__version__,time.ctime()) ################################################### HTML markup functions def _doHTMLStart(self): # Start of html page self.out.write('\n') self.out.write('%s\n'%(self.title)) self.out.write(self._getDocumentCreatedBy()) self.out.write('\n') # Get background self.out.write('\n'%self._getPageColor()) self._doPageHeader() self.out.write('
')

    def _getHTMLStyles(self, toktype, toktext):
        # Get styles
        tags, color = self.colors.get(toktype, self.colors[NAME])[:2]#
        tagstart=[]
        tagend=[]
        # check for styles and set them if needed.
        if 'b' in tags:#Bold
            tagstart.append('')
            tagend.append('')
        if 'i' in tags:#Italics
            tagstart.append('')
            tagend.append('')
        if 'u' in tags:#Underline
            tagstart.append('')
            tagend.append('')
        # HTML tags should be paired like so : Doh!
        tagend.reverse()
        starttags="".join(tagstart)
        endtags="".join(tagend)
        return starttags,endtags,color

    def _sendHTMLText(self, toktype, toktext):
        numberlinks = self.numberlinks
        
        # If it is an error, set a red box around the bad tokens
        # older browsers should ignore it
        if toktype == ERRORTOKEN:
            style = ' style="border: solid 1.5pt #FF0000;"'
        else:
            style = ''
        # Get styles
        starttag, endtag, color = self._getHTMLStyles(toktype, toktext)
        # This is a hack to 'fix' multi-line  strings.
        # Multi-line strings are treated as only one token 
        # even though they can be several physical lines.
        # That makes it hard to spot the start of a line,
        # because at this level all we know about are tokens.
        
        if toktext.count(self.LINENUMHOLDER):
            # rip apart the string and separate it by line.
            # count lines and change all linenum token to line numbers.
            # embedded all the new font tags inside the current one.
            # Do this by ending the tag first then writing our new tags,
            # then starting another font tag exactly like the first one.
            if toktype == LINENUMBER:
                splittext = toktext.split(self.LINENUMHOLDER)
            else:    
                splittext = toktext.split(self.LINENUMHOLDER+' ')
            store = []
            store.append(splittext.pop(0))
            lstarttag, lendtag, lcolor = self._getHTMLStyles(LINENUMBER, toktext)
            count = len(splittext)
            for item in splittext:
                num =  self._getLineNumber()
                if numberlinks:
                    numstrip = num.strip()
                    content = '%s' \
                              %(numstrip,numstrip,num)
                else:
                    content = num
                if count <= 1:
                    endtag,starttag = '',''
                linenumber = ''.join([endtag,'',
                            lstarttag, content, lendtag, '' ,starttag])
                store.append(linenumber+item)
            toktext = ''.join(store)
        # send text
        ## Output optimization
        # skip font tag if black text, but styles will still be sent. (b,u,i)
        if color !='#000000':
            startfont = ''%(color, style)
            endfont = ''
        else:
            startfont, endfont = ('','')
        if toktype != LINENUMBER:
            self.out.write(''.join([startfont,starttag,
                                     toktext,endtag,endfont]))
        else:
            self.out.write(toktext)
        return

    def _doHTMLHeader(self):
        # Optional
        if self.header != '':
            self.out.write('%s\n'%self.header)
        else:
            color = self._getForeColor(NAME)
            self.out.write('# %s \
                            
# %s

\n'% (color, self.title, time.ctime())) def _doHTMLFooter(self): # Optional if self.footer != '': self.out.write('%s\n'%self.footer) else: color = self._getForeColor(NAME) self.out.write(' \
# %s
# %s
\n'% (color, self.title, time.ctime())) def _doHTMLEnd(self): # End of html page self.out.write('
\n') # Write a little info at the bottom self._doPageFooter() self.out.write('\n') #################################################### CSS markup functions def _getCSSStyle(self, key): # Get the tags and colors from the dictionary tags, forecolor, backcolor = self._getStyle(key) style=[] border = None bordercolor = None tags = tags.lower() if tags: # get the border color if specified # the border color will be appended to # the list after we define a border if '#' in tags:# border color start = tags.find('#') end = start + 7 bordercolor = tags[start:end] tags.replace(bordercolor,'',1) # text styles if 'b' in tags:# Bold style.append('font-weight:bold;') else: style.append('font-weight:normal;') if 'i' in tags:# Italic style.append('font-style:italic;') if 'u' in tags:# Underline style.append('text-decoration:underline;') # border size if 'l' in tags:# thick border size='thick' elif 'm' in tags:# medium border size='medium' elif 't' in tags:# thin border size='thin' else:# default size='medium' # border styles if 'n' in tags:# inset border border='inset' elif 'o' in tags:# outset border border='outset' elif 'r' in tags:# ridge border border='ridge' elif 'g' in tags:# groove border border='groove' elif '=' in tags:# double border border='double' elif '.' in tags:# dotted border border='dotted' elif '-' in tags:# dashed border border='dashed' elif 's' in tags:# solid border border='solid' # border type check seperate_sides=0 for side in ['<','>','^','v']: if side in tags: seperate_sides+=1 # border box or seperate sides if seperate_sides==0 and border: style.append('border: %s %s;'%(border,size)) else: if border == None: border = 'solid' if 'v' in tags:# bottom border style.append('border-bottom:%s %s;'%(border,size)) if '<' in tags:# left border style.append('border-left:%s %s;'%(border,size)) if '>' in tags:# right border style.append('border-right:%s %s;'%(border,size)) if '^' in tags:# top border style.append('border-top:%s %s;'%(border,size)) else: style.append('font-weight:normal;')# css inherited style fix # we have to define our borders before we set colors if bordercolor: style.append('border-color:%s;'%bordercolor) # text forecolor style.append('color:%s;'% forecolor) # text backcolor if backcolor: style.append('background-color:%s;'%backcolor) return (self._getMarkupClass(key),' '.join(style)) def _sendCSSStyle(self, external=0): """ create external and internal style sheets""" styles = [] external += self.external if not external: styles.append('\n') return ''.join(styles) def _doCSSStart(self): # Start of css/html 4.01 page self.out.write('\n') self.out.write('%s\n'%(self.title)) self.out.write(self._getDocumentCreatedBy()) self.out.write('\n') self._doCSSStyleSheet() self.out.write('\n\n') # Write a little info at the top. self._doPageHeader() self.out.write(self.colors.get(CODESTART,'
\n'))
        return

    def _doCSSStyleSheet(self):
        if not self.external:
            # write an embedded style sheet
            self.out.write(self._sendCSSStyle())
        else:
            # write a link to an external style sheet
            self.out.write('')
        return

    def _sendCSSText(self, toktype, toktext):
        # This is a hack to 'fix' multi-line strings.
        # Multi-line strings are treated as only one token 
        # even though they can be several physical lines.
        # That makes it hard to spot the start of a line,
        # because at this level all we know about are tokens.
        markupclass = MARKUPDICT.get(toktype, MARKUPDICT[NAME])
        # if it is a LINENUMBER type then we can skip the rest
        if toktext == self.LINESTART and toktype == LINENUMBER:
            self.out.write('')
            return
        if toktext.count(self.LINENUMHOLDER):
            # rip apart the string and separate it by line
            # count lines and change all linenum token to line numbers
            # also convert linestart and lineend tokens
            #   lnum  text 
            #################################################
            newmarkup = MARKUPDICT.get(LINENUMBER, MARKUPDICT[NAME])
            lstartspan = ''%(newmarkup)
            if toktype == LINENUMBER:
                splittext = toktext.split(self.LINENUMHOLDER)
            else:    
                splittext = toktext.split(self.LINENUMHOLDER+' ')
            store = []
            # we have already seen the first linenumber token
            # so we can skip the first one
            store.append(splittext.pop(0))
            for item in splittext:
                num = self._getLineNumber()
                if self.numberlinks:
                    numstrip = num.strip()
                    content= '%s' \
                              %(numstrip,numstrip,num)
                else:
                    content = num
                linenumber= ''.join([lstartspan,content,''])
                store.append(linenumber+item)
            toktext = ''.join(store)
        if toktext.count(self.LINESTART):
            # wraps the textline in a line span
            # this adds a lot of kludges, is it really worth it?
            store = []
            parts = toktext.split(self.LINESTART+' ')
            # handle the first part differently
            # the whole token gets wraqpped in a span later on
            first = parts.pop(0)
            # place spans before the newline
            pos = first.rfind('\n')
            if pos != -1:
                first=first[:pos]+''+first[pos:]
            store.append(first)
            #process the rest of the string
            for item in parts:
                #handle line numbers if present
                if self.dolinenums:
                    item = item.replace('',
                           ''%(markupclass))
                else:
                    item = '%s'%(markupclass,item)
                # add endings for line and string tokens
                pos = item.rfind('\n')
                if pos != -1:
                    item=item[:pos]+'\n'
                store.append(item)
            # add start tags for lines
            toktext = ''.join(store)
        # Send text
        if toktype != LINENUMBER:
            if toktype == TEXT and self.textFlag == 'DIV':
                startspan = '
'%(markupclass) endspan = '
' elif toktype == TEXT and self.textFlag == 'RAW': startspan,endspan = ('','') else: startspan = ''%(markupclass) endspan = '' self.out.write(''.join([startspan, toktext, endspan])) else: self.out.write(toktext) return def _doCSSHeader(self): if self.header != '': self.out.write('%s\n'%self.header) else: name = MARKUPDICT.get(NAME) self.out.write('
# %s
\ # %s

\n'%(name, self.title, time.ctime())) def _doCSSFooter(self): # Optional if self.footer != '': self.out.write('%s\n'%self.footer) else: self.out.write('
# %s
\ # %s
\n'%(MARKUPDICT.get(NAME),self.title, time.ctime())) def _doCSSEnd(self): # End of css/html page self.out.write(self.colors.get(CODEEND,'
\n')) # Write a little info at the bottom self._doPageFooter() self.out.write('\n') return ################################################## XHTML markup functions def _doXHTMLStart(self): # XHTML is really just XML + HTML 4.01. # We only need to change the page headers, # and a few tags to get valid XHTML. # Start of xhtml page self.out.write('\n \ \n \ \n') self.out.write('%s\n'%(self.title)) self.out.write(self._getDocumentCreatedBy()) self.out.write('\n') self._doXHTMLStyleSheet() self.out.write('\n\n') # Write a little info at the top. self._doPageHeader() self.out.write(self.colors.get(CODESTART,'
\n'))
        return

    def _doXHTMLStyleSheet(self):
        if not self.external:
            # write an embedded style sheet
            self.out.write(self._sendCSSStyle())
        else:
            # write a link to an external style sheet
            self.out.write('\n')
        return

    def _sendXHTMLText(self, toktype, toktext):
        self._sendCSSText(toktype, toktext)

    def _doXHTMLHeader(self):
        # Optional
        if self.header:
            self.out.write('%s\n'%self.header)
        else:
            name = MARKUPDICT.get(NAME)
            self.out.write('
# %s
\ # %s

\n '%( name, self.title, time.ctime())) def _doXHTMLFooter(self): # Optional if self.footer: self.out.write('%s\n'%self.footer) else: self.out.write('
# %s
\ # %s
\n'%(MARKUPDICT.get(NAME), self.title, time.ctime())) def _doXHTMLEnd(self): self._doCSSEnd() ############################################################################# if __name__ == '__main__': cli() ############################################################################# # PySourceColor.py # 2004, 2005 M.E.Farmer Jr. # Python license Paste-1.7.5.1/paste/util/threadedprint.py0000664000175000017500000002002311354703364016650 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ threadedprint.py ================ :author: Ian Bicking :date: 12 Jul 2004 Multi-threaded printing; allows the output produced via print to be separated according to the thread. To use this, you must install the catcher, like:: threadedprint.install() The installation optionally takes one of three parameters: default The default destination for print statements (e.g., ``sys.stdout``). factory A function that will produce the stream for a thread, given the thread's name. paramwriter Instead of writing to a file-like stream, this function will be called like ``paramwriter(thread_name, text)`` for every write. The thread name is the value returned by ``threading.currentThread().getName()``, a string (typically something like Thread-N). You can also submit file-like objects for specific threads, which will override any of these parameters. To do this, call ``register(stream, [threadName])``. ``threadName`` is optional, and if not provided the stream will be registered for the current thread. If no specific stream is registered for a thread, and no default has been provided, then an error will occur when anything is written to ``sys.stdout`` (or printed). Note: the stream's ``write`` method will be called in the thread the text came from, so you should consider thread safety, especially if multiple threads share the same writer. Note: if you want access to the original standard out, use ``sys.__stdout__``. You may also uninstall this, via:: threadedprint.uninstall() TODO ---- * Something with ``sys.stderr``. * Some default handlers. Maybe something that hooks into `logging`. * Possibly cache the results of ``factory`` calls. This would be a semantic change. """ import threading import sys from paste.util import filemixin class PrintCatcher(filemixin.FileMixin): def __init__(self, default=None, factory=None, paramwriter=None, leave_stdout=False): assert len(filter(lambda x: x is not None, [default, factory, paramwriter])) <= 1, ( "You can only provide one of default, factory, or paramwriter") if leave_stdout: assert not default, ( "You cannot pass in both default (%r) and " "leave_stdout=True" % default) default = sys.stdout if default: self._defaultfunc = self._writedefault elif factory: self._defaultfunc = self._writefactory elif paramwriter: self._defaultfunc = self._writeparam else: self._defaultfunc = self._writeerror self._default = default self._factory = factory self._paramwriter = paramwriter self._catchers = {} def write(self, v, currentThread=threading.currentThread): name = currentThread().getName() catchers = self._catchers if not catchers.has_key(name): self._defaultfunc(name, v) else: catcher = catchers[name] catcher.write(v) def seek(self, *args): # Weird, but Google App Engine is seeking on stdout name = threading.currentThread().getName() catchers = self._catchers if not name in catchers: self._default.seek(*args) else: catchers[name].seek(*args) def read(self, *args): name = threading.currentThread().getName() catchers = self._catchers if not name in catchers: self._default.read(*args) else: catchers[name].read(*args) def _writedefault(self, name, v): self._default.write(v) def _writefactory(self, name, v): self._factory(name).write(v) def _writeparam(self, name, v): self._paramwriter(name, v) def _writeerror(self, name, v): assert False, ( "There is no PrintCatcher output stream for the thread %r" % name) def register(self, catcher, name=None, currentThread=threading.currentThread): if name is None: name = currentThread().getName() self._catchers[name] = catcher def deregister(self, name=None, currentThread=threading.currentThread): if name is None: name = currentThread().getName() assert self._catchers.has_key(name), ( "There is no PrintCatcher catcher for the thread %r" % name) del self._catchers[name] _printcatcher = None _oldstdout = None def install(**kw): global _printcatcher, _oldstdout, register, deregister if (not _printcatcher or sys.stdout is not _printcatcher): _oldstdout = sys.stdout _printcatcher = sys.stdout = PrintCatcher(**kw) register = _printcatcher.register deregister = _printcatcher.deregister def uninstall(): global _printcatcher, _oldstdout, register, deregister if _printcatcher: sys.stdout = _oldstdout _printcatcher = _oldstdout = None register = not_installed_error deregister = not_installed_error def not_installed_error(*args, **kw): assert False, ( "threadedprint has not yet been installed (call " "threadedprint.install())") register = deregister = not_installed_error class StdinCatcher(filemixin.FileMixin): def __init__(self, default=None, factory=None, paramwriter=None): assert len(filter(lambda x: x is not None, [default, factory, paramwriter])) <= 1, ( "You can only provide one of default, factory, or paramwriter") if default: self._defaultfunc = self._readdefault elif factory: self._defaultfunc = self._readfactory elif paramwriter: self._defaultfunc = self._readparam else: self._defaultfunc = self._readerror self._default = default self._factory = factory self._paramwriter = paramwriter self._catchers = {} def read(self, size=None, currentThread=threading.currentThread): name = currentThread().getName() catchers = self._catchers if not catchers.has_key(name): return self._defaultfunc(name, size) else: catcher = catchers[name] return catcher.read(size) def _readdefault(self, name, size): self._default.read(size) def _readfactory(self, name, size): self._factory(name).read(size) def _readparam(self, name, size): self._paramreader(name, size) def _readerror(self, name, size): assert False, ( "There is no StdinCatcher output stream for the thread %r" % name) def register(self, catcher, name=None, currentThread=threading.currentThread): if name is None: name = currentThread().getName() self._catchers[name] = catcher def deregister(self, catcher, name=None, currentThread=threading.currentThread): if name is None: name = currentThread().getName() assert self._catchers.has_key(name), ( "There is no StdinCatcher catcher for the thread %r" % name) del self._catchers[name] _stdincatcher = None _oldstdin = None def install_stdin(**kw): global _stdincatcher, _oldstdin, register_stdin, deregister_stdin if not _stdincatcher: _oldstdin = sys.stdin _stdincatcher = sys.stdin = StdinCatcher(**kw) register_stdin = _stdincatcher.register deregister_stdin = _stdincatcher.deregister def uninstall(): global _stdincatcher, _oldstin, register_stdin, deregister_stdin if _stdincatcher: sys.stdin = _oldstdin _stdincatcher = _oldstdin = None register_stdin = deregister_stdin = not_installed_error_stdin def not_installed_error_stdin(*args, **kw): assert False, ( "threadedprint has not yet been installed for stdin (call " "threadedprint.install_stdin())") Paste-1.7.5.1/paste/util/import_string.py0000664000175000017500000000604611354703364016724 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ 'imports' a string -- converts a string to a Python object, importing any necessary modules and evaluating the expression. Everything before the : in an import expression is the module path; everything after is an expression to be evaluated in the namespace of that module. Alternately, if no : is present, then import the modules and get the attributes as necessary. Arbitrary expressions are not allowed in that case. """ def eval_import(s): """ Import a module, or import an object from a module. A module name like ``foo.bar:baz()`` can be used, where ``foo.bar`` is the module, and ``baz()`` is an expression evaluated in the context of that module. Note this is not safe on arbitrary strings because of the eval. """ if ':' not in s: return simple_import(s) module_name, expr = s.split(':', 1) module = import_module(module_name) obj = eval(expr, module.__dict__) return obj def simple_import(s): """ Import a module, or import an object from a module. A name like ``foo.bar.baz`` can be a module ``foo.bar.baz`` or a module ``foo.bar`` with an object ``baz`` in it, or a module ``foo`` with an object ``bar`` with an attribute ``baz``. """ parts = s.split('.') module = import_module(parts[0]) name = parts[0] parts = parts[1:] last_import_error = None while parts: name += '.' + parts[0] try: module = import_module(name) parts = parts[1:] except ImportError, e: last_import_error = e break obj = module while parts: try: obj = getattr(module, parts[0]) except AttributeError: raise ImportError( "Cannot find %s in module %r (stopped importing modules with error %s)" % (parts[0], module, last_import_error)) parts = parts[1:] return obj def import_module(s): """ Import a module. """ mod = __import__(s) parts = s.split('.') for part in parts[1:]: mod = getattr(mod, part) return mod def try_import_module(module_name): """ Imports a module, but catches import errors. Only catches errors when that module doesn't exist; if that module itself has an import error it will still get raised. Returns None if the module doesn't exist. """ try: return import_module(module_name) except ImportError, e: if not getattr(e, 'args', None): raise desc = e.args[0] if not desc.startswith('No module named '): raise desc = desc[len('No module named '):] # If you import foo.bar.baz, the bad import could be any # of foo.bar.baz, bar.baz, or baz; we'll test them all: parts = module_name.split('.') for i in range(len(parts)): if desc == '.'.join(parts[i:]): return None raise Paste-1.7.5.1/paste/util/doctest24.py0000664000175000017500000030213211354703364015632 0ustar ianbianb# Module doctest. # Released to the public domain 16-Jan-2001, by Tim Peters (tim@python.org). # Major enhancements and refactoring by: # Jim Fulton # Edward Loper # Provided as-is; use at your own risk; no warranty; no promises; enjoy! r"""Module doctest -- a framework for running examples in docstrings. In simplest use, end each module M to be tested with: def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() Then running the module as a script will cause the examples in the docstrings to get executed and verified: python M.py This won't display anything unless an example fails, in which case the failing example(s) and the cause(s) of the failure(s) are printed to stdout (why not stderr? because stderr is a lame hack <0.2 wink>), and the final line of output is "Test failed.". Run it with the -v switch instead: python M.py -v and a detailed report of all examples tried is printed to stdout, along with assorted summaries at the end. You can force verbose mode by passing "verbose=True" to testmod, or prohibit it by passing "verbose=False". In either of those cases, sys.argv is not examined by testmod. There are a variety of other ways to run doctests, including integration with the unittest framework, and support for running non-Python text files containing doctests. There are also many ways to override parts of doctest's default behaviors. See the Library Reference Manual for details. """ __docformat__ = 'reStructuredText en' __all__ = [ # 0, Option Flags 'register_optionflag', 'DONT_ACCEPT_TRUE_FOR_1', 'DONT_ACCEPT_BLANKLINE', 'NORMALIZE_WHITESPACE', 'ELLIPSIS', 'IGNORE_EXCEPTION_DETAIL', 'COMPARISON_FLAGS', 'REPORT_UDIFF', 'REPORT_CDIFF', 'REPORT_NDIFF', 'REPORT_ONLY_FIRST_FAILURE', 'REPORTING_FLAGS', # 1. Utility Functions 'is_private', # 2. Example & DocTest 'Example', 'DocTest', # 3. Doctest Parser 'DocTestParser', # 4. Doctest Finder 'DocTestFinder', # 5. Doctest Runner 'DocTestRunner', 'OutputChecker', 'DocTestFailure', 'UnexpectedException', 'DebugRunner', # 6. Test Functions 'testmod', 'testfile', 'run_docstring_examples', # 7. Tester 'Tester', # 8. Unittest Support 'DocTestSuite', 'DocFileSuite', 'set_unittest_reportflags', # 9. Debugging Support 'script_from_examples', 'testsource', 'debug_src', 'debug', ] import __future__ import sys, traceback, inspect, linecache, os, re, types import unittest, difflib, pdb, tempfile import warnings from StringIO import StringIO # Don't whine about the deprecated is_private function in this # module's tests. warnings.filterwarnings("ignore", "is_private", DeprecationWarning, __name__, 0) # There are 4 basic classes: # - Example: a pair, plus an intra-docstring line number. # - DocTest: a collection of examples, parsed from a docstring, plus # info about where the docstring came from (name, filename, lineno). # - DocTestFinder: extracts DocTests from a given object's docstring and # its contained objects' docstrings. # - DocTestRunner: runs DocTest cases, and accumulates statistics. # # So the basic picture is: # # list of: # +------+ +---------+ +-------+ # |object| --DocTestFinder-> | DocTest | --DocTestRunner-> |results| # +------+ +---------+ +-------+ # | Example | # | ... | # | Example | # +---------+ # Option constants. OPTIONFLAGS_BY_NAME = {} def register_optionflag(name): flag = 1 << len(OPTIONFLAGS_BY_NAME) OPTIONFLAGS_BY_NAME[name] = flag return flag DONT_ACCEPT_TRUE_FOR_1 = register_optionflag('DONT_ACCEPT_TRUE_FOR_1') DONT_ACCEPT_BLANKLINE = register_optionflag('DONT_ACCEPT_BLANKLINE') NORMALIZE_WHITESPACE = register_optionflag('NORMALIZE_WHITESPACE') ELLIPSIS = register_optionflag('ELLIPSIS') IGNORE_EXCEPTION_DETAIL = register_optionflag('IGNORE_EXCEPTION_DETAIL') COMPARISON_FLAGS = (DONT_ACCEPT_TRUE_FOR_1 | DONT_ACCEPT_BLANKLINE | NORMALIZE_WHITESPACE | ELLIPSIS | IGNORE_EXCEPTION_DETAIL) REPORT_UDIFF = register_optionflag('REPORT_UDIFF') REPORT_CDIFF = register_optionflag('REPORT_CDIFF') REPORT_NDIFF = register_optionflag('REPORT_NDIFF') REPORT_ONLY_FIRST_FAILURE = register_optionflag('REPORT_ONLY_FIRST_FAILURE') REPORTING_FLAGS = (REPORT_UDIFF | REPORT_CDIFF | REPORT_NDIFF | REPORT_ONLY_FIRST_FAILURE) # Special string markers for use in `want` strings: BLANKLINE_MARKER = '' ELLIPSIS_MARKER = '...' ###################################################################### ## Table of Contents ###################################################################### # 1. Utility Functions # 2. Example & DocTest -- store test cases # 3. DocTest Parser -- extracts examples from strings # 4. DocTest Finder -- extracts test cases from objects # 5. DocTest Runner -- runs test cases # 6. Test Functions -- convenient wrappers for testing # 7. Tester Class -- for backwards compatibility # 8. Unittest Support # 9. Debugging Support # 10. Example Usage ###################################################################### ## 1. Utility Functions ###################################################################### def is_private(prefix, base): """prefix, base -> true iff name prefix + "." + base is "private". Prefix may be an empty string, and base does not contain a period. Prefix is ignored (although functions you write conforming to this protocol may make use of it). Return true iff base begins with an (at least one) underscore, but does not both begin and end with (at least) two underscores. >>> is_private("a.b", "my_func") False >>> is_private("____", "_my_func") True >>> is_private("someclass", "__init__") False >>> is_private("sometypo", "__init_") True >>> is_private("x.y.z", "_") True >>> is_private("_x.y.z", "__") False >>> is_private("", "") # senseless but consistent False """ warnings.warn("is_private is deprecated; it wasn't useful; " "examine DocTestFinder.find() lists instead", DeprecationWarning, stacklevel=2) return base[:1] == "_" and not base[:2] == "__" == base[-2:] def _extract_future_flags(globs): """ Return the compiler-flags associated with the future features that have been imported into the given namespace (globs). """ flags = 0 for fname in __future__.all_feature_names: feature = globs.get(fname, None) if feature is getattr(__future__, fname): flags |= feature.compiler_flag return flags def _normalize_module(module, depth=2): """ Return the module specified by `module`. In particular: - If `module` is a module, then return module. - If `module` is a string, then import and return the module with that name. - If `module` is None, then return the calling module. The calling module is assumed to be the module of the stack frame at the given depth in the call stack. """ if inspect.ismodule(module): return module elif isinstance(module, (str, unicode)): return __import__(module, globals(), locals(), ["*"]) elif module is None: return sys.modules[sys._getframe(depth).f_globals['__name__']] else: raise TypeError("Expected a module, string, or None") def _indent(s, indent=4): """ Add the given number of space characters to the beginning every non-blank line in `s`, and return the result. """ # This regexp matches the start of non-blank lines: return re.sub('(?m)^(?!$)', indent*' ', s) def _exception_traceback(exc_info): """ Return a string containing a traceback message for the given exc_info tuple (as returned by sys.exc_info()). """ # Get a traceback message. excout = StringIO() exc_type, exc_val, exc_tb = exc_info traceback.print_exception(exc_type, exc_val, exc_tb, file=excout) return excout.getvalue() # Override some StringIO methods. class _SpoofOut(StringIO): def getvalue(self): result = StringIO.getvalue(self) # If anything at all was written, make sure there's a trailing # newline. There's no way for the expected output to indicate # that a trailing newline is missing. if result and not result.endswith("\n"): result += "\n" # Prevent softspace from screwing up the next test case, in # case they used print with a trailing comma in an example. if hasattr(self, "softspace"): del self.softspace return result def truncate(self, size=None): StringIO.truncate(self, size) if hasattr(self, "softspace"): del self.softspace # Worst-case linear-time ellipsis matching. def _ellipsis_match(want, got): """ Essentially the only subtle case: >>> _ellipsis_match('aa...aa', 'aaa') False """ if ELLIPSIS_MARKER not in want: return want == got # Find "the real" strings. ws = want.split(ELLIPSIS_MARKER) assert len(ws) >= 2 # Deal with exact matches possibly needed at one or both ends. startpos, endpos = 0, len(got) w = ws[0] if w: # starts with exact match if got.startswith(w): startpos = len(w) del ws[0] else: return False w = ws[-1] if w: # ends with exact match if got.endswith(w): endpos -= len(w) del ws[-1] else: return False if startpos > endpos: # Exact end matches required more characters than we have, as in # _ellipsis_match('aa...aa', 'aaa') return False # For the rest, we only need to find the leftmost non-overlapping # match for each piece. If there's no overall match that way alone, # there's no overall match period. for w in ws: # w may be '' at times, if there are consecutive ellipses, or # due to an ellipsis at the start or end of `want`. That's OK. # Search for an empty string succeeds, and doesn't change startpos. startpos = got.find(w, startpos, endpos) if startpos < 0: return False startpos += len(w) return True def _comment_line(line): "Return a commented form of the given line" line = line.rstrip() if line: return '# '+line else: return '#' class _OutputRedirectingPdb(pdb.Pdb): """ A specialized version of the python debugger that redirects stdout to a given stream when interacting with the user. Stdout is *not* redirected when traced code is executed. """ def __init__(self, out): self.__out = out pdb.Pdb.__init__(self) def trace_dispatch(self, *args): # Redirect stdout to the given stream. save_stdout = sys.stdout sys.stdout = self.__out # Call Pdb's trace dispatch method. try: return pdb.Pdb.trace_dispatch(self, *args) finally: sys.stdout = save_stdout # [XX] Normalize with respect to os.path.pardir? def _module_relative_path(module, path): if not inspect.ismodule(module): raise TypeError, 'Expected a module: %r' % module if path.startswith('/'): raise ValueError, 'Module-relative files may not have absolute paths' # Find the base directory for the path. if hasattr(module, '__file__'): # A normal module/package basedir = os.path.split(module.__file__)[0] elif module.__name__ == '__main__': # An interactive session. if len(sys.argv)>0 and sys.argv[0] != '': basedir = os.path.split(sys.argv[0])[0] else: basedir = os.curdir else: # A module w/o __file__ (this includes builtins) raise ValueError("Can't resolve paths relative to the module " + module + " (it has no __file__)") # Combine the base directory and the path. return os.path.join(basedir, *(path.split('/'))) ###################################################################### ## 2. Example & DocTest ###################################################################### ## - An "example" is a pair, where "source" is a ## fragment of source code, and "want" is the expected output for ## "source." The Example class also includes information about ## where the example was extracted from. ## ## - A "doctest" is a collection of examples, typically extracted from ## a string (such as an object's docstring). The DocTest class also ## includes information about where the string was extracted from. class Example: """ A single doctest example, consisting of source code and expected output. `Example` defines the following attributes: - source: A single Python statement, always ending with a newline. The constructor adds a newline if needed. - want: The expected output from running the source code (either from stdout, or a traceback in case of exception). `want` ends with a newline unless it's empty, in which case it's an empty string. The constructor adds a newline if needed. - exc_msg: The exception message generated by the example, if the example is expected to generate an exception; or `None` if it is not expected to generate an exception. This exception message is compared against the return value of `traceback.format_exception_only()`. `exc_msg` ends with a newline unless it's `None`. The constructor adds a newline if needed. - lineno: The line number within the DocTest string containing this Example where the Example begins. This line number is zero-based, with respect to the beginning of the DocTest. - indent: The example's indentation in the DocTest string. I.e., the number of space characters that preceed the example's first prompt. - options: A dictionary mapping from option flags to True or False, which is used to override default options for this example. Any option flags not contained in this dictionary are left at their default value (as specified by the DocTestRunner's optionflags). By default, no options are set. """ def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, options=None): # Normalize inputs. if not source.endswith('\n'): source += '\n' if want and not want.endswith('\n'): want += '\n' if exc_msg is not None and not exc_msg.endswith('\n'): exc_msg += '\n' # Store properties. self.source = source self.want = want self.lineno = lineno self.indent = indent if options is None: options = {} self.options = options self.exc_msg = exc_msg class DocTest: """ A collection of doctest examples that should be run in a single namespace. Each `DocTest` defines the following attributes: - examples: the list of examples. - globs: The namespace (aka globals) that the examples should be run in. - name: A name identifying the DocTest (typically, the name of the object whose docstring this DocTest was extracted from). - filename: The name of the file that this DocTest was extracted from, or `None` if the filename is unknown. - lineno: The line number within filename where this DocTest begins, or `None` if the line number is unavailable. This line number is zero-based, with respect to the beginning of the file. - docstring: The string that the examples were extracted from, or `None` if the string is unavailable. """ def __init__(self, examples, globs, name, filename, lineno, docstring): """ Create a new DocTest containing the given examples. The DocTest's globals are initialized with a copy of `globs`. """ assert not isinstance(examples, basestring), \ "DocTest no longer accepts str; use DocTestParser instead" self.examples = examples self.docstring = docstring self.globs = globs.copy() self.name = name self.filename = filename self.lineno = lineno def __repr__(self): if len(self.examples) == 0: examples = 'no examples' elif len(self.examples) == 1: examples = '1 example' else: examples = '%d examples' % len(self.examples) return ('' % (self.name, self.filename, self.lineno, examples)) # This lets us sort tests by name: def __cmp__(self, other): if not isinstance(other, DocTest): return -1 return cmp((self.name, self.filename, self.lineno, id(self)), (other.name, other.filename, other.lineno, id(other))) ###################################################################### ## 3. DocTestParser ###################################################################### class DocTestParser: """ A class used to parse strings containing doctest examples. """ # This regular expression is used to find doctest examples in a # string. It defines three groups: `source` is the source code # (including leading indentation and prompts); `indent` is the # indentation of the first (PS1) line of the source code; and # `want` is the expected output (including leading indentation). _EXAMPLE_RE = re.compile(r''' # Source consists of a PS1 line followed by zero or more PS2 lines. (?P (?:^(?P [ ]*) >>> .*) # PS1 line (?:\n [ ]* \.\.\. .*)*) # PS2 lines \n? # Want consists of any non-blank lines that do not start with PS1. (?P (?:(?![ ]*$) # Not a blank line (?![ ]*>>>) # Not a line starting with PS1 .*$\n? # But any other line )*) ''', re.MULTILINE | re.VERBOSE) # A regular expression for handling `want` strings that contain # expected exceptions. It divides `want` into three pieces: # - the traceback header line (`hdr`) # - the traceback stack (`stack`) # - the exception message (`msg`), as generated by # traceback.format_exception_only() # `msg` may have multiple lines. We assume/require that the # exception message is the first non-indented line starting with a word # character following the traceback header line. _EXCEPTION_RE = re.compile(r""" # Grab the traceback header. Different versions of Python have # said different things on the first traceback line. ^(?P Traceback\ \( (?: most\ recent\ call\ last | innermost\ last ) \) : ) \s* $ # toss trailing whitespace on the header. (?P .*?) # don't blink: absorb stuff until... ^ (?P \w+ .*) # a line *starts* with alphanum. """, re.VERBOSE | re.MULTILINE | re.DOTALL) # A callable returning a true value iff its argument is a blank line # or contains a single comment. _IS_BLANK_OR_COMMENT = re.compile(r'^[ ]*(#.*)?$').match def parse(self, string, name=''): """ Divide the given string into examples and intervening text, and return them as a list of alternating Examples and strings. Line numbers for the Examples are 0-based. The optional argument `name` is a name identifying this string, and is only used for error messages. """ string = string.expandtabs() # If all lines begin with the same indentation, then strip it. min_indent = self._min_indent(string) if min_indent > 0: string = '\n'.join([l[min_indent:] for l in string.split('\n')]) output = [] charno, lineno = 0, 0 # Find all doctest examples in the string: for m in self._EXAMPLE_RE.finditer(string): # Add the pre-example text to `output`. output.append(string[charno:m.start()]) # Update lineno (lines before this example) lineno += string.count('\n', charno, m.start()) # Extract info from the regexp match. (source, options, want, exc_msg) = \ self._parse_example(m, name, lineno) # Create an Example, and add it to the list. if not self._IS_BLANK_OR_COMMENT(source): output.append( Example(source, want, exc_msg, lineno=lineno, indent=min_indent+len(m.group('indent')), options=options) ) # Update lineno (lines inside this example) lineno += string.count('\n', m.start(), m.end()) # Update charno. charno = m.end() # Add any remaining post-example text to `output`. output.append(string[charno:]) return output def get_doctest(self, string, globs, name, filename, lineno): """ Extract all doctest examples from the given string, and collect them into a `DocTest` object. `globs`, `name`, `filename`, and `lineno` are attributes for the new `DocTest` object. See the documentation for `DocTest` for more information. """ return DocTest(self.get_examples(string, name), globs, name, filename, lineno, string) def get_examples(self, string, name=''): """ Extract all doctest examples from the given string, and return them as a list of `Example` objects. Line numbers are 0-based, because it's most common in doctests that nothing interesting appears on the same line as opening triple-quote, and so the first interesting line is called \"line 1\" then. The optional argument `name` is a name identifying this string, and is only used for error messages. """ return [x for x in self.parse(string, name) if isinstance(x, Example)] def _parse_example(self, m, name, lineno): """ Given a regular expression match from `_EXAMPLE_RE` (`m`), return a pair `(source, want)`, where `source` is the matched example's source code (with prompts and indentation stripped); and `want` is the example's expected output (with indentation stripped). `name` is the string's name, and `lineno` is the line number where the example starts; both are used for error messages. """ # Get the example's indentation level. indent = len(m.group('indent')) # Divide source into lines; check that they're properly # indented; and then strip their indentation & prompts. source_lines = m.group('source').split('\n') self._check_prompt_blank(source_lines, indent, name, lineno) self._check_prefix(source_lines[1:], ' '*indent + '.', name, lineno) source = '\n'.join([sl[indent+4:] for sl in source_lines]) # Divide want into lines; check that it's properly indented; and # then strip the indentation. Spaces before the last newline should # be preserved, so plain rstrip() isn't good enough. want = m.group('want') want_lines = want.split('\n') if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]): del want_lines[-1] # forget final newline & spaces after it self._check_prefix(want_lines, ' '*indent, name, lineno + len(source_lines)) want = '\n'.join([wl[indent:] for wl in want_lines]) # If `want` contains a traceback message, then extract it. m = self._EXCEPTION_RE.match(want) if m: exc_msg = m.group('msg') else: exc_msg = None # Extract options from the source. options = self._find_options(source, name, lineno) return source, options, want, exc_msg # This regular expression looks for option directives in the # source code of an example. Option directives are comments # starting with "doctest:". Warning: this may give false # positives for string-literals that contain the string # "#doctest:". Eliminating these false positives would require # actually parsing the string; but we limit them by ignoring any # line containing "#doctest:" that is *followed* by a quote mark. _OPTION_DIRECTIVE_RE = re.compile(r'#\s*doctest:\s*([^\n\'"]*)$', re.MULTILINE) def _find_options(self, source, name, lineno): """ Return a dictionary containing option overrides extracted from option directives in the given source string. `name` is the string's name, and `lineno` is the line number where the example starts; both are used for error messages. """ options = {} # (note: with the current regexp, this will match at most once:) for m in self._OPTION_DIRECTIVE_RE.finditer(source): option_strings = m.group(1).replace(',', ' ').split() for option in option_strings: if (option[0] not in '+-' or option[1:] not in OPTIONFLAGS_BY_NAME): raise ValueError('line %r of the doctest for %s ' 'has an invalid option: %r' % (lineno+1, name, option)) flag = OPTIONFLAGS_BY_NAME[option[1:]] options[flag] = (option[0] == '+') if options and self._IS_BLANK_OR_COMMENT(source): raise ValueError('line %r of the doctest for %s has an option ' 'directive on a line with no example: %r' % (lineno, name, source)) return options # This regular expression finds the indentation of every non-blank # line in a string. _INDENT_RE = re.compile('^([ ]*)(?=\S)', re.MULTILINE) def _min_indent(self, s): "Return the minimum indentation of any non-blank line in `s`" indents = [len(indent) for indent in self._INDENT_RE.findall(s)] if len(indents) > 0: return min(indents) else: return 0 def _check_prompt_blank(self, lines, indent, name, lineno): """ Given the lines of a source string (including prompts and leading indentation), check to make sure that every prompt is followed by a space character. If any line is not followed by a space character, then raise ValueError. """ for i, line in enumerate(lines): if len(line) >= indent+4 and line[indent+3] != ' ': raise ValueError('line %r of the docstring for %s ' 'lacks blank after %s: %r' % (lineno+i+1, name, line[indent:indent+3], line)) def _check_prefix(self, lines, prefix, name, lineno): """ Check that every line in the given list starts with the given prefix; if any line does not, then raise a ValueError. """ for i, line in enumerate(lines): if line and not line.startswith(prefix): raise ValueError('line %r of the docstring for %s has ' 'inconsistent leading whitespace: %r' % (lineno+i+1, name, line)) ###################################################################### ## 4. DocTest Finder ###################################################################### class DocTestFinder: """ A class used to extract the DocTests that are relevant to a given object, from its docstring and the docstrings of its contained objects. Doctests can currently be extracted from the following object types: modules, functions, classes, methods, staticmethods, classmethods, and properties. """ def __init__(self, verbose=False, parser=DocTestParser(), recurse=True, _namefilter=None, exclude_empty=True): """ Create a new doctest finder. The optional argument `parser` specifies a class or function that should be used to create new DocTest objects (or objects that implement the same interface as DocTest). The signature for this factory function should match the signature of the DocTest constructor. If the optional argument `recurse` is false, then `find` will only examine the given object, and not any contained objects. If the optional argument `exclude_empty` is false, then `find` will include tests for objects with empty docstrings. """ self._parser = parser self._verbose = verbose self._recurse = recurse self._exclude_empty = exclude_empty # _namefilter is undocumented, and exists only for temporary backward- # compatibility support of testmod's deprecated isprivate mess. self._namefilter = _namefilter def find(self, obj, name=None, module=None, globs=None, extraglobs=None): """ Return a list of the DocTests that are defined by the given object's docstring, or by any of its contained objects' docstrings. The optional parameter `module` is the module that contains the given object. If the module is not specified or is None, then the test finder will attempt to automatically determine the correct module. The object's module is used: - As a default namespace, if `globs` is not specified. - To prevent the DocTestFinder from extracting DocTests from objects that are imported from other modules. - To find the name of the file containing the object. - To help find the line number of the object within its file. Contained objects whose module does not match `module` are ignored. If `module` is False, no attempt to find the module will be made. This is obscure, of use mostly in tests: if `module` is False, or is None but cannot be found automatically, then all objects are considered to belong to the (non-existent) module, so all contained objects will (recursively) be searched for doctests. The globals for each DocTest is formed by combining `globs` and `extraglobs` (bindings in `extraglobs` override bindings in `globs`). A new copy of the globals dictionary is created for each DocTest. If `globs` is not specified, then it defaults to the module's `__dict__`, if specified, or {} otherwise. If `extraglobs` is not specified, then it defaults to {}. """ # If name was not specified, then extract it from the object. if name is None: name = getattr(obj, '__name__', None) if name is None: raise ValueError("DocTestFinder.find: name must be given " "when obj.__name__ doesn't exist: %r" % (type(obj),)) # Find the module that contains the given object (if obj is # a module, then module=obj.). Note: this may fail, in which # case module will be None. if module is False: module = None elif module is None: module = inspect.getmodule(obj) # Read the module's source code. This is used by # DocTestFinder._find_lineno to find the line number for a # given object's docstring. try: file = inspect.getsourcefile(obj) or inspect.getfile(obj) source_lines = linecache.getlines(file) if not source_lines: source_lines = None except TypeError: source_lines = None # Initialize globals, and merge in extraglobs. if globs is None: if module is None: globs = {} else: globs = module.__dict__.copy() else: globs = globs.copy() if extraglobs is not None: globs.update(extraglobs) # Recursively expore `obj`, extracting DocTests. tests = [] self._find(tests, obj, name, module, source_lines, globs, {}) return tests def _filter(self, obj, prefix, base): """ Return true if the given object should not be examined. """ return (self._namefilter is not None and self._namefilter(prefix, base)) def _from_module(self, module, object): """ Return true if the given object is defined in the given module. """ if module is None: return True elif inspect.isfunction(object): return module.__dict__ is object.func_globals elif inspect.isclass(object): return module.__name__ == object.__module__ elif inspect.getmodule(object) is not None: return module is inspect.getmodule(object) elif hasattr(object, '__module__'): return module.__name__ == object.__module__ elif isinstance(object, property): return True # [XX] no way not be sure. else: raise ValueError("object must be a class or function") def _find(self, tests, obj, name, module, source_lines, globs, seen): """ Find tests for the given object and any contained objects, and add them to `tests`. """ if self._verbose: print 'Finding tests in %s' % name # If we've already processed this object, then ignore it. if id(obj) in seen: return seen[id(obj)] = 1 # Find a test for this object, and add it to the list of tests. test = self._get_test(obj, name, module, globs, source_lines) if test is not None: tests.append(test) # Look for tests in a module's contained objects. if inspect.ismodule(obj) and self._recurse: for valname, val in obj.__dict__.items(): # Check if this contained object should be ignored. if self._filter(val, name, valname): continue valname = '%s.%s' % (name, valname) # Recurse to functions & classes. if ((inspect.isfunction(val) or inspect.isclass(val)) and self._from_module(module, val)): self._find(tests, val, valname, module, source_lines, globs, seen) # Look for tests in a module's __test__ dictionary. if inspect.ismodule(obj) and self._recurse: for valname, val in getattr(obj, '__test__', {}).items(): if not isinstance(valname, basestring): raise ValueError("DocTestFinder.find: __test__ keys " "must be strings: %r" % (type(valname),)) if not (inspect.isfunction(val) or inspect.isclass(val) or inspect.ismethod(val) or inspect.ismodule(val) or isinstance(val, basestring)): raise ValueError("DocTestFinder.find: __test__ values " "must be strings, functions, methods, " "classes, or modules: %r" % (type(val),)) valname = '%s.__test__.%s' % (name, valname) self._find(tests, val, valname, module, source_lines, globs, seen) # Look for tests in a class's contained objects. if inspect.isclass(obj) and self._recurse: for valname, val in obj.__dict__.items(): # Check if this contained object should be ignored. if self._filter(val, name, valname): continue # Special handling for staticmethod/classmethod. if isinstance(val, staticmethod): val = getattr(obj, valname) if isinstance(val, classmethod): val = getattr(obj, valname).im_func # Recurse to methods, properties, and nested classes. if ((inspect.isfunction(val) or inspect.isclass(val) or isinstance(val, property)) and self._from_module(module, val)): valname = '%s.%s' % (name, valname) self._find(tests, val, valname, module, source_lines, globs, seen) def _get_test(self, obj, name, module, globs, source_lines): """ Return a DocTest for the given object, if it defines a docstring; otherwise, return None. """ # Extract the object's docstring. If it doesn't have one, # then return None (no test for this object). if isinstance(obj, basestring): docstring = obj else: try: if obj.__doc__ is None: docstring = '' else: docstring = obj.__doc__ if not isinstance(docstring, basestring): docstring = str(docstring) except (TypeError, AttributeError): docstring = '' # Find the docstring's location in the file. lineno = self._find_lineno(obj, source_lines) # Don't bother if the docstring is empty. if self._exclude_empty and not docstring: return None # Return a DocTest for this object. if module is None: filename = None else: filename = getattr(module, '__file__', module.__name__) if filename[-4:] in (".pyc", ".pyo"): filename = filename[:-1] return self._parser.get_doctest(docstring, globs, name, filename, lineno) def _find_lineno(self, obj, source_lines): """ Return a line number of the given object's docstring. Note: this method assumes that the object has a docstring. """ lineno = None # Find the line number for modules. if inspect.ismodule(obj): lineno = 0 # Find the line number for classes. # Note: this could be fooled if a class is defined multiple # times in a single file. if inspect.isclass(obj): if source_lines is None: return None pat = re.compile(r'^\s*class\s*%s\b' % getattr(obj, '__name__', '-')) for i, line in enumerate(source_lines): if pat.match(line): lineno = i break # Find the line number for functions & methods. if inspect.ismethod(obj): obj = obj.im_func if inspect.isfunction(obj): obj = obj.func_code if inspect.istraceback(obj): obj = obj.tb_frame if inspect.isframe(obj): obj = obj.f_code if inspect.iscode(obj): lineno = getattr(obj, 'co_firstlineno', None)-1 # Find the line number where the docstring starts. Assume # that it's the first line that begins with a quote mark. # Note: this could be fooled by a multiline function # signature, where a continuation line begins with a quote # mark. if lineno is not None: if source_lines is None: return lineno+1 pat = re.compile('(^|.*:)\s*\w*("|\')') for lineno in range(lineno, len(source_lines)): if pat.match(source_lines[lineno]): return lineno # We couldn't find the line number. return None ###################################################################### ## 5. DocTest Runner ###################################################################### class DocTestRunner: """ A class used to run DocTest test cases, and accumulate statistics. The `run` method is used to process a single DocTest case. It returns a tuple `(f, t)`, where `t` is the number of test cases tried, and `f` is the number of test cases that failed. >>> tests = DocTestFinder().find(_TestClass) >>> runner = DocTestRunner(verbose=False) >>> for test in tests: ... print runner.run(test) (0, 2) (0, 1) (0, 2) (0, 2) The `summarize` method prints a summary of all the test cases that have been run by the runner, and returns an aggregated `(f, t)` tuple: >>> runner.summarize(verbose=1) 4 items passed all tests: 2 tests in _TestClass 2 tests in _TestClass.__init__ 2 tests in _TestClass.get 1 tests in _TestClass.square 7 tests in 4 items. 7 passed and 0 failed. Test passed. (0, 7) The aggregated number of tried examples and failed examples is also available via the `tries` and `failures` attributes: >>> runner.tries 7 >>> runner.failures 0 The comparison between expected outputs and actual outputs is done by an `OutputChecker`. This comparison may be customized with a number of option flags; see the documentation for `testmod` for more information. If the option flags are insufficient, then the comparison may also be customized by passing a subclass of `OutputChecker` to the constructor. The test runner's display output can be controlled in two ways. First, an output function (`out) can be passed to `TestRunner.run`; this function will be called with strings that should be displayed. It defaults to `sys.stdout.write`. If capturing the output is not sufficient, then the display output can be also customized by subclassing DocTestRunner, and overriding the methods `report_start`, `report_success`, `report_unexpected_exception`, and `report_failure`. """ # This divider string is used to separate failure messages, and to # separate sections of the summary. DIVIDER = "*" * 70 def __init__(self, checker=None, verbose=None, optionflags=0): """ Create a new test runner. Optional keyword arg `checker` is the `OutputChecker` that should be used to compare the expected outputs and actual outputs of doctest examples. Optional keyword arg 'verbose' prints lots of stuff if true, only failures if false; by default, it's true iff '-v' is in sys.argv. Optional argument `optionflags` can be used to control how the test runner compares expected output to actual output, and how it displays failures. See the documentation for `testmod` for more information. """ self._checker = checker or OutputChecker() if verbose is None: verbose = '-v' in sys.argv self._verbose = verbose self.optionflags = optionflags self.original_optionflags = optionflags # Keep track of the examples we've run. self.tries = 0 self.failures = 0 self._name2ft = {} # Create a fake output target for capturing doctest output. self._fakeout = _SpoofOut() #///////////////////////////////////////////////////////////////// # Reporting methods #///////////////////////////////////////////////////////////////// def report_start(self, out, test, example): """ Report that the test runner is about to process the given example. (Only displays a message if verbose=True) """ if self._verbose: if example.want: out('Trying:\n' + _indent(example.source) + 'Expecting:\n' + _indent(example.want)) else: out('Trying:\n' + _indent(example.source) + 'Expecting nothing\n') def report_success(self, out, test, example, got): """ Report that the given example ran successfully. (Only displays a message if verbose=True) """ if self._verbose: out("ok\n") def report_failure(self, out, test, example, got): """ Report that the given example failed. """ out(self._failure_header(test, example) + self._checker.output_difference(example, got, self.optionflags)) def report_unexpected_exception(self, out, test, example, exc_info): """ Report that the given example raised an unexpected exception. """ out(self._failure_header(test, example) + 'Exception raised:\n' + _indent(_exception_traceback(exc_info))) def _failure_header(self, test, example): out = [self.DIVIDER] if test.filename: if test.lineno is not None and example.lineno is not None: lineno = test.lineno + example.lineno + 1 else: lineno = '?' out.append('File "%s", line %s, in %s' % (test.filename, lineno, test.name)) else: out.append('Line %s, in %s' % (example.lineno+1, test.name)) out.append('Failed example:') source = example.source out.append(_indent(source)) return '\n'.join(out) #///////////////////////////////////////////////////////////////// # DocTest Running #///////////////////////////////////////////////////////////////// def __run(self, test, compileflags, out): """ Run the examples in `test`. Write the outcome of each example with one of the `DocTestRunner.report_*` methods, using the writer function `out`. `compileflags` is the set of compiler flags that should be used to execute examples. Return a tuple `(f, t)`, where `t` is the number of examples tried, and `f` is the number of examples that failed. The examples are run in the namespace `test.globs`. """ # Keep track of the number of failures and tries. failures = tries = 0 # Save the option flags (since option directives can be used # to modify them). original_optionflags = self.optionflags SUCCESS, FAILURE, BOOM = range(3) # `outcome` state check = self._checker.check_output # Process each example. for examplenum, example in enumerate(test.examples): # If REPORT_ONLY_FIRST_FAILURE is set, then supress # reporting after the first failure. quiet = (self.optionflags & REPORT_ONLY_FIRST_FAILURE and failures > 0) # Merge in the example's options. self.optionflags = original_optionflags if example.options: for (optionflag, val) in example.options.items(): if val: self.optionflags |= optionflag else: self.optionflags &= ~optionflag # Record that we started this example. tries += 1 if not quiet: self.report_start(out, test, example) # Use a special filename for compile(), so we can retrieve # the source code during interactive debugging (see # __patched_linecache_getlines). filename = '' % (test.name, examplenum) # Run the example in the given context (globs), and record # any exception that gets raised. (But don't intercept # keyboard interrupts.) try: # Don't blink! This is where the user's code gets run. exec compile(example.source, filename, "single", compileflags, 1) in test.globs self.debugger.set_continue() # ==== Example Finished ==== exception = None except KeyboardInterrupt: raise except: exception = sys.exc_info() self.debugger.set_continue() # ==== Example Finished ==== got = self._fakeout.getvalue() # the actual output self._fakeout.truncate(0) outcome = FAILURE # guilty until proved innocent or insane # If the example executed without raising any exceptions, # verify its output. if exception is None: if check(example.want, got, self.optionflags): outcome = SUCCESS # The example raised an exception: check if it was expected. else: exc_info = sys.exc_info() exc_msg = traceback.format_exception_only(*exc_info[:2])[-1] if not quiet: got += _exception_traceback(exc_info) # If `example.exc_msg` is None, then we weren't expecting # an exception. if example.exc_msg is None: outcome = BOOM # We expected an exception: see whether it matches. elif check(example.exc_msg, exc_msg, self.optionflags): outcome = SUCCESS # Another chance if they didn't care about the detail. elif self.optionflags & IGNORE_EXCEPTION_DETAIL: m1 = re.match(r'[^:]*:', example.exc_msg) m2 = re.match(r'[^:]*:', exc_msg) if m1 and m2 and check(m1.group(0), m2.group(0), self.optionflags): outcome = SUCCESS # Report the outcome. if outcome is SUCCESS: if not quiet: self.report_success(out, test, example, got) elif outcome is FAILURE: if not quiet: self.report_failure(out, test, example, got) failures += 1 elif outcome is BOOM: if not quiet: self.report_unexpected_exception(out, test, example, exc_info) failures += 1 else: assert False, ("unknown outcome", outcome) # Restore the option flags (in case they were modified) self.optionflags = original_optionflags # Record and return the number of failures and tries. self.__record_outcome(test, failures, tries) return failures, tries def __record_outcome(self, test, f, t): """ Record the fact that the given DocTest (`test`) generated `f` failures out of `t` tried examples. """ f2, t2 = self._name2ft.get(test.name, (0,0)) self._name2ft[test.name] = (f+f2, t+t2) self.failures += f self.tries += t __LINECACHE_FILENAME_RE = re.compile(r'[\w\.]+)' r'\[(?P\d+)\]>$') def __patched_linecache_getlines(self, filename, module_globals=None): m = self.__LINECACHE_FILENAME_RE.match(filename) if m and m.group('name') == self.test.name: example = self.test.examples[int(m.group('examplenum'))] return example.source.splitlines(True) else: return self.save_linecache_getlines(filename)#?, module_globals) def run(self, test, compileflags=None, out=None, clear_globs=True): """ Run the examples in `test`, and display the results using the writer function `out`. The examples are run in the namespace `test.globs`. If `clear_globs` is true (the default), then this namespace will be cleared after the test runs, to help with garbage collection. If you would like to examine the namespace after the test completes, then use `clear_globs=False`. `compileflags` gives the set of flags that should be used by the Python compiler when running the examples. If not specified, then it will default to the set of future-import flags that apply to `globs`. The output of each example is checked using `DocTestRunner.check_output`, and the results are formatted by the `DocTestRunner.report_*` methods. """ self.test = test if compileflags is None: compileflags = _extract_future_flags(test.globs) save_stdout = sys.stdout if out is None: out = save_stdout.write sys.stdout = self._fakeout # Patch pdb.set_trace to restore sys.stdout during interactive # debugging (so it's not still redirected to self._fakeout). # Note that the interactive output will go to *our* # save_stdout, even if that's not the real sys.stdout; this # allows us to write test cases for the set_trace behavior. save_set_trace = pdb.set_trace self.debugger = _OutputRedirectingPdb(save_stdout) self.debugger.reset() pdb.set_trace = self.debugger.set_trace # Patch linecache.getlines, so we can see the example's source # when we're inside the debugger. self.save_linecache_getlines = linecache.getlines linecache.getlines = self.__patched_linecache_getlines try: return self.__run(test, compileflags, out) finally: sys.stdout = save_stdout pdb.set_trace = save_set_trace linecache.getlines = self.save_linecache_getlines if clear_globs: test.globs.clear() #///////////////////////////////////////////////////////////////// # Summarization #///////////////////////////////////////////////////////////////// def summarize(self, verbose=None): """ Print a summary of all the test cases that have been run by this DocTestRunner, and return a tuple `(f, t)`, where `f` is the total number of failed examples, and `t` is the total number of tried examples. The optional `verbose` argument controls how detailed the summary is. If the verbosity is not specified, then the DocTestRunner's verbosity is used. """ if verbose is None: verbose = self._verbose notests = [] passed = [] failed = [] totalt = totalf = 0 for x in self._name2ft.items(): name, (f, t) = x assert f <= t totalt += t totalf += f if t == 0: notests.append(name) elif f == 0: passed.append( (name, t) ) else: failed.append(x) if verbose: if notests: print len(notests), "items had no tests:" notests.sort() for thing in notests: print " ", thing if passed: print len(passed), "items passed all tests:" passed.sort() for thing, count in passed: print " %3d tests in %s" % (count, thing) if failed: print self.DIVIDER print len(failed), "items had failures:" failed.sort() for thing, (f, t) in failed: print " %3d of %3d in %s" % (f, t, thing) if verbose: print totalt, "tests in", len(self._name2ft), "items." print totalt - totalf, "passed and", totalf, "failed." if totalf: print "***Test Failed***", totalf, "failures." elif verbose: print "Test passed." return totalf, totalt #///////////////////////////////////////////////////////////////// # Backward compatibility cruft to maintain doctest.master. #///////////////////////////////////////////////////////////////// def merge(self, other): d = self._name2ft for name, (f, t) in other._name2ft.items(): if name in d: print "*** DocTestRunner.merge: '" + name + "' in both" \ " testers; summing outcomes." f2, t2 = d[name] f = f + f2 t = t + t2 d[name] = f, t class OutputChecker: """ A class used to check the whether the actual output from a doctest example matches the expected output. `OutputChecker` defines two methods: `check_output`, which compares a given pair of outputs, and returns true if they match; and `output_difference`, which returns a string describing the differences between two outputs. """ def check_output(self, want, got, optionflags): """ Return True iff the actual output from an example (`got`) matches the expected output (`want`). These strings are always considered to match if they are identical; but depending on what option flags the test runner is using, several non-exact match types are also possible. See the documentation for `TestRunner` for more information about option flags. """ # Handle the common case first, for efficiency: # if they're string-identical, always return true. if got == want: return True # The values True and False replaced 1 and 0 as the return # value for boolean comparisons in Python 2.3. if not (optionflags & DONT_ACCEPT_TRUE_FOR_1): if (got,want) == ("True\n", "1\n"): return True if (got,want) == ("False\n", "0\n"): return True # can be used as a special sequence to signify a # blank line, unless the DONT_ACCEPT_BLANKLINE flag is used. if not (optionflags & DONT_ACCEPT_BLANKLINE): # Replace in want with a blank line. want = re.sub('(?m)^%s\s*?$' % re.escape(BLANKLINE_MARKER), '', want) # If a line in got contains only spaces, then remove the # spaces. got = re.sub('(?m)^\s*?$', '', got) if got == want: return True # This flag causes doctest to ignore any differences in the # contents of whitespace strings. Note that this can be used # in conjunction with the ELLIPSIS flag. if optionflags & NORMALIZE_WHITESPACE: got = ' '.join(got.split()) want = ' '.join(want.split()) if got == want: return True # The ELLIPSIS flag says to let the sequence "..." in `want` # match any substring in `got`. if optionflags & ELLIPSIS: if _ellipsis_match(want, got): return True # We didn't find any match; return false. return False # Should we do a fancy diff? def _do_a_fancy_diff(self, want, got, optionflags): # Not unless they asked for a fancy diff. if not optionflags & (REPORT_UDIFF | REPORT_CDIFF | REPORT_NDIFF): return False # If expected output uses ellipsis, a meaningful fancy diff is # too hard ... or maybe not. In two real-life failures Tim saw, # a diff was a major help anyway, so this is commented out. # [todo] _ellipsis_match() knows which pieces do and don't match, # and could be the basis for a kick-ass diff in this case. ##if optionflags & ELLIPSIS and ELLIPSIS_MARKER in want: ## return False # ndiff does intraline difference marking, so can be useful even # for 1-line differences. if optionflags & REPORT_NDIFF: return True # The other diff types need at least a few lines to be helpful. return want.count('\n') > 2 and got.count('\n') > 2 def output_difference(self, example, got, optionflags): """ Return a string describing the differences between the expected output for a given example (`example`) and the actual output (`got`). `optionflags` is the set of option flags used to compare `want` and `got`. """ want = example.want # If s are being used, then replace blank lines # with in the actual output string. if not (optionflags & DONT_ACCEPT_BLANKLINE): got = re.sub('(?m)^[ ]*(?=\n)', BLANKLINE_MARKER, got) # Check if we should use diff. if self._do_a_fancy_diff(want, got, optionflags): # Split want & got into lines. want_lines = want.splitlines(True) # True == keep line ends got_lines = got.splitlines(True) # Use difflib to find their differences. if optionflags & REPORT_UDIFF: diff = difflib.unified_diff(want_lines, got_lines, n=2) diff = list(diff)[2:] # strip the diff header kind = 'unified diff with -expected +actual' elif optionflags & REPORT_CDIFF: diff = difflib.context_diff(want_lines, got_lines, n=2) diff = list(diff)[2:] # strip the diff header kind = 'context diff with expected followed by actual' elif optionflags & REPORT_NDIFF: engine = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) diff = list(engine.compare(want_lines, got_lines)) kind = 'ndiff with -expected +actual' else: assert 0, 'Bad diff option' # Remove trailing whitespace on diff output. diff = [line.rstrip() + '\n' for line in diff] return 'Differences (%s):\n' % kind + _indent(''.join(diff)) # If we're not using diff, then simply list the expected # output followed by the actual output. if want and got: return 'Expected:\n%sGot:\n%s' % (_indent(want), _indent(got)) elif want: return 'Expected:\n%sGot nothing\n' % _indent(want) elif got: return 'Expected nothing\nGot:\n%s' % _indent(got) else: return 'Expected nothing\nGot nothing\n' class DocTestFailure(Exception): """A DocTest example has failed in debugging mode. The exception instance has variables: - test: the DocTest object being run - excample: the Example object that failed - got: the actual output """ def __init__(self, test, example, got): self.test = test self.example = example self.got = got def __str__(self): return str(self.test) class UnexpectedException(Exception): """A DocTest example has encountered an unexpected exception The exception instance has variables: - test: the DocTest object being run - excample: the Example object that failed - exc_info: the exception info """ def __init__(self, test, example, exc_info): self.test = test self.example = example self.exc_info = exc_info def __str__(self): return str(self.test) class DebugRunner(DocTestRunner): r"""Run doc tests but raise an exception as soon as there is a failure. If an unexpected exception occurs, an UnexpectedException is raised. It contains the test, the example, and the original exception: >>> runner = DebugRunner(verbose=False) >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42', ... {}, 'foo', 'foo.py', 0) >>> try: ... runner.run(test) ... except UnexpectedException, failure: ... pass >>> failure.test is test True >>> failure.example.want '42\n' >>> exc_info = failure.exc_info >>> raise exc_info[0], exc_info[1], exc_info[2] Traceback (most recent call last): ... KeyError We wrap the original exception to give the calling application access to the test and example information. If the output doesn't match, then a DocTestFailure is raised: >>> test = DocTestParser().get_doctest(''' ... >>> x = 1 ... >>> x ... 2 ... ''', {}, 'foo', 'foo.py', 0) >>> try: ... runner.run(test) ... except DocTestFailure, failure: ... pass DocTestFailure objects provide access to the test: >>> failure.test is test True As well as to the example: >>> failure.example.want '2\n' and the actual output: >>> failure.got '1\n' If a failure or error occurs, the globals are left intact: >>> del test.globs['__builtins__'] >>> test.globs {'x': 1} >>> test = DocTestParser().get_doctest(''' ... >>> x = 2 ... >>> raise KeyError ... ''', {}, 'foo', 'foo.py', 0) >>> runner.run(test) Traceback (most recent call last): ... UnexpectedException: >>> del test.globs['__builtins__'] >>> test.globs {'x': 2} But the globals are cleared if there is no error: >>> test = DocTestParser().get_doctest(''' ... >>> x = 2 ... ''', {}, 'foo', 'foo.py', 0) >>> runner.run(test) (0, 1) >>> test.globs {} """ def run(self, test, compileflags=None, out=None, clear_globs=True): r = DocTestRunner.run(self, test, compileflags, out, False) if clear_globs: test.globs.clear() return r def report_unexpected_exception(self, out, test, example, exc_info): raise UnexpectedException(test, example, exc_info) def report_failure(self, out, test, example, got): raise DocTestFailure(test, example, got) ###################################################################### ## 6. Test Functions ###################################################################### # These should be backwards compatible. # For backward compatibility, a global instance of a DocTestRunner # class, updated by testmod. master = None def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False): """m=None, name=None, globs=None, verbose=None, isprivate=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, exclude_empty=False Test examples in docstrings in functions and classes reachable from module m (or the current module if m is not supplied), starting with m.__doc__. Unless isprivate is specified, private names are not skipped. Also test examples reachable from dict m.__test__ if it exists and is not None. m.__test__ maps names to functions, classes and strings; function and class docstrings are tested even if the name is private; strings are tested directly, as if they were docstrings. Return (#failures, #tests). See doctest.__doc__ for an overview. Optional keyword arg "name" gives the name of the module; by default use m.__name__. Optional keyword arg "globs" gives a dict to be used as the globals when executing examples; by default, use m.__dict__. A copy of this dict is actually used for each docstring, so that each docstring's examples start with a clean slate. Optional keyword arg "extraglobs" gives a dictionary that should be merged into the globals that are used to execute examples. By default, no extra globals are used. This is new in 2.4. Optional keyword arg "verbose" prints lots of stuff if true, prints only failures if false; by default, it's true iff "-v" is in sys.argv. Optional keyword arg "report" prints a summary at the end when true, else prints nothing at the end. In verbose mode, the summary is detailed, else very brief (in fact, empty if all tests passed). Optional keyword arg "optionflags" or's together module constants, and defaults to 0. This is new in 2.3. Possible values (see the docs for details): DONT_ACCEPT_TRUE_FOR_1 DONT_ACCEPT_BLANKLINE NORMALIZE_WHITESPACE ELLIPSIS IGNORE_EXCEPTION_DETAIL REPORT_UDIFF REPORT_CDIFF REPORT_NDIFF REPORT_ONLY_FIRST_FAILURE Optional keyword arg "raise_on_error" raises an exception on the first unexpected exception or failure. This allows failures to be post-mortem debugged. Deprecated in Python 2.4: Optional keyword arg "isprivate" specifies a function used to determine whether a name is private. The default function is treat all functions as public. Optionally, "isprivate" can be set to doctest.is_private to skip over functions marked as private using the underscore naming convention; see its docs for details. Advanced tomfoolery: testmod runs methods of a local instance of class doctest.Tester, then merges the results into (or creates) global Tester instance doctest.master. Methods of doctest.master can be called directly too, if you want to do something unusual. Passing report=0 to testmod is especially useful then, to delay displaying a summary. Invoke doctest.master.summarize(verbose) when you're done fiddling. """ global master if isprivate is not None: warnings.warn("the isprivate argument is deprecated; " "examine DocTestFinder.find() lists instead", DeprecationWarning) # If no module was given, then use __main__. if m is None: # DWA - m will still be None if this wasn't invoked from the command # line, in which case the following TypeError is about as good an error # as we should expect m = sys.modules.get('__main__') # Check that we were actually given a module. if not inspect.ismodule(m): raise TypeError("testmod: module required; %r" % (m,)) # If no name was given, then use the module's name. if name is None: name = m.__name__ # Find, parse, and run all tests in the given module. finder = DocTestFinder(_namefilter=isprivate, exclude_empty=exclude_empty) if raise_on_error: runner = DebugRunner(verbose=verbose, optionflags=optionflags) else: runner = DocTestRunner(verbose=verbose, optionflags=optionflags) for test in finder.find(m, name, globs=globs, extraglobs=extraglobs): runner.run(test) if report: runner.summarize() if master is None: master = runner else: master.merge(runner) return runner.failures, runner.tries def testfile(filename, module_relative=True, name=None, package=None, globs=None, verbose=None, report=True, optionflags=0, extraglobs=None, raise_on_error=False, parser=DocTestParser()): """ Test examples in the given file. Return (#failures, #tests). Optional keyword arg "module_relative" specifies how filenames should be interpreted: - If "module_relative" is True (the default), then "filename" specifies a module-relative path. By default, this path is relative to the calling module's directory; but if the "package" argument is specified, then it is relative to that package. To ensure os-independence, "filename" should use "/" characters to separate path segments, and should not be an absolute path (i.e., it may not begin with "/"). - If "module_relative" is False, then "filename" specifies an os-specific path. The path may be absolute or relative (to the current working directory). Optional keyword arg "name" gives the name of the test; by default use the file's basename. Optional keyword argument "package" is a Python package or the name of a Python package whose directory should be used as the base directory for a module relative filename. If no package is specified, then the calling module's directory is used as the base directory for module relative filenames. It is an error to specify "package" if "module_relative" is False. Optional keyword arg "globs" gives a dict to be used as the globals when executing examples; by default, use {}. A copy of this dict is actually used for each docstring, so that each docstring's examples start with a clean slate. Optional keyword arg "extraglobs" gives a dictionary that should be merged into the globals that are used to execute examples. By default, no extra globals are used. Optional keyword arg "verbose" prints lots of stuff if true, prints only failures if false; by default, it's true iff "-v" is in sys.argv. Optional keyword arg "report" prints a summary at the end when true, else prints nothing at the end. In verbose mode, the summary is detailed, else very brief (in fact, empty if all tests passed). Optional keyword arg "optionflags" or's together module constants, and defaults to 0. Possible values (see the docs for details): DONT_ACCEPT_TRUE_FOR_1 DONT_ACCEPT_BLANKLINE NORMALIZE_WHITESPACE ELLIPSIS IGNORE_EXCEPTION_DETAIL REPORT_UDIFF REPORT_CDIFF REPORT_NDIFF REPORT_ONLY_FIRST_FAILURE Optional keyword arg "raise_on_error" raises an exception on the first unexpected exception or failure. This allows failures to be post-mortem debugged. Optional keyword arg "parser" specifies a DocTestParser (or subclass) that should be used to extract tests from the files. Advanced tomfoolery: testmod runs methods of a local instance of class doctest.Tester, then merges the results into (or creates) global Tester instance doctest.master. Methods of doctest.master can be called directly too, if you want to do something unusual. Passing report=0 to testmod is especially useful then, to delay displaying a summary. Invoke doctest.master.summarize(verbose) when you're done fiddling. """ global master if package and not module_relative: raise ValueError("Package may only be specified for module-" "relative paths.") # Relativize the path if module_relative: package = _normalize_module(package) filename = _module_relative_path(package, filename) # If no name was given, then use the file's name. if name is None: name = os.path.basename(filename) # Assemble the globals. if globs is None: globs = {} else: globs = globs.copy() if extraglobs is not None: globs.update(extraglobs) if raise_on_error: runner = DebugRunner(verbose=verbose, optionflags=optionflags) else: runner = DocTestRunner(verbose=verbose, optionflags=optionflags) # Read the file, convert it to a test, and run it. s = open(filename).read() test = parser.get_doctest(s, globs, name, filename, 0) runner.run(test) if report: runner.summarize() if master is None: master = runner else: master.merge(runner) return runner.failures, runner.tries def run_docstring_examples(f, globs, verbose=False, name="NoName", compileflags=None, optionflags=0): """ Test examples in the given object's docstring (`f`), using `globs` as globals. Optional argument `name` is used in failure messages. If the optional argument `verbose` is true, then generate output even if there are no failures. `compileflags` gives the set of flags that should be used by the Python compiler when running the examples. If not specified, then it will default to the set of future-import flags that apply to `globs`. Optional keyword arg `optionflags` specifies options for the testing and output. See the documentation for `testmod` for more information. """ # Find, parse, and run all tests in the given module. finder = DocTestFinder(verbose=verbose, recurse=False) runner = DocTestRunner(verbose=verbose, optionflags=optionflags) for test in finder.find(f, name, globs=globs): runner.run(test, compileflags=compileflags) ###################################################################### ## 7. Tester ###################################################################### # This is provided only for backwards compatibility. It's not # actually used in any way. class Tester: def __init__(self, mod=None, globs=None, verbose=None, isprivate=None, optionflags=0): warnings.warn("class Tester is deprecated; " "use class doctest.DocTestRunner instead", DeprecationWarning, stacklevel=2) if mod is None and globs is None: raise TypeError("Tester.__init__: must specify mod or globs") if mod is not None and not inspect.ismodule(mod): raise TypeError("Tester.__init__: mod must be a module; %r" % (mod,)) if globs is None: globs = mod.__dict__ self.globs = globs self.verbose = verbose self.isprivate = isprivate self.optionflags = optionflags self.testfinder = DocTestFinder(_namefilter=isprivate) self.testrunner = DocTestRunner(verbose=verbose, optionflags=optionflags) def runstring(self, s, name): test = DocTestParser().get_doctest(s, self.globs, name, None, None) if self.verbose: print "Running string", name (f,t) = self.testrunner.run(test) if self.verbose: print f, "of", t, "examples failed in string", name return (f,t) def rundoc(self, object, name=None, module=None): f = t = 0 tests = self.testfinder.find(object, name, module=module, globs=self.globs) for test in tests: (f2, t2) = self.testrunner.run(test) (f,t) = (f+f2, t+t2) return (f,t) def rundict(self, d, name, module=None): import new m = new.module(name) m.__dict__.update(d) if module is None: module = False return self.rundoc(m, name, module) def run__test__(self, d, name): import new m = new.module(name) m.__test__ = d return self.rundoc(m, name) def summarize(self, verbose=None): return self.testrunner.summarize(verbose) def merge(self, other): self.testrunner.merge(other.testrunner) ###################################################################### ## 8. Unittest Support ###################################################################### _unittest_reportflags = 0 def set_unittest_reportflags(flags): """Sets the unittest option flags. The old flag is returned so that a runner could restore the old value if it wished to: >>> old = _unittest_reportflags >>> set_unittest_reportflags(REPORT_NDIFF | ... REPORT_ONLY_FIRST_FAILURE) == old True >>> import doctest >>> doctest._unittest_reportflags == (REPORT_NDIFF | ... REPORT_ONLY_FIRST_FAILURE) True Only reporting flags can be set: >>> set_unittest_reportflags(ELLIPSIS) Traceback (most recent call last): ... ValueError: ('Only reporting flags allowed', 8) >>> set_unittest_reportflags(old) == (REPORT_NDIFF | ... REPORT_ONLY_FIRST_FAILURE) True """ global _unittest_reportflags if (flags & REPORTING_FLAGS) != flags: raise ValueError("Only reporting flags allowed", flags) old = _unittest_reportflags _unittest_reportflags = flags return old class DocTestCase(unittest.TestCase): def __init__(self, test, optionflags=0, setUp=None, tearDown=None, checker=None): unittest.TestCase.__init__(self) self._dt_optionflags = optionflags self._dt_checker = checker self._dt_test = test self._dt_setUp = setUp self._dt_tearDown = tearDown def setUp(self): test = self._dt_test if self._dt_setUp is not None: self._dt_setUp(test) def tearDown(self): test = self._dt_test if self._dt_tearDown is not None: self._dt_tearDown(test) test.globs.clear() def runTest(self): test = self._dt_test old = sys.stdout new = StringIO() optionflags = self._dt_optionflags if not (optionflags & REPORTING_FLAGS): # The option flags don't include any reporting flags, # so add the default reporting flags optionflags |= _unittest_reportflags runner = DocTestRunner(optionflags=optionflags, checker=self._dt_checker, verbose=False) try: runner.DIVIDER = "-"*70 failures, tries = runner.run( test, out=new.write, clear_globs=False) finally: sys.stdout = old if failures: raise self.failureException(self.format_failure(new.getvalue())) def format_failure(self, err): test = self._dt_test if test.lineno is None: lineno = 'unknown line number' else: lineno = '%s' % test.lineno lname = '.'.join(test.name.split('.')[-1:]) return ('Failed doctest test for %s\n' ' File "%s", line %s, in %s\n\n%s' % (test.name, test.filename, lineno, lname, err) ) def debug(self): r"""Run the test case without results and without catching exceptions The unit test framework includes a debug method on test cases and test suites to support post-mortem debugging. The test code is run in such a way that errors are not caught. This way a caller can catch the errors and initiate post-mortem debugging. The DocTestCase provides a debug method that raises UnexpectedException errors if there is an unexepcted exception: >>> test = DocTestParser().get_doctest('>>> raise KeyError\n42', ... {}, 'foo', 'foo.py', 0) >>> case = DocTestCase(test) >>> try: ... case.debug() ... except UnexpectedException, failure: ... pass The UnexpectedException contains the test, the example, and the original exception: >>> failure.test is test True >>> failure.example.want '42\n' >>> exc_info = failure.exc_info >>> raise exc_info[0], exc_info[1], exc_info[2] Traceback (most recent call last): ... KeyError If the output doesn't match, then a DocTestFailure is raised: >>> test = DocTestParser().get_doctest(''' ... >>> x = 1 ... >>> x ... 2 ... ''', {}, 'foo', 'foo.py', 0) >>> case = DocTestCase(test) >>> try: ... case.debug() ... except DocTestFailure, failure: ... pass DocTestFailure objects provide access to the test: >>> failure.test is test True As well as to the example: >>> failure.example.want '2\n' and the actual output: >>> failure.got '1\n' """ self.setUp() runner = DebugRunner(optionflags=self._dt_optionflags, checker=self._dt_checker, verbose=False) runner.run(self._dt_test) self.tearDown() def id(self): return self._dt_test.name def __repr__(self): name = self._dt_test.name.split('.') return "%s (%s)" % (name[-1], '.'.join(name[:-1])) __str__ = __repr__ def shortDescription(self): return "Doctest: " + self._dt_test.name def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, **options): """ Convert doctest tests for a module to a unittest test suite. This converts each documentation string in a module that contains doctest tests to a unittest test case. If any of the tests in a doc string fail, then the test case fails. An exception is raised showing the name of the file containing the test and a (sometimes approximate) line number. The `module` argument provides the module to be tested. The argument can be either a module or a module name. If no argument is given, the calling module is used. A number of options may be provided as keyword arguments: setUp A set-up function. This is called before running the tests in each file. The setUp function will be passed a DocTest object. The setUp function can access the test globals as the globs attribute of the test passed. tearDown A tear-down function. This is called after running the tests in each file. The tearDown function will be passed a DocTest object. The tearDown function can access the test globals as the globs attribute of the test passed. globs A dictionary containing initial global variables for the tests. optionflags A set of doctest option flags expressed as an integer. """ if test_finder is None: test_finder = DocTestFinder() module = _normalize_module(module) tests = test_finder.find(module, globs=globs, extraglobs=extraglobs) if globs is None: globs = module.__dict__ if not tests: # Why do we want to do this? Because it reveals a bug that might # otherwise be hidden. raise ValueError(module, "has no tests") tests.sort() suite = unittest.TestSuite() for test in tests: if len(test.examples) == 0: continue if not test.filename: filename = module.__file__ if filename[-4:] in (".pyc", ".pyo"): filename = filename[:-1] test.filename = filename suite.addTest(DocTestCase(test, **options)) return suite class DocFileCase(DocTestCase): def id(self): return '_'.join(self._dt_test.name.split('.')) def __repr__(self): return self._dt_test.filename __str__ = __repr__ def format_failure(self, err): return ('Failed doctest test for %s\n File "%s", line 0\n\n%s' % (self._dt_test.name, self._dt_test.filename, err) ) def DocFileTest(path, module_relative=True, package=None, globs=None, parser=DocTestParser(), **options): if globs is None: globs = {} if package and not module_relative: raise ValueError("Package may only be specified for module-" "relative paths.") # Relativize the path. if module_relative: package = _normalize_module(package) path = _module_relative_path(package, path) # Find the file and read it. name = os.path.basename(path) doc = open(path).read() # Convert it to a test, and wrap it in a DocFileCase. test = parser.get_doctest(doc, globs, name, path, 0) return DocFileCase(test, **options) def DocFileSuite(*paths, **kw): """A unittest suite for one or more doctest files. The path to each doctest file is given as a string; the interpretation of that string depends on the keyword argument "module_relative". A number of options may be provided as keyword arguments: module_relative If "module_relative" is True, then the given file paths are interpreted as os-independent module-relative paths. By default, these paths are relative to the calling module's directory; but if the "package" argument is specified, then they are relative to that package. To ensure os-independence, "filename" should use "/" characters to separate path segments, and may not be an absolute path (i.e., it may not begin with "/"). If "module_relative" is False, then the given file paths are interpreted as os-specific paths. These paths may be absolute or relative (to the current working directory). package A Python package or the name of a Python package whose directory should be used as the base directory for module relative paths. If "package" is not specified, then the calling module's directory is used as the base directory for module relative filenames. It is an error to specify "package" if "module_relative" is False. setUp A set-up function. This is called before running the tests in each file. The setUp function will be passed a DocTest object. The setUp function can access the test globals as the globs attribute of the test passed. tearDown A tear-down function. This is called after running the tests in each file. The tearDown function will be passed a DocTest object. The tearDown function can access the test globals as the globs attribute of the test passed. globs A dictionary containing initial global variables for the tests. optionflags A set of doctest option flags expressed as an integer. parser A DocTestParser (or subclass) that should be used to extract tests from the files. """ suite = unittest.TestSuite() # We do this here so that _normalize_module is called at the right # level. If it were called in DocFileTest, then this function # would be the caller and we might guess the package incorrectly. if kw.get('module_relative', True): kw['package'] = _normalize_module(kw.get('package')) for path in paths: suite.addTest(DocFileTest(path, **kw)) return suite ###################################################################### ## 9. Debugging Support ###################################################################### def script_from_examples(s): r"""Extract script from text with examples. Converts text with examples to a Python script. Example input is converted to regular code. Example output and all other words are converted to comments: >>> text = ''' ... Here are examples of simple math. ... ... Python has super accurate integer addition ... ... >>> 2 + 2 ... 5 ... ... And very friendly error messages: ... ... >>> 1/0 ... To Infinity ... And ... Beyond ... ... You can use logic if you want: ... ... >>> if 0: ... ... blah ... ... blah ... ... ... ... Ho hum ... ''' >>> print script_from_examples(text) # Here are examples of simple math. # # Python has super accurate integer addition # 2 + 2 # Expected: ## 5 # # And very friendly error messages: # 1/0 # Expected: ## To Infinity ## And ## Beyond # # You can use logic if you want: # if 0: blah blah # # Ho hum """ output = [] for piece in DocTestParser().parse(s): if isinstance(piece, Example): # Add the example's source code (strip trailing NL) output.append(piece.source[:-1]) # Add the expected output: want = piece.want if want: output.append('# Expected:') output += ['## '+l for l in want.split('\n')[:-1]] else: # Add non-example text. output += [_comment_line(l) for l in piece.split('\n')[:-1]] # Trim junk on both ends. while output and output[-1] == '#': output.pop() while output and output[0] == '#': output.pop(0) # Combine the output, and return it. return '\n'.join(output) def testsource(module, name): """Extract the test sources from a doctest docstring as a script. Provide the module (or dotted name of the module) containing the test to be debugged and the name (within the module) of the object with the doc string with tests to be debugged. """ module = _normalize_module(module) tests = DocTestFinder().find(module) test = [t for t in tests if t.name == name] if not test: raise ValueError(name, "not found in tests") test = test[0] testsrc = script_from_examples(test.docstring) return testsrc def debug_src(src, pm=False, globs=None): """Debug a single doctest docstring, in argument `src`'""" testsrc = script_from_examples(src) debug_script(testsrc, pm, globs) def debug_script(src, pm=False, globs=None): "Debug a test script. `src` is the script, as a string." import pdb # Note that tempfile.NameTemporaryFile() cannot be used. As the # docs say, a file so created cannot be opened by name a second time # on modern Windows boxes, and execfile() needs to open it. srcfilename = tempfile.mktemp(".py", "doctestdebug") f = open(srcfilename, 'w') f.write(src) f.close() try: if globs: globs = globs.copy() else: globs = {} if pm: try: execfile(srcfilename, globs, globs) except: print sys.exc_info()[1] pdb.post_mortem(sys.exc_info()[2]) else: # Note that %r is vital here. '%s' instead can, e.g., cause # backslashes to get treated as metacharacters on Windows. pdb.run("execfile(%r)" % srcfilename, globs, globs) finally: os.remove(srcfilename) def debug(module, name, pm=False): """Debug a single doctest docstring. Provide the module (or dotted name of the module) containing the test to be debugged and the name (within the module) of the object with the docstring with tests to be debugged. """ module = _normalize_module(module) testsrc = testsource(module, name) debug_script(testsrc, pm, module.__dict__) ###################################################################### ## 10. Example Usage ###################################################################### class _TestClass: """ A pointless class, for sanity-checking of docstring testing. Methods: square() get() >>> _TestClass(13).get() + _TestClass(-12).get() 1 >>> hex(_TestClass(13).square().get()) '0xa9' """ def __init__(self, val): """val -> _TestClass object with associated value val. >>> t = _TestClass(123) >>> print t.get() 123 """ self.val = val def square(self): """square() -> square TestClass's associated value >>> _TestClass(13).square().get() 169 """ self.val = self.val ** 2 return self def get(self): """get() -> return TestClass's associated value. >>> x = _TestClass(-42) >>> print x.get() -42 """ return self.val __test__ = {"_TestClass": _TestClass, "string": r""" Example of a string object, searched as-is. >>> x = 1; y = 2 >>> x + y, x * y (3, 2) """, "bool-int equivalence": r""" In 2.2, boolean expressions displayed 0 or 1. By default, we still accept them. This can be disabled by passing DONT_ACCEPT_TRUE_FOR_1 to the new optionflags argument. >>> 4 == 4 1 >>> 4 == 4 True >>> 4 > 4 0 >>> 4 > 4 False """, "blank lines": r""" Blank lines can be marked with : >>> print 'foo\n\nbar\n' foo bar """, "ellipsis": r""" If the ellipsis flag is used, then '...' can be used to elide substrings in the desired output: >>> print range(1000) #doctest: +ELLIPSIS [0, 1, 2, ..., 999] """, "whitespace normalization": r""" If the whitespace normalization flag is used, then differences in whitespace are ignored. >>> print range(30) #doctest: +NORMALIZE_WHITESPACE [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] """, } def _test(): r = unittest.TextTestRunner() r.run(DocTestSuite()) if __name__ == "__main__": _test() Paste-1.7.5.1/paste/util/quoting.py0000664000175000017500000000465411443715766015525 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php import cgi import htmlentitydefs import urllib import re __all__ = ['html_quote', 'html_unquote', 'url_quote', 'url_unquote', 'strip_html'] default_encoding = 'UTF-8' def html_quote(v, encoding=None): r""" Quote the value (turned to a string) as HTML. This quotes <, >, and quotes: >>> html_quote(1) '1' >>> html_quote(None) '' >>> html_quote('') '<hey!>' >>> html_quote(u'\u1029') '\xe1\x80\xa9' """ encoding = encoding or default_encoding if v is None: return '' elif isinstance(v, str): return cgi.escape(v, 1) elif isinstance(v, unicode): return cgi.escape(v.encode(encoding), 1) else: return cgi.escape(unicode(v).encode(encoding), 1) _unquote_re = re.compile(r'&([a-zA-Z]+);') def _entity_subber(match, name2c=htmlentitydefs.name2codepoint): code = name2c.get(match.group(1)) if code: return unichr(code) else: return match.group(0) def html_unquote(s, encoding=None): r""" Decode the value. >>> html_unquote('<hey you>') u'' >>> html_unquote('') u'' >>> html_unquote('&blahblah;') u'&blahblah;' >>> html_unquote('\xe1\x80\xa9') u'\u1029' """ if isinstance(s, str): if s == '': # workaround re.sub('', '', u'') returning '' < 2.5.2 # instead of u'' >= 2.5.2 return u'' s = s.decode(encoding or default_encoding) return _unquote_re.sub(_entity_subber, s) def strip_html(s): # should this use html_unquote? s = re.sub('<.*?>', '', s) s = html_unquote(s) return s def no_quote(s): """ Quoting that doesn't do anything """ return s _comment_quote_re = re.compile(r'\-\s*\>') # Everything but \r, \n, \t: _bad_chars_re = re.compile('[\x00-\x08\x0b-\x0c\x0e-\x1f]') def comment_quote(s): """ Quote that makes sure text can't escape a comment """ comment = str(s) #comment = _bad_chars_re.sub('', comment) #print 'in ', repr(str(s)) #print 'out', repr(comment) comment = _comment_quote_re.sub('->', comment) return comment url_quote = urllib.quote url_unquote = urllib.unquote if __name__ == '__main__': import doctest doctest.testmod() Paste-1.7.5.1/paste/util/dateinterval.py0000664000175000017500000000455611354703364016512 0ustar ianbianb""" DateInterval.py Convert interval strings (in the form of 1w2d, etc) to seconds, and back again. Is not exactly about months or years (leap years in particular). Accepts (y)ear, (b)month, (w)eek, (d)ay, (h)our, (m)inute, (s)econd. Exports only timeEncode and timeDecode functions. """ import re __all__ = ['interval_decode', 'interval_encode'] second = 1 minute = second*60 hour = minute*60 day = hour*24 week = day*7 month = day*30 year = day*365 timeValues = { 'y': year, 'b': month, 'w': week, 'd': day, 'h': hour, 'm': minute, 's': second, } timeOrdered = timeValues.items() timeOrdered.sort(lambda a, b: -cmp(a[1], b[1])) def interval_encode(seconds, include_sign=False): """Encodes a number of seconds (representing a time interval) into a form like 1h2d3s. >>> interval_encode(10) '10s' >>> interval_encode(493939) '5d17h12m19s' """ s = '' orig = seconds seconds = abs(seconds) for char, amount in timeOrdered: if seconds >= amount: i, seconds = divmod(seconds, amount) s += '%i%s' % (i, char) if orig < 0: s = '-' + s elif not orig: return '0' elif include_sign: s = '+' + s return s _timeRE = re.compile(r'[0-9]+[a-zA-Z]') def interval_decode(s): """Decodes a number in the format 1h4d3m (1 hour, 3 days, 3 minutes) into a number of seconds >>> interval_decode('40s') 40 >>> interval_decode('10000s') 10000 >>> interval_decode('3d1w45s') 864045 """ time = 0 sign = 1 s = s.strip() if s.startswith('-'): s = s[1:] sign = -1 elif s.startswith('+'): s = s[1:] for match in allMatches(s, _timeRE): char = match.group(0)[-1].lower() if not timeValues.has_key(char): # @@: should signal error continue time += int(match.group(0)[:-1]) * timeValues[char] return time # @@-sgd 2002-12-23 - this function does not belong in this module, find a better place. def allMatches(source, regex): """Return a list of matches for regex in source """ pos = 0 end = len(source) rv = [] match = regex.search(source, pos) while match: rv.append(match) match = regex.search(source, match.end() ) return rv if __name__ == '__main__': import doctest doctest.testmod() Paste-1.7.5.1/paste/util/multidict.py0000664000175000017500000002622111354703364016017 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php import cgi import copy import sys from UserDict import DictMixin class MultiDict(DictMixin): """ An ordered dictionary that can have multiple values for each key. Adds the methods getall, getone, mixed, and add to the normal dictionary interface. """ def __init__(self, *args, **kw): if len(args) > 1: raise TypeError( "MultiDict can only be called with one positional argument") if args: if hasattr(args[0], 'iteritems'): items = list(args[0].iteritems()) elif hasattr(args[0], 'items'): items = args[0].items() else: items = list(args[0]) self._items = items else: self._items = [] self._items.extend(kw.iteritems()) def __getitem__(self, key): for k, v in self._items: if k == key: return v raise KeyError(repr(key)) def __setitem__(self, key, value): try: del self[key] except KeyError: pass self._items.append((key, value)) def add(self, key, value): """ Add the key and value, not overwriting any previous value. """ self._items.append((key, value)) def getall(self, key): """ Return a list of all values matching the key (may be an empty list) """ result = [] for k, v in self._items: if key == k: result.append(v) return result def getone(self, key): """ Get one value matching the key, raising a KeyError if multiple values were found. """ v = self.getall(key) if not v: raise KeyError('Key not found: %r' % key) if len(v) > 1: raise KeyError('Multiple values match %r: %r' % (key, v)) return v[0] def mixed(self): """ Returns a dictionary where the values are either single values, or a list of values when a key/value appears more than once in this dictionary. This is similar to the kind of dictionary often used to represent the variables in a web request. """ result = {} multi = {} for key, value in self._items: if key in result: # We do this to not clobber any lists that are # *actual* values in this dictionary: if key in multi: result[key].append(value) else: result[key] = [result[key], value] multi[key] = None else: result[key] = value return result def dict_of_lists(self): """ Returns a dictionary where each key is associated with a list of values. """ result = {} for key, value in self._items: if key in result: result[key].append(value) else: result[key] = [value] return result def __delitem__(self, key): items = self._items found = False for i in range(len(items)-1, -1, -1): if items[i][0] == key: del items[i] found = True if not found: raise KeyError(repr(key)) def __contains__(self, key): for k, v in self._items: if k == key: return True return False has_key = __contains__ def clear(self): self._items = [] def copy(self): return MultiDict(self) def setdefault(self, key, default=None): for k, v in self._items: if key == k: return v self._items.append((key, default)) return default def pop(self, key, *args): if len(args) > 1: raise TypeError, "pop expected at most 2 arguments, got "\ + repr(1 + len(args)) for i in range(len(self._items)): if self._items[i][0] == key: v = self._items[i][1] del self._items[i] return v if args: return args[0] else: raise KeyError(repr(key)) def popitem(self): return self._items.pop() def update(self, other=None, **kwargs): if other is None: pass elif hasattr(other, 'items'): self._items.extend(other.items()) elif hasattr(other, 'keys'): for k in other.keys(): self._items.append((k, other[k])) else: for k, v in other: self._items.append((k, v)) if kwargs: self.update(kwargs) def __repr__(self): items = ', '.join(['(%r, %r)' % v for v in self._items]) return '%s([%s])' % (self.__class__.__name__, items) def __len__(self): return len(self._items) ## ## All the iteration: ## def keys(self): return [k for k, v in self._items] def iterkeys(self): for k, v in self._items: yield k __iter__ = iterkeys def items(self): return self._items[:] def iteritems(self): return iter(self._items) def values(self): return [v for k, v in self._items] def itervalues(self): for k, v in self._items: yield v class UnicodeMultiDict(DictMixin): """ A MultiDict wrapper that decodes returned values to unicode on the fly. Decoding is not applied to assigned values. The key/value contents are assumed to be ``str``/``strs`` or ``str``/``FieldStorages`` (as is returned by the ``paste.request.parse_`` functions). Can optionally also decode keys when the ``decode_keys`` argument is True. ``FieldStorage`` instances are cloned, and the clone's ``filename`` variable is decoded. Its ``name`` variable is decoded when ``decode_keys`` is enabled. """ def __init__(self, multi=None, encoding=None, errors='strict', decode_keys=False): self.multi = multi if encoding is None: encoding = sys.getdefaultencoding() self.encoding = encoding self.errors = errors self.decode_keys = decode_keys def _decode_key(self, key): if self.decode_keys: try: key = key.decode(self.encoding, self.errors) except AttributeError: pass return key def _decode_value(self, value): """ Decode the specified value to unicode. Assumes value is a ``str`` or `FieldStorage`` object. ``FieldStorage`` objects are specially handled. """ if isinstance(value, cgi.FieldStorage): # decode FieldStorage's field name and filename value = copy.copy(value) if self.decode_keys: value.name = value.name.decode(self.encoding, self.errors) value.filename = value.filename.decode(self.encoding, self.errors) else: try: value = value.decode(self.encoding, self.errors) except AttributeError: pass return value def __getitem__(self, key): return self._decode_value(self.multi.__getitem__(key)) def __setitem__(self, key, value): self.multi.__setitem__(key, value) def add(self, key, value): """ Add the key and value, not overwriting any previous value. """ self.multi.add(key, value) def getall(self, key): """ Return a list of all values matching the key (may be an empty list) """ return [self._decode_value(v) for v in self.multi.getall(key)] def getone(self, key): """ Get one value matching the key, raising a KeyError if multiple values were found. """ return self._decode_value(self.multi.getone(key)) def mixed(self): """ Returns a dictionary where the values are either single values, or a list of values when a key/value appears more than once in this dictionary. This is similar to the kind of dictionary often used to represent the variables in a web request. """ unicode_mixed = {} for key, value in self.multi.mixed().iteritems(): if isinstance(value, list): value = [self._decode_value(value) for value in value] else: value = self._decode_value(value) unicode_mixed[self._decode_key(key)] = value return unicode_mixed def dict_of_lists(self): """ Returns a dictionary where each key is associated with a list of values. """ unicode_dict = {} for key, value in self.multi.dict_of_lists().iteritems(): value = [self._decode_value(value) for value in value] unicode_dict[self._decode_key(key)] = value return unicode_dict def __delitem__(self, key): self.multi.__delitem__(key) def __contains__(self, key): return self.multi.__contains__(key) has_key = __contains__ def clear(self): self.multi.clear() def copy(self): return UnicodeMultiDict(self.multi.copy(), self.encoding, self.errors) def setdefault(self, key, default=None): return self._decode_value(self.multi.setdefault(key, default)) def pop(self, key, *args): return self._decode_value(self.multi.pop(key, *args)) def popitem(self): k, v = self.multi.popitem() return (self._decode_key(k), self._decode_value(v)) def __repr__(self): items = ', '.join(['(%r, %r)' % v for v in self.items()]) return '%s([%s])' % (self.__class__.__name__, items) def __len__(self): return self.multi.__len__() ## ## All the iteration: ## def keys(self): return [self._decode_key(k) for k in self.multi.iterkeys()] def iterkeys(self): for k in self.multi.iterkeys(): yield self._decode_key(k) __iter__ = iterkeys def items(self): return [(self._decode_key(k), self._decode_value(v)) for \ k, v in self.multi.iteritems()] def iteritems(self): for k, v in self.multi.iteritems(): yield (self._decode_key(k), self._decode_value(v)) def values(self): return [self._decode_value(v) for v in self.multi.itervalues()] def itervalues(self): for v in self.multi.itervalues(): yield self._decode_value(v) __test__ = { 'general': """ >>> d = MultiDict(a=1, b=2) >>> d['a'] 1 >>> d.getall('c') [] >>> d.add('a', 2) >>> d['a'] 1 >>> d.getall('a') [1, 2] >>> d['b'] = 4 >>> d.getall('b') [4] >>> d.keys() ['a', 'a', 'b'] >>> d.items() [('a', 1), ('a', 2), ('b', 4)] >>> d.mixed() {'a': [1, 2], 'b': 4} >>> MultiDict([('a', 'b')], c=2) MultiDict([('a', 'b'), ('c', 2)]) """} if __name__ == '__main__': import doctest doctest.testmod() Paste-1.7.5.1/paste/util/template.py0000664000175000017500000005734711354703364015651 0ustar ianbianb""" A small templating language This implements a small templating language for use internally in Paste and Paste Script. This language implements if/elif/else, for/continue/break, expressions, and blocks of Python code. The syntax is:: {{any expression (function calls etc)}} {{any expression | filter}} {{for x in y}}...{{endfor}} {{if x}}x{{elif y}}y{{else}}z{{endif}} {{py:x=1}} {{py: def foo(bar): return 'baz' }} {{default var = default_value}} {{# comment}} You use this with the ``Template`` class or the ``sub`` shortcut. The ``Template`` class takes the template string and the name of the template (for errors) and a default namespace. Then (like ``string.Template``) you can call the ``tmpl.substitute(**kw)`` method to make a substitution (or ``tmpl.substitute(a_dict)``). ``sub(content, **kw)`` substitutes the template immediately. You can use ``__name='tmpl.html'`` to set the name of the template. If there are syntax errors ``TemplateError`` will be raised. """ import re import sys import cgi import urllib from paste.util.looper import looper __all__ = ['TemplateError', 'Template', 'sub', 'HTMLTemplate', 'sub_html', 'html', 'bunch'] token_re = re.compile(r'\{\{|\}\}') in_re = re.compile(r'\s+in\s+') var_re = re.compile(r'^[a-z_][a-z0-9_]*$', re.I) class TemplateError(Exception): """Exception raised while parsing a template """ def __init__(self, message, position, name=None): self.message = message self.position = position self.name = name def __str__(self): msg = '%s at line %s column %s' % ( self.message, self.position[0], self.position[1]) if self.name: msg += ' in %s' % self.name return msg class _TemplateContinue(Exception): pass class _TemplateBreak(Exception): pass class Template(object): default_namespace = { 'start_braces': '{{', 'end_braces': '}}', 'looper': looper, } default_encoding = 'utf8' def __init__(self, content, name=None, namespace=None): self.content = content self._unicode = isinstance(content, unicode) self.name = name self._parsed = parse(content, name=name) if namespace is None: namespace = {} self.namespace = namespace def from_filename(cls, filename, namespace=None, encoding=None): f = open(filename, 'rb') c = f.read() f.close() if encoding: c = c.decode(encoding) return cls(content=c, name=filename, namespace=namespace) from_filename = classmethod(from_filename) def __repr__(self): return '<%s %s name=%r>' % ( self.__class__.__name__, hex(id(self))[2:], self.name) def substitute(self, *args, **kw): if args: if kw: raise TypeError( "You can only give positional *or* keyword arguments") if len(args) > 1: raise TypeError( "You can only give on positional argument") kw = args[0] ns = self.default_namespace.copy() ns.update(self.namespace) ns.update(kw) result = self._interpret(ns) return result def _interpret(self, ns): __traceback_hide__ = True parts = [] self._interpret_codes(self._parsed, ns, out=parts) return ''.join(parts) def _interpret_codes(self, codes, ns, out): __traceback_hide__ = True for item in codes: if isinstance(item, basestring): out.append(item) else: self._interpret_code(item, ns, out) def _interpret_code(self, code, ns, out): __traceback_hide__ = True name, pos = code[0], code[1] if name == 'py': self._exec(code[2], ns, pos) elif name == 'continue': raise _TemplateContinue() elif name == 'break': raise _TemplateBreak() elif name == 'for': vars, expr, content = code[2], code[3], code[4] expr = self._eval(expr, ns, pos) self._interpret_for(vars, expr, content, ns, out) elif name == 'cond': parts = code[2:] self._interpret_if(parts, ns, out) elif name == 'expr': parts = code[2].split('|') base = self._eval(parts[0], ns, pos) for part in parts[1:]: func = self._eval(part, ns, pos) base = func(base) out.append(self._repr(base, pos)) elif name == 'default': var, expr = code[2], code[3] if var not in ns: result = self._eval(expr, ns, pos) ns[var] = result elif name == 'comment': return else: assert 0, "Unknown code: %r" % name def _interpret_for(self, vars, expr, content, ns, out): __traceback_hide__ = True for item in expr: if len(vars) == 1: ns[vars[0]] = item else: if len(vars) != len(item): raise ValueError( 'Need %i items to unpack (got %i items)' % (len(vars), len(item))) for name, value in zip(vars, item): ns[name] = value try: self._interpret_codes(content, ns, out) except _TemplateContinue: continue except _TemplateBreak: break def _interpret_if(self, parts, ns, out): __traceback_hide__ = True # @@: if/else/else gets through for part in parts: assert not isinstance(part, basestring) name, pos = part[0], part[1] if name == 'else': result = True else: result = self._eval(part[2], ns, pos) if result: self._interpret_codes(part[3], ns, out) break def _eval(self, code, ns, pos): __traceback_hide__ = True try: value = eval(code, ns) return value except: exc_info = sys.exc_info() e = exc_info[1] if getattr(e, 'args'): arg0 = e.args[0] else: arg0 = str(e) e.args = (self._add_line_info(arg0, pos),) raise exc_info[0], e, exc_info[2] def _exec(self, code, ns, pos): __traceback_hide__ = True try: exec code in ns except: exc_info = sys.exc_info() e = exc_info[1] e.args = (self._add_line_info(e.args[0], pos),) raise exc_info[0], e, exc_info[2] def _repr(self, value, pos): __traceback_hide__ = True try: if value is None: return '' if self._unicode: try: value = unicode(value) except UnicodeDecodeError: value = str(value) else: value = str(value) except: exc_info = sys.exc_info() e = exc_info[1] e.args = (self._add_line_info(e.args[0], pos),) raise exc_info[0], e, exc_info[2] else: if self._unicode and isinstance(value, str): if not self.decode_encoding: raise UnicodeDecodeError( 'Cannot decode str value %r into unicode ' '(no default_encoding provided)' % value) value = value.decode(self.default_encoding) elif not self._unicode and isinstance(value, unicode): if not self.decode_encoding: raise UnicodeEncodeError( 'Cannot encode unicode value %r into str ' '(no default_encoding provided)' % value) value = value.encode(self.default_encoding) return value def _add_line_info(self, msg, pos): msg = "%s at line %s column %s" % ( msg, pos[0], pos[1]) if self.name: msg += " in file %s" % self.name return msg def sub(content, **kw): name = kw.get('__name') tmpl = Template(content, name=name) return tmpl.substitute(kw) return result def paste_script_template_renderer(content, vars, filename=None): tmpl = Template(content, name=filename) return tmpl.substitute(vars) class bunch(dict): def __init__(self, **kw): for name, value in kw.items(): setattr(self, name, value) def __setattr__(self, name, value): self[name] = value def __getattr__(self, name): try: return self[name] except KeyError: raise AttributeError(name) def __getitem__(self, key): if 'default' in self: try: return dict.__getitem__(self, key) except KeyError: return dict.__getitem__(self, 'default') else: return dict.__getitem__(self, key) def __repr__(self): items = [ (k, v) for k, v in self.items()] items.sort() return '<%s %s>' % ( self.__class__.__name__, ' '.join(['%s=%r' % (k, v) for k, v in items])) ############################################################ ## HTML Templating ############################################################ class html(object): def __init__(self, value): self.value = value def __str__(self): return self.value def __repr__(self): return '<%s %r>' % ( self.__class__.__name__, self.value) def html_quote(value): if value is None: return '' if not isinstance(value, basestring): if hasattr(value, '__unicode__'): value = unicode(value) else: value = str(value) value = cgi.escape(value, 1) if isinstance(value, unicode): value = value.encode('ascii', 'xmlcharrefreplace') return value def url(v): if not isinstance(v, basestring): if hasattr(v, '__unicode__'): v = unicode(v) else: v = str(v) if isinstance(v, unicode): v = v.encode('utf8') return urllib.quote(v) def attr(**kw): kw = kw.items() kw.sort() parts = [] for name, value in kw: if value is None: continue if name.endswith('_'): name = name[:-1] parts.append('%s="%s"' % (html_quote(name), html_quote(value))) return html(' '.join(parts)) class HTMLTemplate(Template): default_namespace = Template.default_namespace.copy() default_namespace.update(dict( html=html, attr=attr, url=url, )) def _repr(self, value, pos): plain = Template._repr(self, value, pos) if isinstance(value, html): return plain else: return html_quote(plain) def sub_html(content, **kw): name = kw.get('__name') tmpl = HTMLTemplate(content, name=name) return tmpl.substitute(kw) return result ############################################################ ## Lexing and Parsing ############################################################ def lex(s, name=None, trim_whitespace=True): """ Lex a string into chunks: >>> lex('hey') ['hey'] >>> lex('hey {{you}}') ['hey ', ('you', (1, 7))] >>> lex('hey {{') Traceback (most recent call last): ... TemplateError: No }} to finish last expression at line 1 column 7 >>> lex('hey }}') Traceback (most recent call last): ... TemplateError: }} outside expression at line 1 column 7 >>> lex('hey {{ {{') Traceback (most recent call last): ... TemplateError: {{ inside expression at line 1 column 10 """ in_expr = False chunks = [] last = 0 last_pos = (1, 1) for match in token_re.finditer(s): expr = match.group(0) pos = find_position(s, match.end()) if expr == '{{' and in_expr: raise TemplateError('{{ inside expression', position=pos, name=name) elif expr == '}}' and not in_expr: raise TemplateError('}} outside expression', position=pos, name=name) if expr == '{{': part = s[last:match.start()] if part: chunks.append(part) in_expr = True else: chunks.append((s[last:match.start()], last_pos)) in_expr = False last = match.end() last_pos = pos if in_expr: raise TemplateError('No }} to finish last expression', name=name, position=last_pos) part = s[last:] if part: chunks.append(part) if trim_whitespace: chunks = trim_lex(chunks) return chunks statement_re = re.compile(r'^(?:if |elif |else |for |py:)') single_statements = ['endif', 'endfor', 'continue', 'break'] trail_whitespace_re = re.compile(r'\n[\t ]*$') lead_whitespace_re = re.compile(r'^[\t ]*\n') def trim_lex(tokens): r""" Takes a lexed set of tokens, and removes whitespace when there is a directive on a line by itself: >>> tokens = lex('{{if x}}\nx\n{{endif}}\ny', trim_whitespace=False) >>> tokens [('if x', (1, 3)), '\nx\n', ('endif', (3, 3)), '\ny'] >>> trim_lex(tokens) [('if x', (1, 3)), 'x\n', ('endif', (3, 3)), 'y'] """ for i in range(len(tokens)): current = tokens[i] if isinstance(tokens[i], basestring): # we don't trim this continue item = current[0] if not statement_re.search(item) and item not in single_statements: continue if not i: prev = '' else: prev = tokens[i-1] if i+1 >= len(tokens): next = '' else: next = tokens[i+1] if (not isinstance(next, basestring) or not isinstance(prev, basestring)): continue if ((not prev or trail_whitespace_re.search(prev)) and (not next or lead_whitespace_re.search(next))): if prev: m = trail_whitespace_re.search(prev) # +1 to leave the leading \n on: prev = prev[:m.start()+1] tokens[i-1] = prev if next: m = lead_whitespace_re.search(next) next = next[m.end():] tokens[i+1] = next return tokens def find_position(string, index): """Given a string and index, return (line, column)""" leading = string[:index].splitlines() return (len(leading), len(leading[-1])+1) def parse(s, name=None): r""" Parses a string into a kind of AST >>> parse('{{x}}') [('expr', (1, 3), 'x')] >>> parse('foo') ['foo'] >>> parse('{{if x}}test{{endif}}') [('cond', (1, 3), ('if', (1, 3), 'x', ['test']))] >>> parse('series->{{for x in y}}x={{x}}{{endfor}}') ['series->', ('for', (1, 11), ('x',), 'y', ['x=', ('expr', (1, 27), 'x')])] >>> parse('{{for x, y in z:}}{{continue}}{{endfor}}') [('for', (1, 3), ('x', 'y'), 'z', [('continue', (1, 21))])] >>> parse('{{py:x=1}}') [('py', (1, 3), 'x=1')] >>> parse('{{if x}}a{{elif y}}b{{else}}c{{endif}}') [('cond', (1, 3), ('if', (1, 3), 'x', ['a']), ('elif', (1, 12), 'y', ['b']), ('else', (1, 23), None, ['c']))] Some exceptions:: >>> parse('{{continue}}') Traceback (most recent call last): ... TemplateError: continue outside of for loop at line 1 column 3 >>> parse('{{if x}}foo') Traceback (most recent call last): ... TemplateError: No {{endif}} at line 1 column 3 >>> parse('{{else}}') Traceback (most recent call last): ... TemplateError: else outside of an if block at line 1 column 3 >>> parse('{{if x}}{{for x in y}}{{endif}}{{endfor}}') Traceback (most recent call last): ... TemplateError: Unexpected endif at line 1 column 25 >>> parse('{{if}}{{endif}}') Traceback (most recent call last): ... TemplateError: if with no expression at line 1 column 3 >>> parse('{{for x y}}{{endfor}}') Traceback (most recent call last): ... TemplateError: Bad for (no "in") in 'x y' at line 1 column 3 >>> parse('{{py:x=1\ny=2}}') Traceback (most recent call last): ... TemplateError: Multi-line py blocks must start with a newline at line 1 column 3 """ tokens = lex(s, name=name) result = [] while tokens: next, tokens = parse_expr(tokens, name) result.append(next) return result def parse_expr(tokens, name, context=()): if isinstance(tokens[0], basestring): return tokens[0], tokens[1:] expr, pos = tokens[0] expr = expr.strip() if expr.startswith('py:'): expr = expr[3:].lstrip(' \t') if expr.startswith('\n'): expr = expr[1:] else: if '\n' in expr: raise TemplateError( 'Multi-line py blocks must start with a newline', position=pos, name=name) return ('py', pos, expr), tokens[1:] elif expr in ('continue', 'break'): if 'for' not in context: raise TemplateError( 'continue outside of for loop', position=pos, name=name) return (expr, pos), tokens[1:] elif expr.startswith('if '): return parse_cond(tokens, name, context) elif (expr.startswith('elif ') or expr == 'else'): raise TemplateError( '%s outside of an if block' % expr.split()[0], position=pos, name=name) elif expr in ('if', 'elif', 'for'): raise TemplateError( '%s with no expression' % expr, position=pos, name=name) elif expr in ('endif', 'endfor'): raise TemplateError( 'Unexpected %s' % expr, position=pos, name=name) elif expr.startswith('for '): return parse_for(tokens, name, context) elif expr.startswith('default '): return parse_default(tokens, name, context) elif expr.startswith('#'): return ('comment', pos, tokens[0][0]), tokens[1:] return ('expr', pos, tokens[0][0]), tokens[1:] def parse_cond(tokens, name, context): start = tokens[0][1] pieces = [] context = context + ('if',) while 1: if not tokens: raise TemplateError( 'Missing {{endif}}', position=start, name=name) if (isinstance(tokens[0], tuple) and tokens[0][0] == 'endif'): return ('cond', start) + tuple(pieces), tokens[1:] next, tokens = parse_one_cond(tokens, name, context) pieces.append(next) def parse_one_cond(tokens, name, context): (first, pos), tokens = tokens[0], tokens[1:] content = [] if first.endswith(':'): first = first[:-1] if first.startswith('if '): part = ('if', pos, first[3:].lstrip(), content) elif first.startswith('elif '): part = ('elif', pos, first[5:].lstrip(), content) elif first == 'else': part = ('else', pos, None, content) else: assert 0, "Unexpected token %r at %s" % (first, pos) while 1: if not tokens: raise TemplateError( 'No {{endif}}', position=pos, name=name) if (isinstance(tokens[0], tuple) and (tokens[0][0] == 'endif' or tokens[0][0].startswith('elif ') or tokens[0][0] == 'else')): return part, tokens next, tokens = parse_expr(tokens, name, context) content.append(next) def parse_for(tokens, name, context): first, pos = tokens[0] tokens = tokens[1:] context = ('for',) + context content = [] assert first.startswith('for ') if first.endswith(':'): first = first[:-1] first = first[3:].strip() match = in_re.search(first) if not match: raise TemplateError( 'Bad for (no "in") in %r' % first, position=pos, name=name) vars = first[:match.start()] if '(' in vars: raise TemplateError( 'You cannot have () in the variable section of a for loop (%r)' % vars, position=pos, name=name) vars = tuple([ v.strip() for v in first[:match.start()].split(',') if v.strip()]) expr = first[match.end():] while 1: if not tokens: raise TemplateError( 'No {{endfor}}', position=pos, name=name) if (isinstance(tokens[0], tuple) and tokens[0][0] == 'endfor'): return ('for', pos, vars, expr, content), tokens[1:] next, tokens = parse_expr(tokens, name, context) content.append(next) def parse_default(tokens, name, context): first, pos = tokens[0] assert first.startswith('default ') first = first.split(None, 1)[1] parts = first.split('=', 1) if len(parts) == 1: raise TemplateError( "Expression must be {{default var=value}}; no = found in %r" % first, position=pos, name=name) var = parts[0].strip() if ',' in var: raise TemplateError( "{{default x, y = ...}} is not supported", position=pos, name=name) if not var_re.search(var): raise TemplateError( "Not a valid variable name for {{default}}: %r" % var, position=pos, name=name) expr = parts[1].strip() return ('default', pos, var, expr), tokens[1:] _fill_command_usage = """\ %prog [OPTIONS] TEMPLATE arg=value Use py:arg=value to set a Python value; otherwise all values are strings. """ def fill_command(args=None): import sys, optparse, pkg_resources, os if args is None: args = sys.argv[1:] dist = pkg_resources.get_distribution('Paste') parser = optparse.OptionParser( version=str(dist), usage=_fill_command_usage) parser.add_option( '-o', '--output', dest='output', metavar="FILENAME", help="File to write output to (default stdout)") parser.add_option( '--html', dest='use_html', action='store_true', help="Use HTML style filling (including automatic HTML quoting)") parser.add_option( '--env', dest='use_env', action='store_true', help="Put the environment in as top-level variables") options, args = parser.parse_args(args) if len(args) < 1: print 'You must give a template filename' print dir(parser) assert 0 template_name = args[0] args = args[1:] vars = {} if options.use_env: vars.update(os.environ) for value in args: if '=' not in value: print 'Bad argument: %r' % value sys.exit(2) name, value = value.split('=', 1) if name.startswith('py:'): name = name[:3] value = eval(value) vars[name] = value if template_name == '-': template_content = sys.stdin.read() template_name = '' else: f = open(template_name, 'rb') template_content = f.read() f.close() if options.use_html: TemplateClass = HTMLTemplate else: TemplateClass = Template template = TemplateClass(template_content, name=template_name) result = template.substitute(vars) if options.output: f = open(options.output, 'wb') f.write(result) f.close() else: sys.stdout.write(result) if __name__ == '__main__': from paste.util.template import fill_command fill_command() Paste-1.7.5.1/paste/util/converters.py0000664000175000017500000000153711354703364016216 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php def asbool(obj): if isinstance(obj, (str, unicode)): obj = obj.strip().lower() if obj in ['true', 'yes', 'on', 'y', 't', '1']: return True elif obj in ['false', 'no', 'off', 'n', 'f', '0']: return False else: raise ValueError( "String is not true/false: %r" % obj) return bool(obj) def aslist(obj, sep=None, strip=True): if isinstance(obj, (str, unicode)): lst = obj.split(sep) if strip: lst = [v.strip() for v in lst] return lst elif isinstance(obj, (list, tuple)): return obj elif obj is None: return [] else: return [obj] Paste-1.7.5.1/paste/util/mimeparse.py0000664000175000017500000001470011433546034015777 0ustar ianbianb"""MIME-Type Parser This module provides basic functions for handling mime-types. It can handle matching mime-types against a list of media-ranges. See section 14.1 of the HTTP specification [RFC 2616] for a complete explanation. http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 Based on mimeparse 0.1.2 by Joe Gregorio: http://code.google.com/p/mimeparse/ Contents: - parse_mime_type(): Parses a mime-type into its component parts. - parse_media_range(): Media-ranges are mime-types with wild-cards and a 'q' quality parameter. - quality(): Determines the quality ('q') of a mime-type when compared against a list of media-ranges. - quality_parsed(): Just like quality() except the second parameter must be pre-parsed. - best_match(): Choose the mime-type with the highest quality ('q') from a list of candidates. - desired_matches(): Filter against a list of desired mime-types in the order the server prefers. """ def parse_mime_type(mime_type): """Carves up a mime-type and returns a tuple of the (type, subtype, params) where 'params' is a dictionary of all the parameters for the media range. For example, the media range 'application/xhtml;q=0.5' would get parsed into: ('application', 'xhtml', {'q', '0.5'}) """ type = mime_type.split(';') type, plist = type[0], type[1:] try: type, subtype = type.split('/', 1) except ValueError: type, subtype = type.strip() or '*', '*' else: type = type.strip() or '*' subtype = subtype.strip() or '*' params = {} for param in plist: param = param.split('=', 1) if len(param) == 2: key, value = param[0].strip(), param[1].strip() if key and value: params[key] = value return type, subtype, params def parse_media_range(range): """Carves up a media range and returns a tuple of the (type, subtype, params) where 'params' is a dictionary of all the parameters for the media range. For example, the media range 'application/*;q=0.5' would get parsed into: ('application', '*', {'q', '0.5'}) In addition this function also guarantees that there is a value for 'q' in the params dictionary, filling it in with a proper default if necessary. """ type, subtype, params = parse_mime_type(range) try: if not 0 <= float(params['q']) <= 1: raise ValueError except (KeyError, ValueError): params['q'] = '1' return type, subtype, params def fitness_and_quality_parsed(mime_type, parsed_ranges): """Find the best match for a given mime-type against a list of media_ranges that have already been parsed by parse_media_range(). Returns a tuple of the fitness value and the value of the 'q' quality parameter of the best match, or (-1, 0) if no match was found. Just as for quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges.""" best_fitness, best_fit_q = -1, 0 target_type, target_subtype, target_params = parse_media_range(mime_type) for type, subtype, params in parsed_ranges: if (type == target_type or type == '*' or target_type == '*') and ( subtype == target_subtype or subtype == '*' or target_subtype == '*'): fitness = 0 if type == target_type: fitness += 100 if subtype == target_subtype: fitness += 10 for key in target_params: if key != 'q' and key in params: if params[key] == target_params[key]: fitness += 1 if fitness > best_fitness: best_fitness = fitness best_fit_q = params['q'] return best_fitness, float(best_fit_q) def quality_parsed(mime_type, parsed_ranges): """Find the best match for a given mime-type against a list of media_ranges that have already been parsed by parse_media_range(). Returns the 'q' quality parameter of the best match, 0 if no match was found. This function behaves the same as quality() except that 'parsed_ranges' must be a list of parsed media ranges.""" return fitness_and_quality_parsed(mime_type, parsed_ranges)[1] def quality(mime_type, ranges): """Returns the quality 'q' of a mime-type when compared against the media-ranges in ranges. For example: >>> quality('text/html','text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5') 0.7 """ parsed_ranges = map(parse_media_range, ranges.split(',')) return quality_parsed(mime_type, parsed_ranges) def best_match(supported, header): """Takes a list of supported mime-types and finds the best match for all the media-ranges listed in header. In case of ambiguity, whatever comes first in the list will be chosen. The value of header must be a string that conforms to the format of the HTTP Accept: header. The value of 'supported' is a list of mime-types. >>> best_match(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1') 'text/xml' """ if not supported: return '' parsed_header = map(parse_media_range, header.split(',')) best_type = max([ (fitness_and_quality_parsed(mime_type, parsed_header), -n) for n, mime_type in enumerate(supported)]) return best_type[0][1] and supported[-best_type[1]] or '' def desired_matches(desired, header): """Takes a list of desired mime-types in the order the server prefers to send them regardless of the browsers preference. Browsers (such as Firefox) technically want XML over HTML depending on how one reads the specification. This function is provided for a server to declare a set of desired mime-types it supports, and returns a subset of the desired list in the same order should each one be Accepted by the browser. >>> desired_matches(['text/html', 'application/xml'], \ ... 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png') ['text/html', 'application/xml'] >>> desired_matches(['text/html', 'application/xml'], 'application/xml,application/json') ['application/xml'] """ parsed_ranges = map(parse_media_range, header.split(',')) return [mimetype for mimetype in desired if quality_parsed(mimetype, parsed_ranges)] Paste-1.7.5.1/paste/util/ip4.py0000664000175000017500000002206711354703364014521 0ustar ianbianb# -*- coding: iso-8859-15 -*- """IP4 address range set implementation. Implements an IPv4-range type. Copyright (C) 2006, Heiko Wundram. Released under the MIT-license. """ # Version information # ------------------- __author__ = "Heiko Wundram " __version__ = "0.2" __revision__ = "3" __date__ = "2006-01-20" # Imports # ------- import intset import socket # IP4Range class # -------------- class IP4Range(intset.IntSet): """IP4 address range class with efficient storage of address ranges. Supports all set operations.""" _MINIP4 = 0 _MAXIP4 = (1<<32) - 1 _UNITYTRANS = "".join([chr(n) for n in range(256)]) _IPREMOVE = "0123456789." def __init__(self,*args): """Initialize an ip4range class. The constructor accepts an unlimited number of arguments that may either be tuples in the form (start,stop), integers, longs or strings, where start and stop in a tuple may also be of the form integer, long or string. Passing an integer or long means passing an IPv4-address that's already been converted to integer notation, whereas passing a string specifies an address where this conversion still has to be done. A string address may be in the following formats: - 1.2.3.4 - a plain address, interpreted as a single address - 1.2.3 - a set of addresses, interpreted as 1.2.3.0-1.2.3.255 - localhost - hostname to look up, interpreted as single address - 1.2.3<->5 - a set of addresses, interpreted as 1.2.3.0-1.2.5.255 - 1.2.0.0/16 - a set of addresses, interpreted as 1.2.0.0-1.2.255.255 Only the first three notations are valid if you use a string address in a tuple, whereby notation 2 is interpreted as 1.2.3.0 if specified as lower bound and 1.2.3.255 if specified as upper bound, not as a range of addresses. Specifying a range is done with the <-> operator. This is necessary because '-' might be present in a hostname. '<->' shouldn't be, ever. """ # Special case copy constructor. if len(args) == 1 and isinstance(args[0],IP4Range): super(IP4Range,self).__init__(args[0]) return # Convert arguments to tuple syntax. args = list(args) for i in range(len(args)): argval = args[i] if isinstance(argval,str): if "<->" in argval: # Type 4 address. args[i] = self._parseRange(*argval.split("<->",1)) continue elif "/" in argval: # Type 5 address. args[i] = self._parseMask(*argval.split("/",1)) else: # Type 1, 2 or 3. args[i] = self._parseAddrRange(argval) elif isinstance(argval,tuple): if len(tuple) <> 2: raise ValueError("Tuple is of invalid length.") addr1, addr2 = argval if isinstance(addr1,str): addr1 = self._parseAddrRange(addr1)[0] elif not isinstance(addr1,(int,long)): raise TypeError("Invalid argument.") if isinstance(addr2,str): addr2 = self._parseAddrRange(addr2)[1] elif not isinstance(addr2,(int,long)): raise TypeError("Invalid argument.") args[i] = (addr1,addr2) elif not isinstance(argval,(int,long)): raise TypeError("Invalid argument.") # Initialize the integer set. super(IP4Range,self).__init__(min=self._MINIP4,max=self._MAXIP4,*args) # Parsing functions # ----------------- def _parseRange(self,addr1,addr2): naddr1, naddr1len = _parseAddr(addr1) naddr2, naddr2len = _parseAddr(addr2) if naddr2len < naddr1len: naddr2 += naddr1&(((1<<((naddr1len-naddr2len)*8))-1)<< (naddr2len*8)) naddr2len = naddr1len elif naddr2len > naddr1len: raise ValueError("Range has more dots than address.") naddr1 <<= (4-naddr1len)*8 naddr2 <<= (4-naddr2len)*8 naddr2 += (1<<((4-naddr2len)*8))-1 return (naddr1,naddr2) def _parseMask(self,addr,mask): naddr, naddrlen = _parseAddr(addr) naddr <<= (4-naddrlen)*8 try: if not mask: masklen = 0 else: masklen = int(mask) if not 0 <= masklen <= 32: raise ValueError except ValueError: try: mask = _parseAddr(mask,False) except ValueError: raise ValueError("Mask isn't parseable.") remaining = 0 masklen = 0 if not mask: masklen = 0 else: while not (mask&1): remaining += 1 while (mask&1): mask >>= 1 masklen += 1 if remaining+masklen <> 32: raise ValueError("Mask isn't a proper host mask.") naddr1 = naddr & (((1<>= 8 return ".".join(reversed(rv)) # Iterating # --------- def iteraddresses(self): """Returns an iterator which iterates over ips in this iprange. An IP is returned in string form (e.g. '1.2.3.4').""" for v in super(IP4Range,self).__iter__(): yield self._int2ip(v) def iterranges(self): """Returns an iterator which iterates over ip-ip ranges which build this iprange if combined. An ip-ip pair is returned in string form (e.g. '1.2.3.4-2.3.4.5').""" for r in self._ranges: if r[1]-r[0] == 1: yield self._int2ip(r[0]) else: yield '%s-%s' % (self._int2ip(r[0]),self._int2ip(r[1]-1)) def itermasks(self): """Returns an iterator which iterates over ip/mask pairs which build this iprange if combined. An IP/Mask pair is returned in string form (e.g. '1.2.3.0/24').""" for r in self._ranges: for v in self._itermasks(r): yield v def _itermasks(self,r): ranges = [r] while ranges: cur = ranges.pop() curmask = 0 while True: curmasklen = 1<<(32-curmask) start = (cur[0]+curmasklen-1)&(((1<= cur[0] and start+curmasklen <= cur[1]: break else: curmask += 1 yield "%s/%s" % (self._int2ip(start),curmask) if cur[0] < start: ranges.append((cur[0],start)) if cur[1] > start+curmasklen: ranges.append((start+curmasklen,cur[1])) __iter__ = iteraddresses # Printing # -------- def __repr__(self): """Returns a string which can be used to reconstruct this iprange.""" rv = [] for start, stop in self._ranges: if stop-start == 1: rv.append("%r" % (self._int2ip(start),)) else: rv.append("(%r,%r)" % (self._int2ip(start), self._int2ip(stop-1))) return "%s(%s)" % (self.__class__.__name__,",".join(rv)) def _parseAddr(addr,lookup=True): if lookup and addr.translate(IP4Range._UNITYTRANS, IP4Range._IPREMOVE): try: addr = socket.gethostbyname(addr) except socket.error: raise ValueError("Invalid Hostname as argument.") naddr = 0 for naddrpos, part in enumerate(addr.split(".")): if naddrpos >= 4: raise ValueError("Address contains more than four parts.") try: if not part: part = 0 else: part = int(part) if not 0 <= part < 256: raise ValueError except ValueError: raise ValueError("Address part out of range.") naddr <<= 8 naddr += part return naddr, naddrpos+1 def ip2int(addr, lookup=True): return _parseAddr(addr, lookup=lookup)[0] if __name__ == "__main__": # Little test script. x = IP4Range("172.22.162.250/24") y = IP4Range("172.22.162.250","172.22.163.250","172.22.163.253<->255") print x for val in x.itermasks(): print val for val in y.itermasks(): print val for val in (x|y).itermasks(): print val for val in (x^y).iterranges(): print val for val in x: print val Paste-1.7.5.1/paste/util/subprocess24.py0000664000175000017500000011630311354703364016360 0ustar ianbianb# subprocess - Subprocesses with accessible I/O streams # # For more information about this module, see PEP 324. # # This module should remain compatible with Python 2.2, see PEP 291. # # Copyright (c) 2003-2005 by Peter Astrand # # Licensed to PSF under a Contributor Agreement. # See http://www.python.org/2.4/license for licensing details. r"""subprocess - Subprocesses with accessible I/O streams This module allows you to spawn processes, connect to their input/output/error pipes, and obtain their return codes. This module intends to replace several other, older modules and functions, like: os.system os.spawn* os.popen* popen2.* commands.* Information about how the subprocess module can be used to replace these modules and functions can be found below. Using the subprocess module =========================== This module defines one class called Popen: class Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0): Arguments are: args should be a string, or a sequence of program arguments. The program to execute is normally the first item in the args sequence or string, but can be explicitly set by using the executable argument. On UNIX, with shell=False (default): In this case, the Popen class uses os.execvp() to execute the child program. args should normally be a sequence. A string will be treated as a sequence with the string as the only item (the program to execute). On UNIX, with shell=True: If args is a string, it specifies the command string to execute through the shell. If args is a sequence, the first item specifies the command string, and any additional items will be treated as additional shell arguments. On Windows: the Popen class uses CreateProcess() to execute the child program, which operates on strings. If args is a sequence, it will be converted to a string using the list2cmdline method. Please note that not all MS Windows applications interpret the command line the same way: The list2cmdline is designed for applications using the same rules as the MS C runtime. bufsize, if given, has the same meaning as the corresponding argument to the built-in open() function: 0 means unbuffered, 1 means line buffered, any other positive value means use a buffer of (approximately) that size. A negative bufsize means to use the system default, which usually means fully buffered. The default value for bufsize is 0 (unbuffered). stdin, stdout and stderr specify the executed programs' standard input, standard output and standard error file handles, respectively. Valid values are PIPE, an existing file descriptor (a positive integer), an existing file object, and None. PIPE indicates that a new pipe to the child should be created. With None, no redirection will occur; the child's file handles will be inherited from the parent. Additionally, stderr can be STDOUT, which indicates that the stderr data from the applications should be captured into the same file handle as for stdout. If preexec_fn is set to a callable object, this object will be called in the child process just before the child is executed. If close_fds is true, all file descriptors except 0, 1 and 2 will be closed before the child process is executed. if shell is true, the specified command will be executed through the shell. If cwd is not None, the current directory will be changed to cwd before the child is executed. If env is not None, it defines the environment variables for the new process. If universal_newlines is true, the file objects stdout and stderr are opened as a text files, but lines may be terminated by any of '\n', the Unix end-of-line convention, '\r', the Macintosh convention or '\r\n', the Windows convention. All of these external representations are seen as '\n' by the Python program. Note: This feature is only available if Python is built with universal newline support (the default). Also, the newlines attribute of the file objects stdout, stdin and stderr are not updated by the communicate() method. The startupinfo and creationflags, if given, will be passed to the underlying CreateProcess() function. They can specify things such as appearance of the main window and priority for the new process. (Windows only) This module also defines two shortcut functions: call(*args, **kwargs): Run command with arguments. Wait for command to complete, then return the returncode attribute. The arguments are the same as for the Popen constructor. Example: retcode = call(["ls", "-l"]) Exceptions ---------- Exceptions raised in the child process, before the new program has started to execute, will be re-raised in the parent. Additionally, the exception object will have one extra attribute called 'child_traceback', which is a string containing traceback information from the childs point of view. The most common exception raised is OSError. This occurs, for example, when trying to execute a non-existent file. Applications should prepare for OSErrors. A ValueError will be raised if Popen is called with invalid arguments. Security -------- Unlike some other popen functions, this implementation will never call /bin/sh implicitly. This means that all characters, including shell metacharacters, can safely be passed to child processes. Popen objects ============= Instances of the Popen class have the following methods: poll() Check if child process has terminated. Returns returncode attribute. wait() Wait for child process to terminate. Returns returncode attribute. communicate(input=None) Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optional stdin argument should be a string to be sent to the child process, or None, if no data should be sent to the child. communicate() returns a tuple (stdout, stderr). Note: The data read is buffered in memory, so do not use this method if the data size is large or unlimited. The following attributes are also available: stdin If the stdin argument is PIPE, this attribute is a file object that provides input to the child process. Otherwise, it is None. stdout If the stdout argument is PIPE, this attribute is a file object that provides output from the child process. Otherwise, it is None. stderr If the stderr argument is PIPE, this attribute is file object that provides error output from the child process. Otherwise, it is None. pid The process ID of the child process. returncode The child return code. A None value indicates that the process hasn't terminated yet. A negative value -N indicates that the child was terminated by signal N (UNIX only). Replacing older functions with the subprocess module ==================================================== In this section, "a ==> b" means that b can be used as a replacement for a. Note: All functions in this section fail (more or less) silently if the executed program cannot be found; this module raises an OSError exception. In the following examples, we assume that the subprocess module is imported with "from subprocess import *". Replacing /bin/sh shell backquote --------------------------------- output=`mycmd myarg` ==> output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0] Replacing shell pipe line ------------------------- output=`dmesg | grep hda` ==> p1 = Popen(["dmesg"], stdout=PIPE) p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) output = p2.communicate()[0] Replacing os.system() --------------------- sts = os.system("mycmd" + " myarg") ==> p = Popen("mycmd" + " myarg", shell=True) sts = os.waitpid(p.pid, 0) Note: * Calling the program through the shell is usually not required. * It's easier to look at the returncode attribute than the exitstatus. A more real-world example would look like this: try: retcode = call("mycmd" + " myarg", shell=True) if retcode < 0: print >>sys.stderr, "Child was terminated by signal", -retcode else: print >>sys.stderr, "Child returned", retcode except OSError, e: print >>sys.stderr, "Execution failed:", e Replacing os.spawn* ------------------- P_NOWAIT example: pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg") ==> pid = Popen(["/bin/mycmd", "myarg"]).pid P_WAIT example: retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg") ==> retcode = call(["/bin/mycmd", "myarg"]) Vector example: os.spawnvp(os.P_NOWAIT, path, args) ==> Popen([path] + args[1:]) Environment example: os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env) ==> Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"}) Replacing os.popen* ------------------- pipe = os.popen(cmd, mode='r', bufsize) ==> pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout pipe = os.popen(cmd, mode='w', bufsize) ==> pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin (child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize) ==> p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdin, child_stdout) = (p.stdin, p.stdout) (child_stdin, child_stdout, child_stderr) = os.popen3(cmd, mode, bufsize) ==> p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) (child_stdin, child_stdout, child_stderr) = (p.stdin, p.stdout, p.stderr) (child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize) ==> p = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) (child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout) Replacing popen2.* ------------------ Note: If the cmd argument to popen2 functions is a string, the command is executed through /bin/sh. If it is a list, the command is directly executed. (child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode) ==> p = Popen(["somestring"], shell=True, bufsize=bufsize stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdout, child_stdin) = (p.stdout, p.stdin) (child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode) ==> p = Popen(["mycmd", "myarg"], bufsize=bufsize, stdin=PIPE, stdout=PIPE, close_fds=True) (child_stdout, child_stdin) = (p.stdout, p.stdin) The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen, except that: * subprocess.Popen raises an exception if the execution fails * the capturestderr argument is replaced with the stderr argument. * stdin=PIPE and stdout=PIPE must be specified. * popen2 closes all filedescriptors by default, but you have to specify close_fds=True with subprocess.Popen. """ import sys mswindows = (sys.platform == "win32") import os import types import traceback if mswindows: import threading import msvcrt ## @@: Changed in Paste ## Since this module is only used on pre-python-2.4 systems, they probably ## don't have _subprocess installed, but hopefully have the win32 stuff ## installed. if 1: # <-- change this to use pywin32 instead of the _subprocess driver import pywintypes from win32api import GetStdHandle, STD_INPUT_HANDLE, \ STD_OUTPUT_HANDLE, STD_ERROR_HANDLE from win32api import GetCurrentProcess, DuplicateHandle, \ GetModuleFileName, GetVersion from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE from win32pipe import CreatePipe from win32process import CreateProcess, STARTUPINFO, \ GetExitCodeProcess, STARTF_USESTDHANDLES, \ STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0 else: from _subprocess import * class STARTUPINFO: dwFlags = 0 hStdInput = None hStdOutput = None hStdError = None class pywintypes: error = IOError else: import select import errno import fcntl import pickle __all__ = ["Popen", "PIPE", "STDOUT", "call"] try: MAXFD = os.sysconf("SC_OPEN_MAX") except: MAXFD = 256 # True/False does not exist on 2.2.0 try: False except NameError: False = 0 True = 1 _active = [] def _cleanup(): for inst in _active[:]: inst.poll() PIPE = -1 STDOUT = -2 def call(*args, **kwargs): """Run command with arguments. Wait for command to complete, then return the returncode attribute. The arguments are the same as for the Popen constructor. Example: retcode = call(["ls", "-l"]) """ return Popen(*args, **kwargs).wait() def list2cmdline(seq): """ Translate a sequence of arguments into a command line string, using the same rules as the MS C runtime: 1) Arguments are delimited by white space, which is either a space or a tab. 2) A string surrounded by double quotation marks is interpreted as a single argument, regardless of white space contained within. A quoted string can be embedded in an argument. 3) A double quotation mark preceded by a backslash is interpreted as a literal double quotation mark. 4) Backslashes are interpreted literally, unless they immediately precede a double quotation mark. 5) If backslashes immediately precede a double quotation mark, every pair of backslashes is interpreted as a literal backslash. If the number of backslashes is odd, the last backslash escapes the next double quotation mark as described in rule 3. """ # See # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp result = [] needquote = False for arg in seq: bs_buf = [] # Add a space to separate this argument from the others if result: result.append(' ') needquote = (" " in arg) or ("\t" in arg) if needquote: result.append('"') for c in arg: if c == '\\': # Don't know if we need to double yet. bs_buf.append(c) elif c == '"': # Double backspaces. result.append('\\' * len(bs_buf)*2) bs_buf = [] result.append('\\"') else: # Normal char if bs_buf: result.extend(bs_buf) bs_buf = [] result.append(c) # Add remaining backspaces, if any. if bs_buf: result.extend(bs_buf) if needquote: result.extend(bs_buf) result.append('"') return ''.join(result) class Popen(object): def __init__(self, args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0): """Create new Popen instance.""" _cleanup() if not isinstance(bufsize, (int, long)): raise TypeError("bufsize must be an integer") if mswindows: if preexec_fn is not None: raise ValueError("preexec_fn is not supported on Windows " "platforms") if close_fds: raise ValueError("close_fds is not supported on Windows " "platforms") else: # POSIX if startupinfo is not None: raise ValueError("startupinfo is only supported on Windows " "platforms") if creationflags != 0: raise ValueError("creationflags is only supported on Windows " "platforms") self.stdin = None self.stdout = None self.stderr = None self.pid = None self.returncode = None self.universal_newlines = universal_newlines # Input and output objects. The general principle is like # this: # # Parent Child # ------ ----- # p2cwrite ---stdin---> p2cread # c2pread <--stdout--- c2pwrite # errread <--stderr--- errwrite # # On POSIX, the child objects are file descriptors. On # Windows, these are Windows file handles. The parent objects # are file descriptors on both platforms. The parent objects # are None when not using PIPEs. The child objects are None # when not redirecting. (p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) = self._get_handles(stdin, stdout, stderr) self._execute_child(args, executable, preexec_fn, close_fds, cwd, env, universal_newlines, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) if p2cwrite: self.stdin = os.fdopen(p2cwrite, 'wb', bufsize) if c2pread: if universal_newlines: self.stdout = os.fdopen(c2pread, 'rU', bufsize) else: self.stdout = os.fdopen(c2pread, 'rb', bufsize) if errread: if universal_newlines: self.stderr = os.fdopen(errread, 'rU', bufsize) else: self.stderr = os.fdopen(errread, 'rb', bufsize) _active.append(self) def _translate_newlines(self, data): data = data.replace("\r\n", "\n") data = data.replace("\r", "\n") return data if mswindows: # # Windows methods # def _get_handles(self, stdin, stdout, stderr): """Construct and return tupel with IO objects: p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite """ if stdin == None and stdout == None and stderr == None: return (None, None, None, None, None, None) p2cread, p2cwrite = None, None c2pread, c2pwrite = None, None errread, errwrite = None, None if stdin == None: p2cread = GetStdHandle(STD_INPUT_HANDLE) elif stdin == PIPE: p2cread, p2cwrite = CreatePipe(None, 0) # Detach and turn into fd p2cwrite = p2cwrite.Detach() p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0) elif type(stdin) == types.IntType: p2cread = msvcrt.get_osfhandle(stdin) else: # Assuming file-like object p2cread = msvcrt.get_osfhandle(stdin.fileno()) p2cread = self._make_inheritable(p2cread) if stdout == None: c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE) elif stdout == PIPE: c2pread, c2pwrite = CreatePipe(None, 0) # Detach and turn into fd c2pread = c2pread.Detach() c2pread = msvcrt.open_osfhandle(c2pread, 0) elif type(stdout) == types.IntType: c2pwrite = msvcrt.get_osfhandle(stdout) else: # Assuming file-like object c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) c2pwrite = self._make_inheritable(c2pwrite) if stderr == None: errwrite = GetStdHandle(STD_ERROR_HANDLE) elif stderr == PIPE: errread, errwrite = CreatePipe(None, 0) # Detach and turn into fd errread = errread.Detach() errread = msvcrt.open_osfhandle(errread, 0) elif stderr == STDOUT: errwrite = c2pwrite elif type(stderr) == types.IntType: errwrite = msvcrt.get_osfhandle(stderr) else: # Assuming file-like object errwrite = msvcrt.get_osfhandle(stderr.fileno()) errwrite = self._make_inheritable(errwrite) return (p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) def _make_inheritable(self, handle): """Return a duplicate of handle, which is inheritable""" return DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), 0, 1, DUPLICATE_SAME_ACCESS) def _find_w9xpopen(self): """Find and return absolut path to w9xpopen.exe""" w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)), "w9xpopen.exe") if not os.path.exists(w9xpopen): # Eeek - file-not-found - possibly an embedding # situation - see if we can locate it in sys.exec_prefix w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), "w9xpopen.exe") if not os.path.exists(w9xpopen): raise RuntimeError("Cannot locate w9xpopen.exe, which is " "needed for Popen to work with your " "shell or platform.") return w9xpopen def _execute_child(self, args, executable, preexec_fn, close_fds, cwd, env, universal_newlines, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite): """Execute program (MS Windows version)""" if not isinstance(args, types.StringTypes): args = list2cmdline(args) # Process startup details default_startupinfo = STARTUPINFO() if startupinfo == None: startupinfo = default_startupinfo if not None in (p2cread, c2pwrite, errwrite): startupinfo.dwFlags |= STARTF_USESTDHANDLES startupinfo.hStdInput = p2cread startupinfo.hStdOutput = c2pwrite startupinfo.hStdError = errwrite if shell: default_startupinfo.dwFlags |= STARTF_USESHOWWINDOW default_startupinfo.wShowWindow = SW_HIDE comspec = os.environ.get("COMSPEC", "cmd.exe") args = comspec + " /c " + args if (GetVersion() >= 0x80000000L or os.path.basename(comspec).lower() == "command.com"): # Win9x, or using command.com on NT. We need to # use the w9xpopen intermediate program. For more # information, see KB Q150956 # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp) w9xpopen = self._find_w9xpopen() args = '"%s" %s' % (w9xpopen, args) # Not passing CREATE_NEW_CONSOLE has been known to # cause random failures on win9x. Specifically a # dialog: "Your program accessed mem currently in # use at xxx" and a hopeful warning about the # stability of your system. Cost is Ctrl+C wont # kill children. creationflags |= CREATE_NEW_CONSOLE # Start the process try: hp, ht, pid, tid = CreateProcess(executable, args, # no special security None, None, # must inherit handles to pass std # handles 1, creationflags, env, cwd, startupinfo) except pywintypes.error, e: # Translate pywintypes.error to WindowsError, which is # a subclass of OSError. FIXME: We should really # translate errno using _sys_errlist (or simliar), but # how can this be done from Python? raise WindowsError(*e.args) # Retain the process handle, but close the thread handle self._handle = hp self.pid = pid ht.Close() # Child is launched. Close the parent's copy of those pipe # handles that only the child should have open. You need # to make sure that no handles to the write end of the # output pipe are maintained in this process or else the # pipe will not close when the child process exits and the # ReadFile will hang. if p2cread != None: p2cread.Close() if c2pwrite != None: c2pwrite.Close() if errwrite != None: errwrite.Close() def poll(self): """Check if child process has terminated. Returns returncode attribute.""" if self.returncode == None: if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: self.returncode = GetExitCodeProcess(self._handle) _active.remove(self) return self.returncode def wait(self): """Wait for child process to terminate. Returns returncode attribute.""" if self.returncode == None: obj = WaitForSingleObject(self._handle, INFINITE) self.returncode = GetExitCodeProcess(self._handle) _active.remove(self) return self.returncode def _readerthread(self, fh, buffer): buffer.append(fh.read()) def communicate(self, input=None): """Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optional input argument should be a string to be sent to the child process, or None, if no data should be sent to the child. communicate() returns a tuple (stdout, stderr).""" stdout = None # Return stderr = None # Return if self.stdout: stdout = [] stdout_thread = threading.Thread(target=self._readerthread, args=(self.stdout, stdout)) stdout_thread.setDaemon(True) stdout_thread.start() if self.stderr: stderr = [] stderr_thread = threading.Thread(target=self._readerthread, args=(self.stderr, stderr)) stderr_thread.setDaemon(True) stderr_thread.start() if self.stdin: if input != None: self.stdin.write(input) self.stdin.close() if self.stdout: stdout_thread.join() if self.stderr: stderr_thread.join() # All data exchanged. Translate lists into strings. if stdout != None: stdout = stdout[0] if stderr != None: stderr = stderr[0] # Translate newlines, if requested. We cannot let the file # object do the translation: It is based on stdio, which is # impossible to combine with select (unless forcing no # buffering). if self.universal_newlines and hasattr(open, 'newlines'): if stdout: stdout = self._translate_newlines(stdout) if stderr: stderr = self._translate_newlines(stderr) self.wait() return (stdout, stderr) else: # # POSIX methods # def _get_handles(self, stdin, stdout, stderr): """Construct and return tupel with IO objects: p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite """ p2cread, p2cwrite = None, None c2pread, c2pwrite = None, None errread, errwrite = None, None if stdin == None: pass elif stdin == PIPE: p2cread, p2cwrite = os.pipe() elif type(stdin) == types.IntType: p2cread = stdin else: # Assuming file-like object p2cread = stdin.fileno() if stdout == None: pass elif stdout == PIPE: c2pread, c2pwrite = os.pipe() elif type(stdout) == types.IntType: c2pwrite = stdout else: # Assuming file-like object c2pwrite = stdout.fileno() if stderr == None: pass elif stderr == PIPE: errread, errwrite = os.pipe() elif stderr == STDOUT: errwrite = c2pwrite elif type(stderr) == types.IntType: errwrite = stderr else: # Assuming file-like object errwrite = stderr.fileno() return (p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) def _set_cloexec_flag(self, fd): try: cloexec_flag = fcntl.FD_CLOEXEC except AttributeError: cloexec_flag = 1 old = fcntl.fcntl(fd, fcntl.F_GETFD) fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) def _close_fds(self, but): for i in range(3, MAXFD): if i == but: continue try: os.close(i) except: pass def _execute_child(self, args, executable, preexec_fn, close_fds, cwd, env, universal_newlines, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite): """Execute program (POSIX version)""" if isinstance(args, types.StringTypes): args = [args] if shell: args = ["/bin/sh", "-c"] + args if executable == None: executable = args[0] # For transferring possible exec failure from child to parent # The first char specifies the exception type: 0 means # OSError, 1 means some other error. errpipe_read, errpipe_write = os.pipe() self._set_cloexec_flag(errpipe_write) self.pid = os.fork() if self.pid == 0: # Child try: # Close parent's pipe ends if p2cwrite: os.close(p2cwrite) if c2pread: os.close(c2pread) if errread: os.close(errread) os.close(errpipe_read) # Dup fds for child if p2cread: os.dup2(p2cread, 0) if c2pwrite: os.dup2(c2pwrite, 1) if errwrite: os.dup2(errwrite, 2) # Close pipe fds. Make sure we doesn't close the same # fd more than once. if p2cread: os.close(p2cread) if c2pwrite and c2pwrite not in (p2cread,): os.close(c2pwrite) if errwrite and errwrite not in (p2cread, c2pwrite): os.close(errwrite) # Close all other fds, if asked for if close_fds: self._close_fds(but=errpipe_write) if cwd != None: os.chdir(cwd) if preexec_fn: apply(preexec_fn) if env == None: os.execvp(executable, args) else: os.execvpe(executable, args, env) except: exc_type, exc_value, tb = sys.exc_info() # Save the traceback and attach it to the exception object exc_lines = traceback.format_exception(exc_type, exc_value, tb) exc_value.child_traceback = ''.join(exc_lines) os.write(errpipe_write, pickle.dumps(exc_value)) # This exitcode won't be reported to applications, so it # really doesn't matter what we return. os._exit(255) # Parent os.close(errpipe_write) if p2cread and p2cwrite: os.close(p2cread) if c2pwrite and c2pread: os.close(c2pwrite) if errwrite and errread: os.close(errwrite) # Wait for exec to fail or succeed; possibly raising exception data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB os.close(errpipe_read) if data != "": os.waitpid(self.pid, 0) child_exception = pickle.loads(data) raise child_exception def _handle_exitstatus(self, sts): if os.WIFSIGNALED(sts): self.returncode = -os.WTERMSIG(sts) elif os.WIFEXITED(sts): self.returncode = os.WEXITSTATUS(sts) else: # Should never happen raise RuntimeError("Unknown child exit status!") _active.remove(self) def poll(self): """Check if child process has terminated. Returns returncode attribute.""" if self.returncode == None: try: pid, sts = os.waitpid(self.pid, os.WNOHANG) if pid == self.pid: self._handle_exitstatus(sts) except os.error: pass return self.returncode def wait(self): """Wait for child process to terminate. Returns returncode attribute.""" if self.returncode == None: pid, sts = os.waitpid(self.pid, 0) self._handle_exitstatus(sts) return self.returncode def communicate(self, input=None): """Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optional input argument should be a string to be sent to the child process, or None, if no data should be sent to the child. communicate() returns a tuple (stdout, stderr).""" read_set = [] write_set = [] stdout = None # Return stderr = None # Return if self.stdin: # Flush stdio buffer. This might block, if the user has # been writing to .stdin in an uncontrolled fashion. self.stdin.flush() if input: write_set.append(self.stdin) else: self.stdin.close() if self.stdout: read_set.append(self.stdout) stdout = [] if self.stderr: read_set.append(self.stderr) stderr = [] while read_set or write_set: rlist, wlist, xlist = select.select(read_set, write_set, []) if self.stdin in wlist: # When select has indicated that the file is writable, # we can write up to PIPE_BUF bytes without risk # blocking. POSIX defines PIPE_BUF >= 512 bytes_written = os.write(self.stdin.fileno(), input[:512]) input = input[bytes_written:] if not input: self.stdin.close() write_set.remove(self.stdin) if self.stdout in rlist: data = os.read(self.stdout.fileno(), 1024) if data == "": self.stdout.close() read_set.remove(self.stdout) stdout.append(data) if self.stderr in rlist: data = os.read(self.stderr.fileno(), 1024) if data == "": self.stderr.close() read_set.remove(self.stderr) stderr.append(data) # All data exchanged. Translate lists into strings. if stdout != None: stdout = ''.join(stdout) if stderr != None: stderr = ''.join(stderr) # Translate newlines, if requested. We cannot let the file # object do the translation: It is based on stdio, which is # impossible to combine with select (unless forcing no # buffering). if self.universal_newlines and hasattr(open, 'newlines'): if stdout: stdout = self._translate_newlines(stdout) if stderr: stderr = self._translate_newlines(stderr) self.wait() return (stdout, stderr) def _demo_posix(): # # Example 1: Simple redirection: Get process list # plist = Popen(["ps"], stdout=PIPE).communicate()[0] print "Process list:" print plist # # Example 2: Change uid before executing child # if os.getuid() == 0: p = Popen(["id"], preexec_fn=lambda: os.setuid(100)) p.wait() # # Example 3: Connecting several subprocesses # print "Looking for 'hda'..." p1 = Popen(["dmesg"], stdout=PIPE) p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) print repr(p2.communicate()[0]) # # Example 4: Catch execution error # print print "Trying a weird file..." try: print Popen(["/this/path/does/not/exist"]).communicate() except OSError, e: if e.errno == errno.ENOENT: print "The file didn't exist. I thought so..." print "Child traceback:" print e.child_traceback else: print "Error", e.errno else: print >>sys.stderr, "Gosh. No error." def _demo_windows(): # # Example 1: Connecting several subprocesses # print "Looking for 'PROMPT' in set output..." p1 = Popen("set", stdout=PIPE, shell=True) p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE) print repr(p2.communicate()[0]) # # Example 2: Simple execution of program # print "Executing calc..." p = Popen("calc") p.wait() if __name__ == "__main__": if mswindows: _demo_windows() else: _demo_posix() Paste-1.7.5.1/paste/util/intset.py0000664000175000017500000004527311354703364015337 0ustar ianbianb# -*- coding: iso-8859-15 -*- """Immutable integer set type. Integer set class. Copyright (C) 2006, Heiko Wundram. Released under the MIT license. """ # Version information # ------------------- __author__ = "Heiko Wundram " __version__ = "0.2" __revision__ = "6" __date__ = "2006-01-20" # Utility classes # --------------- class _Infinity(object): """Internal type used to represent infinity values.""" __slots__ = ["_neg"] def __init__(self,neg): self._neg = neg def __lt__(self,value): if not isinstance(value,(int,long,_Infinity)): return NotImplemented return ( self._neg and not ( isinstance(value,_Infinity) and value._neg ) ) def __le__(self,value): if not isinstance(value,(int,long,_Infinity)): return NotImplemented return self._neg def __gt__(self,value): if not isinstance(value,(int,long,_Infinity)): return NotImplemented return not ( self._neg or ( isinstance(value,_Infinity) and not value._neg ) ) def __ge__(self,value): if not isinstance(value,(int,long,_Infinity)): return NotImplemented return not self._neg def __eq__(self,value): if not isinstance(value,(int,long,_Infinity)): return NotImplemented return isinstance(value,_Infinity) and self._neg == value._neg def __ne__(self,value): if not isinstance(value,(int,long,_Infinity)): return NotImplemented return not isinstance(value,_Infinity) or self._neg <> value._neg def __repr__(self): return "None" # Constants # --------- _MININF = _Infinity(True) _MAXINF = _Infinity(False) # Integer set class # ----------------- class IntSet(object): """Integer set class with efficient storage in a RLE format of ranges. Supports minus and plus infinity in the range.""" __slots__ = ["_ranges","_min","_max","_hash"] def __init__(self,*args,**kwargs): """Initialize an integer set. The constructor accepts an unlimited number of arguments that may either be tuples in the form of (start,stop) where either start or stop may be a number or None to represent maximum/minimum in that direction. The range specified by (start,stop) is always inclusive (differing from the builtin range operator). Keyword arguments that can be passed to an integer set are min and max, which specify the minimum and maximum number in the set, respectively. You can also pass None here to represent minus or plus infinity, which is also the default. """ # Special case copy constructor. if len(args) == 1 and isinstance(args[0],IntSet): if kwargs: raise ValueError("No keyword arguments for copy constructor.") self._min = args[0]._min self._max = args[0]._max self._ranges = args[0]._ranges self._hash = args[0]._hash return # Initialize set. self._ranges = [] # Process keyword arguments. self._min = kwargs.pop("min",_MININF) self._max = kwargs.pop("max",_MAXINF) if self._min is None: self._min = _MININF if self._max is None: self._max = _MAXINF # Check keyword arguments. if kwargs: raise ValueError("Invalid keyword argument.") if not ( isinstance(self._min,(int,long)) or self._min is _MININF ): raise TypeError("Invalid type of min argument.") if not ( isinstance(self._max,(int,long)) or self._max is _MAXINF ): raise TypeError("Invalid type of max argument.") if ( self._min is not _MININF and self._max is not _MAXINF and self._min > self._max ): raise ValueError("Minimum is not smaller than maximum.") if isinstance(self._max,(int,long)): self._max += 1 # Process arguments. for arg in args: if isinstance(arg,(int,long)): start, stop = arg, arg+1 elif isinstance(arg,tuple): if len(arg) <> 2: raise ValueError("Invalid tuple, must be (start,stop).") # Process argument. start, stop = arg if start is None: start = self._min if stop is None: stop = self._max # Check arguments. if not ( isinstance(start,(int,long)) or start is _MININF ): raise TypeError("Invalid type of tuple start.") if not ( isinstance(stop,(int,long)) or stop is _MAXINF ): raise TypeError("Invalid type of tuple stop.") if ( start is not _MININF and stop is not _MAXINF and start > stop ): continue if isinstance(stop,(int,long)): stop += 1 else: raise TypeError("Invalid argument.") if start > self._max: continue elif start < self._min: start = self._min if stop < self._min: continue elif stop > self._max: stop = self._max self._ranges.append((start,stop)) # Normalize set. self._normalize() # Utility functions for set operations # ------------------------------------ def _iterranges(self,r1,r2,minval=_MININF,maxval=_MAXINF): curval = minval curstates = {"r1":False,"r2":False} imax, jmax = 2*len(r1), 2*len(r2) i, j = 0, 0 while i < imax or j < jmax: if i < imax and ( ( j < jmax and r1[i>>1][i&1] < r2[j>>1][j&1] ) or j == jmax ): cur_r, newname, newstate = r1[i>>1][i&1], "r1", not (i&1) i += 1 else: cur_r, newname, newstate = r2[j>>1][j&1], "r2", not (j&1) j += 1 if curval < cur_r: if cur_r > maxval: break yield curstates, (curval,cur_r) curval = cur_r curstates[newname] = newstate if curval < maxval: yield curstates, (curval,maxval) def _normalize(self): self._ranges.sort() i = 1 while i < len(self._ranges): if self._ranges[i][0] < self._ranges[i-1][1]: self._ranges[i-1] = (self._ranges[i-1][0], max(self._ranges[i-1][1], self._ranges[i][1])) del self._ranges[i] else: i += 1 self._ranges = tuple(self._ranges) self._hash = hash(self._ranges) def __coerce__(self,other): if isinstance(other,IntSet): return self, other elif isinstance(other,(int,long,tuple)): try: return self, self.__class__(other) except TypeError: # Catch a type error, in that case the structure specified by # other is something we can't coerce, return NotImplemented. # ValueErrors are not caught, they signal that the data was # invalid for the constructor. This is appropriate to signal # as a ValueError to the caller. return NotImplemented elif isinstance(other,list): try: return self, self.__class__(*other) except TypeError: # See above. return NotImplemented return NotImplemented # Set function definitions # ------------------------ def _make_function(name,type,doc,pall,pany=None): """Makes a function to match two ranges. Accepts two types: either 'set', which defines a function which returns a set with all ranges matching pall (pany is ignored), or 'bool', which returns True if pall matches for all ranges and pany matches for any one range. doc is the dostring to give this function. pany may be none to ignore the any match. The predicates get a dict with two keys, 'r1', 'r2', which denote whether the current range is present in range1 (self) and/or range2 (other) or none of the two, respectively.""" if type == "set": def f(self,other): coerced = self.__coerce__(other) if coerced is NotImplemented: return NotImplemented other = coerced[1] newset = self.__class__.__new__(self.__class__) newset._min = min(self._min,other._min) newset._max = max(self._max,other._max) newset._ranges = [] for states, (start,stop) in \ self._iterranges(self._ranges,other._ranges, newset._min,newset._max): if pall(states): if newset._ranges and newset._ranges[-1][1] == start: newset._ranges[-1] = (newset._ranges[-1][0],stop) else: newset._ranges.append((start,stop)) newset._ranges = tuple(newset._ranges) newset._hash = hash(self._ranges) return newset elif type == "bool": def f(self,other): coerced = self.__coerce__(other) if coerced is NotImplemented: return NotImplemented other = coerced[1] _min = min(self._min,other._min) _max = max(self._max,other._max) found = not pany for states, (start,stop) in \ self._iterranges(self._ranges,other._ranges,_min,_max): if not pall(states): return False found = found or pany(states) return found else: raise ValueError("Invalid type of function to create.") try: f.func_name = name except TypeError: pass f.func_doc = doc return f # Intersection. __and__ = _make_function("__and__","set", "Intersection of two sets as a new set.", lambda s: s["r1"] and s["r2"]) __rand__ = _make_function("__rand__","set", "Intersection of two sets as a new set.", lambda s: s["r1"] and s["r2"]) intersection = _make_function("intersection","set", "Intersection of two sets as a new set.", lambda s: s["r1"] and s["r2"]) # Union. __or__ = _make_function("__or__","set", "Union of two sets as a new set.", lambda s: s["r1"] or s["r2"]) __ror__ = _make_function("__ror__","set", "Union of two sets as a new set.", lambda s: s["r1"] or s["r2"]) union = _make_function("union","set", "Union of two sets as a new set.", lambda s: s["r1"] or s["r2"]) # Difference. __sub__ = _make_function("__sub__","set", "Difference of two sets as a new set.", lambda s: s["r1"] and not s["r2"]) __rsub__ = _make_function("__rsub__","set", "Difference of two sets as a new set.", lambda s: s["r2"] and not s["r1"]) difference = _make_function("difference","set", "Difference of two sets as a new set.", lambda s: s["r1"] and not s["r2"]) # Symmetric difference. __xor__ = _make_function("__xor__","set", "Symmetric difference of two sets as a new set.", lambda s: s["r1"] ^ s["r2"]) __rxor__ = _make_function("__rxor__","set", "Symmetric difference of two sets as a new set.", lambda s: s["r1"] ^ s["r2"]) symmetric_difference = _make_function("symmetric_difference","set", "Symmetric difference of two sets as a new set.", lambda s: s["r1"] ^ s["r2"]) # Containership testing. __contains__ = _make_function("__contains__","bool", "Returns true if self is superset of other.", lambda s: s["r1"] or not s["r2"]) issubset = _make_function("issubset","bool", "Returns true if self is subset of other.", lambda s: s["r2"] or not s["r1"]) istruesubset = _make_function("istruesubset","bool", "Returns true if self is true subset of other.", lambda s: s["r2"] or not s["r1"], lambda s: s["r2"] and not s["r1"]) issuperset = _make_function("issuperset","bool", "Returns true if self is superset of other.", lambda s: s["r1"] or not s["r2"]) istruesuperset = _make_function("istruesuperset","bool", "Returns true if self is true superset of other.", lambda s: s["r1"] or not s["r2"], lambda s: s["r1"] and not s["r2"]) overlaps = _make_function("overlaps","bool", "Returns true if self overlaps with other.", lambda s: True, lambda s: s["r1"] and s["r2"]) # Comparison. __eq__ = _make_function("__eq__","bool", "Returns true if self is equal to other.", lambda s: not ( s["r1"] ^ s["r2"] )) __ne__ = _make_function("__ne__","bool", "Returns true if self is different to other.", lambda s: True, lambda s: s["r1"] ^ s["r2"]) # Clean up namespace. del _make_function # Define other functions. def inverse(self): """Inverse of set as a new set.""" newset = self.__class__.__new__(self.__class__) newset._min = self._min newset._max = self._max newset._ranges = [] laststop = self._min for r in self._ranges: if laststop < r[0]: newset._ranges.append((laststop,r[0])) laststop = r[1] if laststop < self._max: newset._ranges.append((laststop,self._max)) return newset __invert__ = inverse # Hashing # ------- def __hash__(self): """Returns a hash value representing this integer set. As the set is always stored normalized, the hash value is guaranteed to match for matching ranges.""" return self._hash # Iterating # --------- def __len__(self): """Get length of this integer set. In case the length is larger than 2**31 (including infinitely sized integer sets), it raises an OverflowError. This is due to len() restricting the size to 0 <= len < 2**31.""" if not self._ranges: return 0 if self._ranges[0][0] is _MININF or self._ranges[-1][1] is _MAXINF: raise OverflowError("Infinitely sized integer set.") rlen = 0 for r in self._ranges: rlen += r[1]-r[0] if rlen >= 2**31: raise OverflowError("Integer set bigger than 2**31.") return rlen def len(self): """Returns the length of this integer set as an integer. In case the length is infinite, returns -1. This function exists because of a limitation of the builtin len() function which expects values in the range 0 <= len < 2**31. Use this function in case your integer set might be larger.""" if not self._ranges: return 0 if self._ranges[0][0] is _MININF or self._ranges[-1][1] is _MAXINF: return -1 rlen = 0 for r in self._ranges: rlen += r[1]-r[0] return rlen def __nonzero__(self): """Returns true if this integer set contains at least one item.""" return bool(self._ranges) def __iter__(self): """Iterate over all values in this integer set. Iteration always starts by iterating from lowest to highest over the ranges that are bounded. After processing these, all ranges that are unbounded (maximum 2) are yielded intermixed.""" ubranges = [] for r in self._ranges: if r[0] is _MININF: if r[1] is _MAXINF: ubranges.extend(([0,1],[-1,-1])) else: ubranges.append([r[1]-1,-1]) elif r[1] is _MAXINF: ubranges.append([r[0],1]) else: for val in xrange(r[0],r[1]): yield val if ubranges: while True: for ubrange in ubranges: yield ubrange[0] ubrange[0] += ubrange[1] # Printing # -------- def __repr__(self): """Return a representation of this integer set. The representation is executable to get an equal integer set.""" rv = [] for start, stop in self._ranges: if ( isinstance(start,(int,long)) and isinstance(stop,(int,long)) and stop-start == 1 ): rv.append("%r" % start) elif isinstance(stop,(int,long)): rv.append("(%r,%r)" % (start,stop-1)) else: rv.append("(%r,%r)" % (start,stop)) if self._min is not _MININF: rv.append("min=%r" % self._min) if self._max is not _MAXINF: rv.append("max=%r" % self._max) return "%s(%s)" % (self.__class__.__name__,",".join(rv)) if __name__ == "__main__": # Little test script demonstrating functionality. x = IntSet((10,20),30) y = IntSet((10,20)) z = IntSet((10,20),30,(15,19),min=0,max=40) print x print x&110 print x|110 print x^(15,25) print x-12 print 12 in x print x.issubset(x) print y.issubset(x) print x.istruesubset(x) print y.istruesubset(x) for val in x: print val print x.inverse() print x == z print x == y print x <> y print hash(x) print hash(z) print len(x) print x.len() Paste-1.7.5.1/paste/util/findpackage.py0000664000175000017500000000142211354703364016251 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php import sys import os def find_package(dir): """ Given a directory, finds the equivalent package name. If it is directly in sys.path, returns ''. """ dir = os.path.abspath(dir) orig_dir = dir path = map(os.path.abspath, sys.path) packages = [] last_dir = None while 1: if dir in path: return '.'.join(packages) packages.insert(0, os.path.basename(dir)) dir = os.path.dirname(dir) if last_dir == dir: raise ValueError( "%s is not under any path found in sys.path" % orig_dir) last_dir = dir Paste-1.7.5.1/paste/util/finddata.py0000664000175000017500000000740111354703364015572 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php # Note: you may want to copy this into your setup.py file verbatim, as # you can't import this from another package, when you don't know if # that package is installed yet. import os import sys from fnmatch import fnmatchcase from distutils.util import convert_path # Provided as an attribute, so you can append to these instead # of replicating them: standard_exclude = ('*.py', '*.pyc', '*$py.class', '*~', '.*', '*.bak') standard_exclude_directories = ('.*', 'CVS', '_darcs', './build', './dist', 'EGG-INFO', '*.egg-info') def find_package_data( where='.', package='', exclude=standard_exclude, exclude_directories=standard_exclude_directories, only_in_packages=True, show_ignored=False): """ Return a dictionary suitable for use in ``package_data`` in a distutils ``setup.py`` file. The dictionary looks like:: {'package': [files]} Where ``files`` is a list of all the files in that package that don't match anything in ``exclude``. If ``only_in_packages`` is true, then top-level directories that are not packages won't be included (but directories under packages will). Directories matching any pattern in ``exclude_directories`` will be ignored; by default directories with leading ``.``, ``CVS``, and ``_darcs`` will be ignored. If ``show_ignored`` is true, then all the files that aren't included in package data are shown on stderr (for debugging purposes). Note patterns use wildcards, or can be exact paths (including leading ``./``), and all searching is case-insensitive. """ out = {} stack = [(convert_path(where), '', package, only_in_packages)] while stack: where, prefix, package, only_in_packages = stack.pop(0) for name in os.listdir(where): fn = os.path.join(where, name) if os.path.isdir(fn): bad_name = False for pattern in exclude_directories: if (fnmatchcase(name, pattern) or fn.lower() == pattern.lower()): bad_name = True if show_ignored: print >> sys.stderr, ( "Directory %s ignored by pattern %s" % (fn, pattern)) break if bad_name: continue if (os.path.isfile(os.path.join(fn, '__init__.py')) and not prefix): if not package: new_package = name else: new_package = package + '.' + name stack.append((fn, '', new_package, False)) else: stack.append((fn, prefix + name + '/', package, only_in_packages)) elif package or not only_in_packages: # is a file bad_name = False for pattern in exclude: if (fnmatchcase(name, pattern) or fn.lower() == pattern.lower()): bad_name = True if show_ignored: print >> sys.stderr, ( "File %s ignored by pattern %s" % (fn, pattern)) break if bad_name: continue out.setdefault(package, []).append(prefix+name) return out if __name__ == '__main__': import pprint pprint.pprint( find_package_data(show_ignored=True)) Paste-1.7.5.1/paste/util/classinit.py0000664000175000017500000000347111354703364016014 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php class ClassInitMeta(type): def __new__(meta, class_name, bases, new_attrs): cls = type.__new__(meta, class_name, bases, new_attrs) if (new_attrs.has_key('__classinit__') and not isinstance(cls.__classinit__, staticmethod)): setattr(cls, '__classinit__', staticmethod(cls.__classinit__.im_func)) if hasattr(cls, '__classinit__'): cls.__classinit__(cls, new_attrs) return cls def build_properties(cls, new_attrs): """ Given a class and a new set of attributes (as passed in by __classinit__), create or modify properties based on functions with special names ending in __get, __set, and __del. """ for name, value in new_attrs.items(): if (name.endswith('__get') or name.endswith('__set') or name.endswith('__del')): base = name[:-5] if hasattr(cls, base): old_prop = getattr(cls, base) if not isinstance(old_prop, property): raise ValueError( "Attribute %s is a %s, not a property; function %s is named like a property" % (base, type(old_prop), name)) attrs = {'fget': old_prop.fget, 'fset': old_prop.fset, 'fdel': old_prop.fdel, 'doc': old_prop.__doc__} else: attrs = {} attrs['f' + name[-3:]] = value if name.endswith('__get') and value.__doc__: attrs['doc'] = value.__doc__ new_prop = property(**attrs) setattr(cls, base, new_prop) Paste-1.7.5.1/paste/util/datetimeutil.py0000664000175000017500000002564211354703364016521 0ustar ianbianb# (c) 2005 Clark C. Evans and contributors # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # Some of this code was funded by: http://prometheusresearch.com """ Date, Time, and Timespan Parsing Utilities This module contains parsing support to create "human friendly" ``datetime`` object parsing. The explicit goal of these routines is to provide a multi-format date/time support not unlike that found in Microsoft Excel. In most approaches, the input is very "strict" to prevent errors -- however, this approach is much more liberal since we are assuming the user-interface is parroting back the normalized value and thus the user has immediate feedback if the data is not typed in correctly. ``parse_date`` and ``normalize_date`` These functions take a value like '9 jan 2007' and returns either an ``date`` object, or an ISO 8601 formatted date value such as '2007-01-09'. There is an option to provide an Oracle database style output as well, ``09 JAN 2007``, but this is not the default. This module always treats '/' delimiters as using US date order (since the author's clients are US based), hence '1/9/2007' is January 9th. Since this module treats the '-' as following European order this supports both modes of data-entry; together with immediate parroting back the result to the screen, the author has found this approach to work well in pratice. ``parse_time`` and ``normalize_time`` These functions take a value like '1 pm' and returns either an ``time`` object, or an ISO 8601 formatted 24h clock time such as '13:00'. There is an option to provide for US style time values, '1:00 PM', however this is not the default. ``parse_datetime`` and ``normalize_datetime`` These functions take a value like '9 jan 2007 at 1 pm' and returns either an ``datetime`` object, or an ISO 8601 formatted return (without the T) such as '2007-01-09 13:00'. There is an option to provide for Oracle / US style, '09 JAN 2007 @ 1:00 PM', however this is not the default. ``parse_delta`` and ``normalize_delta`` These functions take a value like '1h 15m' and returns either an ``timedelta`` object, or an 2-decimal fixed-point numerical value in hours, such as '1.25'. The rationale is to support meeting or time-billing lengths, not to be an accurate representation in mili-seconds. As such not all valid ``timedelta`` values will have a normalized representation. """ from datetime import timedelta, time, date from time import localtime import string __all__ = ['parse_timedelta', 'normalize_timedelta', 'parse_time', 'normalize_time', 'parse_date', 'normalize_date'] def _number(val): try: return string.atoi(val) except: return None # # timedelta # def parse_timedelta(val): """ returns a ``timedelta`` object, or None """ if not val: return None val = string.lower(val) if "." in val: val = float(val) return timedelta(hours=int(val), minutes=60*(val % 1.0)) fHour = ("h" in val or ":" in val) fMin = ("m" in val or ":" in val) fFraction = "." in val for noise in "minu:teshour()": val = string.replace(val, noise, ' ') val = string.strip(val) val = string.split(val) hr = 0.0 mi = 0 val.reverse() if fHour: hr = int(val.pop()) if fMin: mi = int(val.pop()) if len(val) > 0 and not hr: hr = int(val.pop()) return timedelta(hours=hr, minutes=mi) def normalize_timedelta(val): """ produces a normalized string value of the timedelta This module returns a normalized time span value consisting of the number of hours in fractional form. For example '1h 15min' is formatted as 01.25. """ if type(val) == str: val = parse_timedelta(val) if not val: return '' hr = val.seconds/3600 mn = (val.seconds % 3600)/60 return "%d.%02d" % (hr, mn * 100/60) # # time # def parse_time(val): if not val: return None hr = mi = 0 val = string.lower(val) amflag = (-1 != string.find(val, 'a')) # set if AM is found pmflag = (-1 != string.find(val, 'p')) # set if PM is found for noise in ":amp.": val = string.replace(val, noise, ' ') val = string.split(val) if len(val) > 1: hr = int(val[0]) mi = int(val[1]) else: val = val[0] if len(val) < 1: pass elif 'now' == val: tm = localtime() hr = tm[3] mi = tm[4] elif 'noon' == val: hr = 12 elif len(val) < 3: hr = int(val) if not amflag and not pmflag and hr < 7: hr += 12 elif len(val) < 5: hr = int(val[:-2]) mi = int(val[-2:]) else: hr = int(val[:1]) if amflag and hr >= 12: hr = hr - 12 if pmflag and hr < 12: hr = hr + 12 return time(hr, mi) def normalize_time(value, ampm): if not value: return '' if type(value) == str: value = parse_time(value) if not ampm: return "%02d:%02d" % (value.hour, value.minute) hr = value.hour am = "AM" if hr < 1 or hr > 23: hr = 12 elif hr >= 12: am = "PM" if hr > 12: hr = hr - 12 return "%02d:%02d %s" % (hr, value.minute, am) # # Date Processing # _one_day = timedelta(days=1) _str2num = {'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6, 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12 } def _month(val): for (key, mon) in _str2num.items(): if key in val: return mon raise TypeError("unknown month '%s'" % val) _days_in_month = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31, } _num2str = {1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun', 7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec', } _wkdy = ("mon", "tue", "wed", "thu", "fri", "sat", "sun") def parse_date(val): if not(val): return None val = string.lower(val) now = None # optimized check for YYYY-MM-DD strict = val.split("-") if len(strict) == 3: (y, m, d) = strict if "+" in d: d = d.split("+")[0] if " " in d: d = d.split(" ")[0] try: now = date(int(y), int(m), int(d)) val = "xxx" + val[10:] except ValueError: pass # allow for 'now', 'mon', 'tue', etc. if not now: chk = val[:3] if chk in ('now','tod'): now = date.today() elif chk in _wkdy: now = date.today() idx = list(_wkdy).index(chk) + 1 while now.isoweekday() != idx: now += _one_day # allow dates to be modified via + or - /w number of days, so # that now+3 is three days from now if now: tail = val[3:].strip() tail = tail.replace("+"," +").replace("-"," -") for item in tail.split(): try: days = int(item) except ValueError: pass else: now += timedelta(days=days) return now # ok, standard parsing yr = mo = dy = None for noise in ('/', '-', ',', '*'): val = string.replace(val, noise, ' ') for noise in _wkdy: val = string.replace(val, noise, ' ') out = [] last = False ldig = False for ch in val: if ch.isdigit(): if last and not ldig: out.append(' ') last = ldig = True else: if ldig: out.append(' ') ldig = False last = True out.append(ch) val = string.split("".join(out)) if 3 == len(val): a = _number(val[0]) b = _number(val[1]) c = _number(val[2]) if len(val[0]) == 4: yr = a if b: # 1999 6 23 mo = b dy = c else: # 1999 Jun 23 mo = _month(val[1]) dy = c elif a > 0: yr = c if len(val[2]) < 4: raise TypeError("four digit year required") if b: # 6 23 1999 dy = b mo = a else: # 23 Jun 1999 dy = a mo = _month(val[1]) else: # Jun 23, 2000 dy = b yr = c if len(val[2]) < 4: raise TypeError("four digit year required") mo = _month(val[0]) elif 2 == len(val): a = _number(val[0]) b = _number(val[1]) if a > 999: yr = a dy = 1 if b > 0: # 1999 6 mo = b else: # 1999 Jun mo = _month(val[1]) elif a > 0: if b > 999: # 6 1999 mo = a yr = b dy = 1 elif b > 0: # 6 23 mo = a dy = b else: # 23 Jun dy = a mo = _month(val[1]) else: if b > 999: # Jun 2001 yr = b dy = 1 else: # Jun 23 dy = b mo = _month(val[0]) elif 1 == len(val): val = val[0] if not val.isdigit(): mo = _month(val) if mo is not None: dy = 1 else: v = _number(val) val = str(v) if 8 == len(val): # 20010623 yr = _number(val[:4]) mo = _number(val[4:6]) dy = _number(val[6:]) elif len(val) in (3,4): if v > 1300: # 2004 yr = v mo = 1 dy = 1 else: # 1202 mo = _number(val[:-2]) dy = _number(val[-2:]) elif v < 32: dy = v else: raise TypeError("four digit year required") tm = localtime() if mo is None: mo = tm[1] if dy is None: dy = tm[2] if yr is None: yr = tm[0] return date(yr, mo, dy) def normalize_date(val, iso8601=True): if not val: return '' if type(val) == str: val = parse_date(val) if iso8601: return "%4d-%02d-%02d" % (val.year, val.month, val.day) return "%02d %s %4d" % (val.day, _num2str[val.month], val.year) Paste-1.7.5.1/paste/util/UserDict24.py0000664000175000017500000001261411354703364015712 0ustar ianbianb"""A more or less complete user-defined wrapper around dictionary objects.""" class UserDict: def __init__(self, dict=None, **kwargs): self.data = {} if dict is not None: if not hasattr(dict,'keys'): dict = type({})(dict) # make mapping from a sequence self.update(dict) if len(kwargs): self.update(kwargs) def __repr__(self): return repr(self.data) def __cmp__(self, dict): if isinstance(dict, UserDict): return cmp(self.data, dict.data) else: return cmp(self.data, dict) def __len__(self): return len(self.data) def __getitem__(self, key): return self.data[key] def __setitem__(self, key, item): self.data[key] = item def __delitem__(self, key): del self.data[key] def clear(self): self.data.clear() def copy(self): if self.__class__ is UserDict: return UserDict(self.data) import copy data = self.data try: self.data = {} c = copy.copy(self) finally: self.data = data c.update(self) return c def keys(self): return self.data.keys() def items(self): return self.data.items() def iteritems(self): return self.data.iteritems() def iterkeys(self): return self.data.iterkeys() def itervalues(self): return self.data.itervalues() def values(self): return self.data.values() def has_key(self, key): return self.data.has_key(key) def update(self, dict): if isinstance(dict, UserDict): self.data.update(dict.data) elif isinstance(dict, type(self.data)): self.data.update(dict) else: for k, v in dict.items(): self[k] = v def get(self, key, failobj=None): if not self.has_key(key): return failobj return self[key] def setdefault(self, key, failobj=None): if not self.has_key(key): self[key] = failobj return self[key] def pop(self, key, *args): return self.data.pop(key, *args) def popitem(self): return self.data.popitem() def __contains__(self, key): return key in self.data def fromkeys(cls, iterable, value=None): d = cls() for key in iterable: d[key] = value return d fromkeys = classmethod(fromkeys) class IterableUserDict(UserDict): def __iter__(self): return iter(self.data) class DictMixin: # Mixin defining all dictionary methods for classes that already have # a minimum dictionary interface including getitem, setitem, delitem, # and keys. Without knowledge of the subclass constructor, the mixin # does not define __init__() or copy(). In addition to the four base # methods, progressively more efficiency comes with defining # __contains__(), __iter__(), and iteritems(). # second level definitions support higher levels def __iter__(self): for k in self.keys(): yield k def has_key(self, key): try: value = self[key] except KeyError: return False return True def __contains__(self, key): return self.has_key(key) # third level takes advantage of second level definitions def iteritems(self): for k in self: yield (k, self[k]) def iterkeys(self): return self.__iter__() # fourth level uses definitions from lower levels def itervalues(self): for _, v in self.iteritems(): yield v def values(self): return [v for _, v in self.iteritems()] def items(self): return list(self.iteritems()) def clear(self): for key in self.keys(): del self[key] def setdefault(self, key, default): try: return self[key] except KeyError: self[key] = default return default def pop(self, key, *args): if len(args) > 1: raise TypeError, "pop expected at most 2 arguments, got "\ + repr(1 + len(args)) try: value = self[key] except KeyError: if args: return args[0] raise del self[key] return value def popitem(self): try: k, v = self.iteritems().next() except StopIteration: raise KeyError, 'container is empty' del self[k] return (k, v) def update(self, other): # Make progressively weaker assumptions about "other" if hasattr(other, 'iteritems'): # iteritems saves memory and lookups for k, v in other.iteritems(): self[k] = v elif hasattr(other, '__iter__'): # iter saves memory for k in other: self[k] = other[k] else: for k in other.keys(): self[k] = other[k] def get(self, key, default=None): try: return self[key] except KeyError: return default def __repr__(self): return repr(dict(self.iteritems())) def __cmp__(self, other): if other is None: return 1 if isinstance(other, DictMixin): other = dict(other.iteritems()) return cmp(dict(self.iteritems()), other) def __len__(self): return len(self.keys()) def __nonzero__(self): return bool(self.iteritems()) Paste-1.7.5.1/paste/util/threadinglocal.py0000664000175000017500000000271411354703364017002 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Implementation of thread-local storage, for Python versions that don't have thread local storage natively. """ try: import threading except ImportError: # No threads, so "thread local" means process-global class local(object): pass else: try: local = threading.local except AttributeError: # Added in 2.4, but now we'll have to define it ourselves import thread class local(object): def __init__(self): self.__dict__['__objs'] = {} def __getattr__(self, attr, g=thread.get_ident): try: return self.__dict__['__objs'][g()][attr] except KeyError: raise AttributeError( "No variable %s defined for the thread %s" % (attr, g())) def __setattr__(self, attr, value, g=thread.get_ident): self.__dict__['__objs'].setdefault(g(), {})[attr] = value def __delattr__(self, attr, g=thread.get_ident): try: del self.__dict__['__objs'][g()][attr] except KeyError: raise AttributeError( "No variable %s defined for thread %s" % (attr, g())) Paste-1.7.5.1/paste/util/filemixin.py0000664000175000017500000000262711354703364016011 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php class FileMixin(object): """ Used to provide auxiliary methods to objects simulating files. Objects must implement write, and read if they are input files. Also they should implement close. Other methods you may wish to override: * flush() * seek(offset[, whence]) * tell() * truncate([size]) Attributes you may wish to provide: * closed * encoding (you should also respect that in write()) * mode * newlines (hard to support) * softspace """ def flush(self): pass def next(self): return self.readline() def readline(self, size=None): # @@: This is a lame implementation; but a buffer would probably # be necessary for a better implementation output = [] while 1: next = self.read(1) if not next: return ''.join(output) output.append(next) if size and size > 0 and len(output) >= size: return ''.join(output) if next == '\n': # @@: also \r? return ''.join(output) def xreadlines(self): return self def writelines(self, lines): for line in lines: self.write(line) Paste-1.7.5.1/paste/util/looper.py0000664000175000017500000000760111354703364015322 0ustar ianbianb""" Helper for looping over sequences, particular in templates. Often in a loop in a template it's handy to know what's next up, previously up, if this is the first or last item in the sequence, etc. These can be awkward to manage in a normal Python loop, but using the looper you can get a better sense of the context. Use like:: >>> for loop, item in looper(['a', 'b', 'c']): ... print loop.number, item ... if not loop.last: ... print '---' 1 a --- 2 b --- 3 c """ __all__ = ['looper'] class looper(object): """ Helper for looping (particularly in templates) Use this like:: for loop, item in looper(seq): if loop.first: ... """ def __init__(self, seq): self.seq = seq def __iter__(self): return looper_iter(self.seq) def __repr__(self): return '<%s for %r>' % ( self.__class__.__name__, self.seq) class looper_iter(object): def __init__(self, seq): self.seq = list(seq) self.pos = 0 def __iter__(self): return self def next(self): if self.pos >= len(self.seq): raise StopIteration result = loop_pos(self.seq, self.pos), self.seq[self.pos] self.pos += 1 return result class loop_pos(object): def __init__(self, seq, pos): self.seq = seq self.pos = pos def __repr__(self): return '' % ( self.seq[pos], pos) def index(self): return self.pos index = property(index) def number(self): return self.pos + 1 number = property(number) def item(self): return self.seq[self.pos] item = property(item) def next(self): try: return self.seq[self.pos+1] except IndexError: return None next = property(next) def previous(self): if self.pos == 0: return None return self.seq[self.pos-1] previous = property(previous) def odd(self): return not self.pos % 2 odd = property(odd) def even(self): return self.pos % 2 even = property(even) def first(self): return self.pos == 0 first = property(first) def last(self): return self.pos == len(self.seq)-1 last = property(last) def length(self): return len(self.seq) length = property(length) def first_group(self, getter=None): """ Returns true if this item is the start of a new group, where groups mean that some attribute has changed. The getter can be None (the item itself changes), an attribute name like ``'.attr'``, a function, or a dict key or list index. """ if self.first: return True return self._compare_group(self.item, self.previous, getter) def last_group(self, getter=None): """ Returns true if this item is the end of a new group, where groups mean that some attribute has changed. The getter can be None (the item itself changes), an attribute name like ``'.attr'``, a function, or a dict key or list index. """ if self.last: return True return self._compare_group(self.item, self.next, getter) def _compare_group(self, item, other, getter): if getter is None: return item != other elif (isinstance(getter, basestring) and getter.startswith('.')): getter = getter[1:] if getter.endswith('()'): getter = getter[:-2] return getattr(item, getter)() != getattr(other, getter)() else: return getattr(item, getter) != getattr(other, getter) elif callable(getter): return getter(item) != getter(other) else: return item[getter] != other[getter] Paste-1.7.5.1/paste/util/string24.py0000664000175000017500000004054711354703364015504 0ustar ianbianb"""A collection of string operations (most are no longer used). Warning: most of the code you see here isn't normally used nowadays. Beginning with Python 1.6, many of these functions are implemented as methods on the standard string object. They used to be implemented by a built-in module called strop, but strop is now obsolete itself. Public module variables: whitespace -- a string containing all characters considered whitespace lowercase -- a string containing all characters considered lowercase letters uppercase -- a string containing all characters considered uppercase letters letters -- a string containing all characters considered letters digits -- a string containing all characters considered decimal digits hexdigits -- a string containing all characters considered hexadecimal digits octdigits -- a string containing all characters considered octal digits punctuation -- a string containing all characters considered punctuation printable -- a string containing all characters considered printable """ # Some strings for ctype-style character classification whitespace = ' \t\n\r\v\f' lowercase = 'abcdefghijklmnopqrstuvwxyz' uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' letters = lowercase + uppercase ascii_lowercase = lowercase ascii_uppercase = uppercase ascii_letters = ascii_lowercase + ascii_uppercase digits = '0123456789' hexdigits = digits + 'abcdef' + 'ABCDEF' octdigits = '01234567' punctuation = """!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~""" printable = digits + letters + punctuation + whitespace # Case conversion helpers # Use str to convert Unicode literal in case of -U # Note that Cookie.py bogusly uses _idmap :( l = map(chr, xrange(256)) _idmap = str('').join(l) del l # Functions which aren't available as string methods. # Capitalize the words in a string, e.g. " aBc dEf " -> "Abc Def". # See also regsub.capwords(). def capwords(s, sep=None): """capwords(s, [sep]) -> string Split the argument into words using split, capitalize each word using capitalize, and join the capitalized words using join. Note that this replaces runs of whitespace characters by a single space. """ return (sep or ' ').join([x.capitalize() for x in s.split(sep)]) # Construct a translation string _idmapL = None def maketrans(fromstr, tostr): """maketrans(frm, to) -> string Return a translation table (a string of 256 bytes long) suitable for use in string.translate. The strings frm and to must be of the same length. """ if len(fromstr) != len(tostr): raise ValueError, "maketrans arguments must have same length" global _idmapL if not _idmapL: _idmapL = map(None, _idmap) L = _idmapL[:] fromstr = map(ord, fromstr) for i in range(len(fromstr)): L[fromstr[i]] = tostr[i] return ''.join(L) #################################################################### import re as _re class _multimap: """Helper class for combining multiple mappings. Used by .{safe_,}substitute() to combine the mapping and keyword arguments. """ def __init__(self, primary, secondary): self._primary = primary self._secondary = secondary def __getitem__(self, key): try: return self._primary[key] except KeyError: return self._secondary[key] class _TemplateMetaclass(type): pattern = r""" %(delim)s(?: (?P%(delim)s) | # Escape sequence of two delimiters (?P%(id)s) | # delimiter and a Python identifier {(?P%(id)s)} | # delimiter and a braced identifier (?P) # Other ill-formed delimiter exprs ) """ def __init__(cls, name, bases, dct): super(_TemplateMetaclass, cls).__init__(name, bases, dct) if 'pattern' in dct: pattern = cls.pattern else: pattern = _TemplateMetaclass.pattern % { 'delim' : _re.escape(cls.delimiter), 'id' : cls.idpattern, } cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE) class Template: """A string class for supporting $-substitutions.""" __metaclass__ = _TemplateMetaclass delimiter = '$' idpattern = r'[_a-z][_a-z0-9]*' def __init__(self, template): self.template = template # Search for $$, $identifier, ${identifier}, and any bare $'s def _invalid(self, mo): i = mo.start('invalid') lines = self.template[:i].splitlines(True) if not lines: colno = 1 lineno = 1 else: colno = i - len(''.join(lines[:-1])) lineno = len(lines) raise ValueError('Invalid placeholder in string: line %d, col %d' % (lineno, colno)) def substitute(self, *args, **kws): if len(args) > 1: raise TypeError('Too many positional arguments') if not args: mapping = kws elif kws: mapping = _multimap(kws, args[0]) else: mapping = args[0] # Helper function for .sub() def convert(mo): # Check the most common path first. named = mo.group('named') or mo.group('braced') if named is not None: val = mapping[named] # We use this idiom instead of str() because the latter will # fail if val is a Unicode containing non-ASCII characters. return '%s' % val if mo.group('escaped') is not None: return self.delimiter if mo.group('invalid') is not None: self._invalid(mo) raise ValueError('Unrecognized named group in pattern', self.pattern) return self.pattern.sub(convert, self.template) def safe_substitute(self, *args, **kws): if len(args) > 1: raise TypeError('Too many positional arguments') if not args: mapping = kws elif kws: mapping = _multimap(kws, args[0]) else: mapping = args[0] # Helper function for .sub() def convert(mo): named = mo.group('named') if named is not None: try: # We use this idiom instead of str() because the latter # will fail if val is a Unicode containing non-ASCII return '%s' % mapping[named] except KeyError: return self.delimiter + named braced = mo.group('braced') if braced is not None: try: return '%s' % mapping[braced] except KeyError: return self.delimiter + '{' + braced + '}' if mo.group('escaped') is not None: return self.delimiter if mo.group('invalid') is not None: return self.delimiter raise ValueError('Unrecognized named group in pattern', self.pattern) return self.pattern.sub(convert, self.template) #################################################################### # NOTE: Everything below here is deprecated. Use string methods instead. # This stuff will go away in Python 3.0. # Backward compatible names for exceptions index_error = ValueError atoi_error = ValueError atof_error = ValueError atol_error = ValueError # convert UPPER CASE letters to lower case def lower(s): """lower(s) -> string Return a copy of the string s converted to lowercase. """ return s.lower() # Convert lower case letters to UPPER CASE def upper(s): """upper(s) -> string Return a copy of the string s converted to uppercase. """ return s.upper() # Swap lower case letters and UPPER CASE def swapcase(s): """swapcase(s) -> string Return a copy of the string s with upper case characters converted to lowercase and vice versa. """ return s.swapcase() # Strip leading and trailing tabs and spaces def strip(s, chars=None): """strip(s [,chars]) -> string Return a copy of the string s with leading and trailing whitespace removed. If chars is given and not None, remove characters in chars instead. If chars is unicode, S will be converted to unicode before stripping. """ return s.strip(chars) # Strip leading tabs and spaces def lstrip(s, chars=None): """lstrip(s [,chars]) -> string Return a copy of the string s with leading whitespace removed. If chars is given and not None, remove characters in chars instead. """ return s.lstrip(chars) # Strip trailing tabs and spaces def rstrip(s, chars=None): """rstrip(s [,chars]) -> string Return a copy of the string s with trailing whitespace removed. If chars is given and not None, remove characters in chars instead. """ return s.rstrip(chars) # Split a string into a list of space/tab-separated words def split(s, sep=None, maxsplit=-1): """split(s [,sep [,maxsplit]]) -> list of strings Return a list of the words in the string s, using sep as the delimiter string. If maxsplit is given, splits at no more than maxsplit places (resulting in at most maxsplit+1 words). If sep is not specified or is None, any whitespace string is a separator. (split and splitfields are synonymous) """ return s.split(sep, maxsplit) splitfields = split # Split a string into a list of space/tab-separated words def rsplit(s, sep=None, maxsplit=-1): """rsplit(s [,sep [,maxsplit]]) -> list of strings Return a list of the words in the string s, using sep as the delimiter string, starting at the end of the string and working to the front. If maxsplit is given, at most maxsplit splits are done. If sep is not specified or is None, any whitespace string is a separator. """ return s.rsplit(sep, maxsplit) # Join fields with optional separator def join(words, sep = ' '): """join(list [,sep]) -> string Return a string composed of the words in list, with intervening occurrences of sep. The default separator is a single space. (joinfields and join are synonymous) """ return sep.join(words) joinfields = join # Find substring, raise exception if not found def index(s, *args): """index(s, sub [,start [,end]]) -> int Like find but raises ValueError when the substring is not found. """ return s.index(*args) # Find last substring, raise exception if not found def rindex(s, *args): """rindex(s, sub [,start [,end]]) -> int Like rfind but raises ValueError when the substring is not found. """ return s.rindex(*args) # Count non-overlapping occurrences of substring def count(s, *args): """count(s, sub[, start[,end]]) -> int Return the number of occurrences of substring sub in string s[start:end]. Optional arguments start and end are interpreted as in slice notation. """ return s.count(*args) # Find substring, return -1 if not found def find(s, *args): """find(s, sub [,start [,end]]) -> in Return the lowest index in s where substring sub is found, such that sub is contained within s[start,end]. Optional arguments start and end are interpreted as in slice notation. Return -1 on failure. """ return s.find(*args) # Find last substring, return -1 if not found def rfind(s, *args): """rfind(s, sub [,start [,end]]) -> int Return the highest index in s where substring sub is found, such that sub is contained within s[start,end]. Optional arguments start and end are interpreted as in slice notation. Return -1 on failure. """ return s.rfind(*args) # for a bit of speed _float = float _int = int _long = long # Convert string to float def atof(s): """atof(s) -> float Return the floating point number represented by the string s. """ return _float(s) # Convert string to integer def atoi(s , base=10): """atoi(s [,base]) -> int Return the integer represented by the string s in the given base, which defaults to 10. The string s must consist of one or more digits, possibly preceded by a sign. If base is 0, it is chosen from the leading characters of s, 0 for octal, 0x or 0X for hexadecimal. If base is 16, a preceding 0x or 0X is accepted. """ return _int(s, base) # Convert string to long integer def atol(s, base=10): """atol(s [,base]) -> long Return the long integer represented by the string s in the given base, which defaults to 10. The string s must consist of one or more digits, possibly preceded by a sign. If base is 0, it is chosen from the leading characters of s, 0 for octal, 0x or 0X for hexadecimal. If base is 16, a preceding 0x or 0X is accepted. A trailing L or l is not accepted, unless base is 0. """ return _long(s, base) # Left-justify a string def ljust(s, width, *args): """ljust(s, width[, fillchar]) -> string Return a left-justified version of s, in a field of the specified width, padded with spaces as needed. The string is never truncated. If specified the fillchar is used instead of spaces. """ return s.ljust(width, *args) # Right-justify a string def rjust(s, width, *args): """rjust(s, width[, fillchar]) -> string Return a right-justified version of s, in a field of the specified width, padded with spaces as needed. The string is never truncated. If specified the fillchar is used instead of spaces. """ return s.rjust(width, *args) # Center a string def center(s, width, *args): """center(s, width[, fillchar]) -> string Return a center version of s, in a field of the specified width. padded with spaces as needed. The string is never truncated. If specified the fillchar is used instead of spaces. """ return s.center(width, *args) # Zero-fill a number, e.g., (12, 3) --> '012' and (-3, 3) --> '-03' # Decadent feature: the argument may be a string or a number # (Use of this is deprecated; it should be a string as with ljust c.s.) def zfill(x, width): """zfill(x, width) -> string Pad a numeric string x with zeros on the left, to fill a field of the specified width. The string x is never truncated. """ if not isinstance(x, basestring): x = repr(x) return x.zfill(width) # Expand tabs in a string. # Doesn't take non-printing chars into account, but does understand \n. def expandtabs(s, tabsize=8): """expandtabs(s [,tabsize]) -> string Return a copy of the string s with all tab characters replaced by the appropriate number of spaces, depending on the current column, and the tabsize (default 8). """ return s.expandtabs(tabsize) # Character translation through look-up table. def translate(s, table, deletions=""): """translate(s,table [,deletions]) -> string Return a copy of the string s, where all characters occurring in the optional argument deletions are removed, and the remaining characters have been mapped through the given translation table, which must be a string of length 256. The deletions argument is not allowed for Unicode strings. """ if deletions: return s.translate(table, deletions) else: # Add s[:0] so that if s is Unicode and table is an 8-bit string, # table is converted to Unicode. This means that table *cannot* # be a dictionary -- for that feature, use u.translate() directly. return s.translate(table + s[:0]) # Capitalize a string, e.g. "aBc dEf" -> "Abc def". def capitalize(s): """capitalize(s) -> string Return a copy of the string s with only its first character capitalized. """ return s.capitalize() # Substring replacement (global) def replace(s, old, new, maxsplit=-1): """replace (str, old, new[, maxsplit]) -> string Return a copy of string str with all occurrences of substring old replaced by new. If the optional argument maxsplit is given, only the first maxsplit occurrences are replaced. """ return s.replace(old, new, maxsplit) # Try importing optional built-in module "strop" -- if it exists, # it redefines some string operations that are 100-1000 times faster. # It also defines values for whitespace, lowercase and uppercase # that match 's definitions. try: from strop import maketrans, lowercase, uppercase, whitespace letters = lowercase + uppercase except ImportError: pass # Use the original versions Paste-1.7.5.1/paste/util/__init__.py0000664000175000017500000000012611354703364015554 0ustar ianbianb""" Package for miscellaneous routines that do not depend on other parts of Paste """ Paste-1.7.5.1/paste/util/classinstance.py0000664000175000017500000000253511354703364016655 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php class classinstancemethod(object): """ Acts like a class method when called from a class, like an instance method when called by an instance. The method should take two arguments, 'self' and 'cls'; one of these will be None depending on how the method was called. """ def __init__(self, func): self.func = func self.__doc__ = func.__doc__ def __get__(self, obj, type=None): return _methodwrapper(self.func, obj=obj, type=type) class _methodwrapper(object): def __init__(self, func, obj, type): self.func = func self.obj = obj self.type = type def __call__(self, *args, **kw): assert not kw.has_key('self') and not kw.has_key('cls'), ( "You cannot use 'self' or 'cls' arguments to a " "classinstancemethod") return self.func(*((self.obj, self.type) + args), **kw) def __repr__(self): if self.obj is None: return ('' % (self.type.__name__, self.func.func_name)) else: return ('' % (self.type.__name__, self.func.func_name, self.obj)) Paste-1.7.5.1/paste/util/killthread.py0000664000175000017500000000231511437654572016152 0ustar ianbianb""" Kill a thread, from http://sebulba.wikispaces.com/recipe+thread2 """ import types try: import ctypes except ImportError: raise ImportError( "You cannot use paste.util.killthread without ctypes installed") if not hasattr(ctypes, 'pythonapi'): raise ImportError( "You cannot use paste.util.killthread without ctypes.pythonapi") def async_raise(tid, exctype): """raises the exception, performs cleanup if needed. tid is the value given by thread.get_ident() (an integer). Raise SystemExit to kill a thread.""" if not isinstance(exctype, (types.ClassType, type)): raise TypeError("Only types can be raised (not instances)") if not isinstance(tid, int): raise TypeError("tid must be an integer") res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), ctypes.py_object(exctype)) if res == 0: raise ValueError("invalid thread id") elif res != 1: # """if it returns a number greater than one, you're in trouble, # and you should call it again with exc=NULL to revert the effect""" ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), 0) raise SystemError("PyThreadState_SetAsyncExc failed") Paste-1.7.5.1/paste/util/scgiserver.py0000664000175000017500000001300411354703364016170 0ustar ianbianb""" SCGI-->WSGI application proxy, "SWAP". (Originally written by Titus Brown.) This lets an SCGI front-end like mod_scgi be used to execute WSGI application objects. To use it, subclass the SWAP class like so:: class TestAppHandler(swap.SWAP): def __init__(self, *args, **kwargs): self.prefix = '/canal' self.app_obj = TestAppClass swap.SWAP.__init__(self, *args, **kwargs) where 'TestAppClass' is the application object from WSGI and '/canal' is the prefix for what is served by the SCGI Web-server-side process. Then execute the SCGI handler "as usual" by doing something like this:: scgi_server.SCGIServer(TestAppHandler, port=4000).serve() and point mod_scgi (or whatever your SCGI front end is) at port 4000. Kudos to the WSGI folk for writing a nice PEP & the Quixote folk for writing a nice extensible SCGI server for Python! """ import sys import time from scgi import scgi_server def debug(msg): timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) sys.stderr.write("[%s] %s\n" % (timestamp, msg)) class SWAP(scgi_server.SCGIHandler): """ SCGI->WSGI application proxy: let an SCGI server execute WSGI application objects. """ app_obj = None prefix = None def __init__(self, *args, **kwargs): assert self.app_obj, "must set app_obj" assert self.prefix is not None, "must set prefix" args = (self,) + args scgi_server.SCGIHandler.__init__(*args, **kwargs) def handle_connection(self, conn): """ Handle an individual connection. """ input = conn.makefile("r") output = conn.makefile("w") environ = self.read_env(input) environ['wsgi.input'] = input environ['wsgi.errors'] = sys.stderr environ['wsgi.version'] = (1, 0) environ['wsgi.multithread'] = False environ['wsgi.multiprocess'] = True environ['wsgi.run_once'] = False # dunno how SCGI does HTTPS signalling; can't test it myself... @CTB if environ.get('HTTPS','off') in ('on','1'): environ['wsgi.url_scheme'] = 'https' else: environ['wsgi.url_scheme'] = 'http' ## SCGI does some weird environ manglement. We need to set ## SCRIPT_NAME from 'prefix' and then set PATH_INFO from ## REQUEST_URI. prefix = self.prefix path = environ['REQUEST_URI'][len(prefix):].split('?', 1)[0] environ['SCRIPT_NAME'] = prefix environ['PATH_INFO'] = path headers_set = [] headers_sent = [] chunks = [] def write(data): chunks.append(data) def start_response(status, response_headers, exc_info=None): if exc_info: try: if headers_sent: # Re-raise original exception if headers sent raise exc_info[0], exc_info[1], exc_info[2] finally: exc_info = None # avoid dangling circular ref elif headers_set: raise AssertionError("Headers already set!") headers_set[:] = [status, response_headers] return write ### result = self.app_obj(environ, start_response) try: for data in result: chunks.append(data) # Before the first output, send the stored headers if not headers_set: # Error -- the app never called start_response status = '500 Server Error' response_headers = [('Content-type', 'text/html')] chunks = ["XXX start_response never called"] else: status, response_headers = headers_sent[:] = headers_set output.write('Status: %s\r\n' % status) for header in response_headers: output.write('%s: %s\r\n' % header) output.write('\r\n') for data in chunks: output.write(data) finally: if hasattr(result,'close'): result.close() # SCGI backends use connection closing to signal 'fini'. try: input.close() output.close() conn.close() except IOError, err: debug("IOError while closing connection ignored: %s" % err) def serve_application(application, prefix, port=None, host=None, max_children=None): """ Serve the specified WSGI application via SCGI proxy. ``application`` The WSGI application to serve. ``prefix`` The prefix for what is served by the SCGI Web-server-side process. ``port`` Optional port to bind the SCGI proxy to. Defaults to SCGIServer's default port value. ``host`` Optional host to bind the SCGI proxy to. Defaults to SCGIServer's default host value. ``host`` Optional maximum number of child processes the SCGIServer will spawn. Defaults to SCGIServer's default max_children value. """ class SCGIAppHandler(SWAP): def __init__ (self, *args, **kwargs): self.prefix = prefix self.app_obj = application SWAP.__init__(self, *args, **kwargs) kwargs = dict(handler_class=SCGIAppHandler) for kwarg in ('host', 'port', 'max_children'): if locals()[kwarg] is not None: kwargs[kwarg] = locals()[kwarg] scgi_server.SCGIServer(**kwargs).serve() Paste-1.7.5.1/paste/config.py0000664000175000017500000001033011354703364014303 0ustar ianbianb# (c) 2006 Ian Bicking, Philip Jenvey and contributors # Written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """Paste Configuration Middleware and Objects""" from paste.registry import RegistryManager, StackedObjectProxy __all__ = ['DispatchingConfig', 'CONFIG', 'ConfigMiddleware'] class DispatchingConfig(StackedObjectProxy): """ This is a configuration object that can be used globally, imported, have references held onto. The configuration may differ by thread (or may not). Specific configurations are registered (and deregistered) either for the process or for threads. """ # @@: What should happen when someone tries to add this # configuration to itself? Probably the conf should become # resolved, and get rid of this delegation wrapper def __init__(self, name='DispatchingConfig'): super(DispatchingConfig, self).__init__(name=name) self.__dict__['_process_configs'] = [] def push_thread_config(self, conf): """ Make ``conf`` the active configuration for this thread. Thread-local configuration always overrides process-wide configuration. This should be used like:: conf = make_conf() dispatching_config.push_thread_config(conf) try: ... do stuff ... finally: dispatching_config.pop_thread_config(conf) """ self._push_object(conf) def pop_thread_config(self, conf=None): """ Remove a thread-local configuration. If ``conf`` is given, it is checked against the popped configuration and an error is emitted if they don't match. """ self._pop_object(conf) def push_process_config(self, conf): """ Like push_thread_config, but applies the configuration to the entire process. """ self._process_configs.append(conf) def pop_process_config(self, conf=None): self._pop_from(self._process_configs, conf) def _pop_from(self, lst, conf): popped = lst.pop() if conf is not None and popped is not conf: raise AssertionError( "The config popped (%s) is not the same as the config " "expected (%s)" % (popped, conf)) def _current_obj(self): try: return super(DispatchingConfig, self)._current_obj() except TypeError: if self._process_configs: return self._process_configs[-1] raise AttributeError( "No configuration has been registered for this process " "or thread") current = current_conf = _current_obj CONFIG = DispatchingConfig() no_config = object() class ConfigMiddleware(RegistryManager): """ A WSGI middleware that adds a ``paste.config`` key (by default) to the request environment, as well as registering the configuration temporarily (for the length of the request) with ``paste.config.CONFIG`` (or any other ``DispatchingConfig`` object). """ def __init__(self, application, config, dispatching_config=CONFIG, environ_key='paste.config'): """ This delegates all requests to `application`, adding a *copy* of the configuration `config`. """ def register_config(environ, start_response): popped_config = environ.get(environ_key, no_config) current_config = environ[environ_key] = config.copy() environ['paste.registry'].register(dispatching_config, current_config) try: app_iter = application(environ, start_response) finally: if popped_config is no_config: environ.pop(environ_key, None) else: environ[environ_key] = popped_config return app_iter super(self.__class__, self).__init__(register_config) def make_config_filter(app, global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) return ConfigMiddleware(app, conf) make_config_middleware = ConfigMiddleware.__doc__ Paste-1.7.5.1/paste/httpheaders.py0000664000175000017500000012412211437650447015363 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php # (c) 2005 Ian Bicking, Clark C. Evans and contributors # This module is part of the Python Paste Project and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # Some of this code was funded by: http://prometheusresearch.com """ HTTP Message Header Fields (see RFC 4229) This contains general support for HTTP/1.1 message headers [1]_ in a manner that supports WSGI ``environ`` [2]_ and ``response_headers`` [3]_. Specifically, this module defines a ``HTTPHeader`` class whose instances correspond to field-name items. The actual field-content for the message-header is stored in the appropriate WSGI collection (either the ``environ`` for requests, or ``response_headers`` for responses). Each ``HTTPHeader`` instance is a callable (defining ``__call__``) that takes one of the following: - an ``environ`` dictionary, returning the corresponding header value by according to the WSGI's ``HTTP_`` prefix mechanism, e.g., ``USER_AGENT(environ)`` returns ``environ.get('HTTP_USER_AGENT')`` - a ``response_headers`` list, giving a comma-delimited string for each corresponding ``header_value`` tuple entries (see below). - a sequence of string ``*args`` that are comma-delimited into a single string value: ``CONTENT_TYPE("text/html","text/plain")`` returns ``"text/html, text/plain"`` - a set of ``**kwargs`` keyword arguments that are used to create a header value, in a manner dependent upon the particular header in question (to make value construction easier and error-free): ``CONTENT_DISPOSITION(max_age=CONTENT_DISPOSITION.ONEWEEK)`` returns ``"public, max-age=60480"`` Each ``HTTPHeader`` instance also provides several methods to act on a WSGI collection, for removing and setting header values. ``delete(collection)`` This method removes all entries of the corresponding header from the given collection (``environ`` or ``response_headers``), e.g., ``USER_AGENT.delete(environ)`` deletes the 'HTTP_USER_AGENT' entry from the ``environ``. ``update(collection, *args, **kwargs)`` This method does an in-place replacement of the given header entry, for example: ``CONTENT_LENGTH(response_headers,len(body))`` The first argument is a valid ``environ`` dictionary or ``response_headers`` list; remaining arguments are passed on to ``__call__(*args, **kwargs)`` for value construction. ``apply(collection, **kwargs)`` This method is similar to update, only that it may affect other headers. For example, according to recommendations in RFC 2616, certain Cache-Control configurations should also set the ``Expires`` header for HTTP/1.0 clients. By default, ``apply()`` is simply ``update()`` but limited to keyword arguments. This particular approach to managing headers within a WSGI collection has several advantages: 1. Typos in the header name are easily detected since they become a ``NameError`` when executed. The approach of using header strings directly can be problematic; for example, the following should return ``None`` : ``environ.get("HTTP_ACCEPT_LANGUAGES")`` 2. For specific headers with validation, using ``__call__`` will result in an automatic header value check. For example, the _ContentDisposition header will reject a value having ``maxage`` or ``max_age`` (the appropriate parameter is ``max-age`` ). 3. When appending/replacing headers, the field-name has the suggested RFC capitalization (e.g. ``Content-Type`` or ``ETag``) for user-agents that incorrectly use case-sensitive matches. 4. Some headers (such as ``Content-Type``) are 0, that is, only one entry of this type may occur in a given set of ``response_headers``. This module knows about those cases and enforces this cardinality constraint. 5. The exact details of WSGI header management are abstracted so the programmer need not worry about operational differences between ``environ`` dictionary or ``response_headers`` list. 6. Sorting of ``HTTPHeaders`` is done following the RFC suggestion that general-headers come first, followed by request and response headers, and finishing with entity-headers. 7. Special care is given to exceptional cases such as Set-Cookie which violates the RFC's recommendation about combining header content into a single entry using comma separation. A particular difficulty with HTTP message headers is a categorization of sorts as described in section 4.2: Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma. This creates three fundamentally different kinds of headers: - Those that do not have a #(values) production, and hence are singular and may only occur once in a set of response fields; this case is handled by the ``_SingleValueHeader`` subclass. - Those which have the #(values) production and follow the combining rule outlined above; our ``_MultiValueHeader`` case. - Those which are multi-valued, but cannot be combined (such as the ``Set-Cookie`` header due to its ``Expires`` parameter); or where combining them into a single header entry would cause common user-agents to fail (``WWW-Authenticate``, ``Warning``) since they fail to handle dates even when properly quoted. This case is handled by ``_MultiEntryHeader``. Since this project does not have time to provide rigorous support and validation for all headers, it does a basic construction of headers listed in RFC 2616 (plus a few others) so that they can be obtained by simply doing ``from paste.httpheaders import *``; the name of the header instance is the "common name" less any dashes to give CamelCase style names. .. [1] http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 .. [2] http://www.python.org/peps/pep-0333.html#environ-variables .. [3] http://www.python.org/peps/pep-0333.html#the-start-response-callable """ import mimetypes import urllib2 import re from rfc822 import formatdate, parsedate_tz, mktime_tz from time import time as now from httpexceptions import HTTPBadRequest __all__ = ['get_header', 'list_headers', 'normalize_headers', 'HTTPHeader', 'EnvironVariable' ] class EnvironVariable(str): """ a CGI ``environ`` variable as described by WSGI This is a helper object so that standard WSGI ``environ`` variables can be extracted w/o syntax error possibility. """ def __call__(self, environ): return environ.get(self,'') def __repr__(self): return '' % self def update(self, environ, value): environ[self] = value REMOTE_USER = EnvironVariable("REMOTE_USER") REMOTE_SESSION = EnvironVariable("REMOTE_SESSION") AUTH_TYPE = EnvironVariable("AUTH_TYPE") REQUEST_METHOD = EnvironVariable("REQUEST_METHOD") SCRIPT_NAME = EnvironVariable("SCRIPT_NAME") PATH_INFO = EnvironVariable("PATH_INFO") for _name, _obj in globals().items(): if isinstance(_obj, EnvironVariable): __all__.append(_name) _headers = {} class HTTPHeader(object): """ an HTTP header HTTPHeader instances represent a particular ``field-name`` of an HTTP message header. They do not hold a field-value, but instead provide operations that work on is corresponding values. Storage of the actual field values is done with WSGI ``environ`` or ``response_headers`` as appropriate. Typically, a sub-classes that represent a specific HTTP header, such as _ContentDisposition, are 0. Once constructed the HTTPHeader instances themselves are immutable and stateless. For purposes of documentation a "container" refers to either a WSGI ``environ`` dictionary, or a ``response_headers`` list. Member variables (and correspondingly constructor arguments). ``name`` the ``field-name`` of the header, in "common form" as presented in RFC 2616; e.g. 'Content-Type' ``category`` one of 'general', 'request', 'response', or 'entity' ``version`` version of HTTP (informational) with which the header should be recognized ``sort_order`` sorting order to be applied before sorting on field-name when ordering headers in a response Special Methods: ``__call__`` The primary method of the HTTPHeader instance is to make it a callable, it takes either a collection, a string value, or keyword arguments and attempts to find/construct a valid field-value ``__lt__`` This method is used so that HTTPHeader objects can be sorted in a manner suggested by RFC 2616. ``__str__`` The string-value for instances of this class is the ``field-name``. Primary Methods: ``delete()`` remove the all occurrences (if any) of the given header in the collection provided ``update()`` replaces (if they exist) all field-value items in the given collection with the value provided ``tuples()`` returns a set of (field-name, field-value) tuples 5 for extending ``response_headers`` Custom Methods (these may not be implemented): ``apply()`` similar to ``update``, but with two differences; first, only keyword arguments can be used, and second, specific sub-classes may introduce side-effects ``parse()`` converts a string value of the header into a more usable form, such as time in seconds for a date header, etc. The collected versions of initialized header instances are immediately registered and accessible through the ``get_header`` function. Do not inherit from this directly, use one of ``_SingleValueHeader``, ``_MultiValueHeader``, or ``_MultiEntryHeader`` as appropriate. """ # # Things which can be customized # version = '1.1' category = 'general' reference = '' extensions = {} def compose(self, **kwargs): """ build header value from keyword arguments This method is used to build the corresponding header value when keyword arguments (or no arguments) were provided. The result should be a sequence of values. For example, the ``Expires`` header takes a keyword argument ``time`` (e.g. time.time()) from which it returns a the corresponding date. """ raise NotImplementedError() def parse(self, *args, **kwargs): """ convert raw header value into more usable form This method invokes ``values()`` with the arguments provided, parses the header results, and then returns a header-specific data structure corresponding to the header. For example, the ``Expires`` header returns seconds (as returned by time.time()) """ raise NotImplementedError() def apply(self, collection, **kwargs): """ update the collection /w header value (may have side effects) This method is similar to ``update`` only that usage may result in other headers being changed as recommended by the corresponding specification. The return value is defined by the particular sub-class. For example, the ``_CacheControl.apply()`` sets the ``Expires`` header in addition to its normal behavior. """ self.update(collection, **kwargs) # # Things which are standardized (mostly) # def __new__(cls, name, category=None, reference=None, version=None): """ construct a new ``HTTPHeader`` instance We use the ``__new__`` operator to ensure that only one ``HTTPHeader`` instance exists for each field-name, and to register the header so that it can be found/enumerated. """ self = get_header(name, raiseError=False) if self: # Allow the registration to happen again, but assert # that everything is identical. assert self.name == name, \ "duplicate registration with different capitalization" assert self.category == category, \ "duplicate registration with different category" assert cls == self.__class__, \ "duplicate registration with different class" return self self = object.__new__(cls) self.name = name assert isinstance(self.name, str) self.category = category or self.category self.version = version or self.version self.reference = reference or self.reference _headers[self.name.lower()] = self self.sort_order = {'general': 1, 'request': 2, 'response': 3, 'entity': 4 }[self.category] self._environ_name = getattr(self, '_environ_name', 'HTTP_'+ self.name.upper().replace("-","_")) self._headers_name = getattr(self, '_headers_name', self.name.lower()) assert self.version in ('1.1', '1.0', '0.9') return self def __str__(self): return self.name def __lt__(self, other): """ sort header instances as specified by RFC 2616 Re-define sorting so that general headers are first, followed by request/response headers, and then entity headers. The list.sort() methods use the less-than operator for this purpose. """ if isinstance(other, HTTPHeader): if self.sort_order != other.sort_order: return self.sort_order < other.sort_order return self.name < other.name return False def __repr__(self): ref = self.reference and (' (%s)' % self.reference) or '' return '<%s %s%s>' % (self.__class__.__name__, self.name, ref) def values(self, *args, **kwargs): """ find/construct field-value(s) for the given header Resolution is done according to the following arguments: - If only keyword arguments are given, then this is equivalent to ``compose(**kwargs)``. - If the first (and only) argument is a dict, it is assumed to be a WSGI ``environ`` and the result of the corresponding ``HTTP_`` entry is returned. - If the first (and only) argument is a list, it is assumed to be a WSGI ``response_headers`` and the field-value(s) for this header are collected and returned. - In all other cases, the arguments are collected, checked that they are string values, possibly verified by the header's logic, and returned. At this time it is an error to provide keyword arguments if args is present (this might change). It is an error to provide both a WSGI object and also string arguments. If no arguments are provided, then ``compose()`` is called to provide a default value for the header; if there is not default it is an error. """ if not args: return self.compose(**kwargs) if list == type(args[0]): assert 1 == len(args) result = [] name = self.name.lower() for value in [value for header, value in args[0] if header.lower() == name]: result.append(value) return result if dict == type(args[0]): assert 1 == len(args) and 'wsgi.version' in args[0] value = args[0].get(self._environ_name) if not value: return () return (value,) for item in args: assert not type(item) in (dict, list) return args def __call__(self, *args, **kwargs): """ converts ``values()`` into a string value This method converts the results of ``values()`` into a string value for common usage. By default, it is asserted that only one value exists; if you need to access all values then either call ``values()`` directly, or inherit ``_MultiValueHeader`` which overrides this method to return a comma separated list of values as described by section 4.2 of RFC 2616. """ values = self.values(*args, **kwargs) assert isinstance(values, (tuple, list)) if not values: return '' assert len(values) == 1, "more than one value: %s" % repr(values) return str(values[0]).strip() def delete(self, collection): """ removes all occurances of the header from the collection provided """ if type(collection) == dict: if self._environ_name in collection: del collection[self._environ_name] return self assert list == type(collection) i = 0 while i < len(collection): if collection[i][0].lower() == self._headers_name: del collection[i] continue i += 1 def update(self, collection, *args, **kwargs): """ updates the collection with the provided header value This method replaces (in-place when possible) all occurrences of the given header with the provided value. If no value is provided, this is the same as ``remove`` (note that this case can only occur if the target is a collection w/o a corresponding header value). The return value is the new header value (which could be a list for ``_MultiEntryHeader`` instances). """ value = self.__call__(*args, **kwargs) if not value: self.delete(collection) return if type(collection) == dict: collection[self._environ_name] = value return assert list == type(collection) i = 0 found = False while i < len(collection): if collection[i][0].lower() == self._headers_name: if found: del collection[i] continue collection[i] = (self.name, value) found = True i += 1 if not found: collection.append((self.name, value)) def tuples(self, *args, **kwargs): value = self.__call__(*args, **kwargs) if not value: return () return [(self.name, value)] class _SingleValueHeader(HTTPHeader): """ a ``HTTPHeader`` with exactly a single value This is the default behavior of ``HTTPHeader`` where returning a the string-value of headers via ``__call__`` assumes that only a single value exists. """ pass class _MultiValueHeader(HTTPHeader): """ a ``HTTPHeader`` with one or more values The field-value for these header instances is is allowed to be more than one value; whereby the ``__call__`` method returns a comma separated list as described by section 4.2 of RFC 2616. """ def __call__(self, *args, **kwargs): results = self.values(*args, **kwargs) if not results: return '' return ", ".join([str(v).strip() for v in results]) def parse(self, *args, **kwargs): value = self.__call__(*args, **kwargs) values = value.split(',') return [ v.strip() for v in values if v.strip()] class _MultiEntryHeader(HTTPHeader): """ a multi-value ``HTTPHeader`` where items cannot be combined with a comma This header is multi-valued, but the values should not be combined with a comma since the header is not in compliance with RFC 2616 (Set-Cookie due to Expires parameter) or which common user-agents do not behave well when the header values are combined. """ def update(self, collection, *args, **kwargs): assert list == type(collection), "``environ`` may not be updated" self.delete(collection) collection.extend(self.tuples(*args, **kwargs)) def tuples(self, *args, **kwargs): values = self.values(*args, **kwargs) if not values: return () return [(self.name, value.strip()) for value in values] def get_header(name, raiseError=True): """ find the given ``HTTPHeader`` instance This function finds the corresponding ``HTTPHeader`` for the ``name`` provided. So that python-style names can be used, underscores are converted to dashes before the lookup. """ retval = _headers.get(str(name).strip().lower().replace("_","-")) if not retval and raiseError: raise AssertionError("'%s' is an unknown header" % name) return retval def list_headers(general=None, request=None, response=None, entity=None): " list all headers for a given category " if not (general or request or response or entity): general = request = response = entity = True search = [] for (bool, strval) in ((general, 'general'), (request, 'request'), (response, 'response'), (entity, 'entity')): if bool: search.append(strval) return [head for head in _headers.values() if head.category in search] def normalize_headers(response_headers, strict=True): """ sort headers as suggested by RFC 2616 This alters the underlying response_headers to use the common name for each header; as well as sorting them with general headers first, followed by request/response headers, then entity headers, and unknown headers last. """ category = {} for idx in range(len(response_headers)): (key, val) = response_headers[idx] head = get_header(key, strict) if not head: newhead = '-'.join([x.capitalize() for x in key.replace("_","-").split("-")]) response_headers[idx] = (newhead, val) category[newhead] = 4 continue response_headers[idx] = (str(head), val) category[str(head)] = head.sort_order def compare(a, b): ac = category[a[0]] bc = category[b[0]] if ac == bc: return cmp(a[0], b[0]) return cmp(ac, bc) response_headers.sort(compare) class _DateHeader(_SingleValueHeader): """ handle date-based headers This extends the ``_SingleValueHeader`` object with specific treatment of time values: - It overrides ``compose`` to provide a sole keyword argument ``time`` which is an offset in seconds from the current time. - A ``time`` method is provided which parses the given value and returns the current time value. """ def compose(self, time=None, delta=None): time = time or now() if delta: assert type(delta) == int time += delta return (formatdate(time),) def parse(self, *args, **kwargs): """ return the time value (in seconds since 1970) """ value = self.__call__(*args, **kwargs) if value: try: return mktime_tz(parsedate_tz(value)) except (TypeError, OverflowError): raise HTTPBadRequest(( "Received an ill-formed timestamp for %s: %s\r\n") % (self.name, value)) # # Following are specific HTTP headers. Since these classes are mostly # singletons, there is no point in keeping the class around once it has # been instantiated, so we use the same name. # class _CacheControl(_MultiValueHeader): """ Cache-Control, RFC 2616 14.9 (use ``CACHE_CONTROL``) This header can be constructed (using keyword arguments), by first specifying one of the following mechanisms: ``public`` if True, this argument specifies that the response, as a whole, may be cashed. ``private`` if True, this argument specifies that the response, as a whole, may be cashed; this implementation does not support the enumeration of private fields ``no_cache`` if True, this argument specifies that the response, as a whole, may not be cashed; this implementation does not support the enumeration of private fields In general, only one of the above three may be True, the other 2 must then be False or None. If all three are None, then the cache is assumed to be ``public``. Following one of these mechanism specifiers are various modifiers: ``no_store`` indicates if content may be stored on disk; otherwise cache is limited to memory (note: users can still save the data, this applies to intermediate caches) ``max_age`` the maximum duration (in seconds) for which the content should be cached; if ``no-cache`` is specified, this defaults to 0 seconds ``s_maxage`` the maximum duration (in seconds) for which the content should be allowed in a shared cache. ``no_transform`` specifies that an intermediate cache should not convert the content from one type to another (e.g. transform a BMP to a PNG). ``extensions`` gives additional cache-control extensions, such as items like, community="UCI" (14.9.6) The usage of ``apply()`` on this header has side-effects. As recommended by RFC 2616, if ``max_age`` is provided, then then the ``Expires`` header is also calculated for HTTP/1.0 clients and proxies (this is done at the time ``apply()`` is called). For ``no-cache`` and for ``private`` cases, we either do not want the response cached or do not want any response accidently returned to other users; so to prevent this case, we set the ``Expires`` header to the time of the request, signifying to HTTP/1.0 transports that the content isn't to be cached. If you are using SSL, your communication is already "private", so to work with HTTP/1.0 browsers over SSL, consider specifying your cache as ``public`` as the distinction between public and private is moot. """ # common values for max-age; "good enough" approximates ONE_HOUR = 60*60 ONE_DAY = ONE_HOUR * 24 ONE_WEEK = ONE_DAY * 7 ONE_MONTH = ONE_DAY * 30 ONE_YEAR = ONE_WEEK * 52 def _compose(self, public=None, private=None, no_cache=None, no_store=False, max_age=None, s_maxage=None, no_transform=False, **extensions): assert isinstance(max_age, (type(None), int)) assert isinstance(s_maxage, (type(None), int)) expires = 0 result = [] if private is True: assert not public and not no_cache and not s_maxage result.append('private') elif no_cache is True: assert not public and not private and not max_age result.append('no-cache') else: assert public is None or public is True assert not private and not no_cache expires = max_age result.append('public') if no_store: result.append('no-store') if no_transform: result.append('no-transform') if max_age is not None: result.append('max-age=%d' % max_age) if s_maxage is not None: result.append('s-maxage=%d' % s_maxage) for (k, v) in extensions.items(): if k not in self.extensions: raise AssertionError("unexpected extension used: '%s'" % k) result.append('%s="%s"' % (k.replace("_", "-"), v)) return (result, expires) def compose(self, **kwargs): (result, expires) = self._compose(**kwargs) return result def apply(self, collection, **kwargs): """ returns the offset expiration in seconds """ (result, expires) = self._compose(**kwargs) if expires is not None: EXPIRES.update(collection, delta=expires) self.update(collection, *result) return expires _CacheControl('Cache-Control', 'general', 'RFC 2616, 14.9') class _ContentType(_SingleValueHeader): """ Content-Type, RFC 2616 section 14.17 Unlike other headers, use the CGI variable instead. """ version = '1.0' _environ_name = 'CONTENT_TYPE' # common mimetype constants UNKNOWN = 'application/octet-stream' TEXT_PLAIN = 'text/plain' TEXT_HTML = 'text/html' TEXT_XML = 'text/xml' def compose(self, major=None, minor=None, charset=None): if not major: if minor in ('plain', 'html', 'xml'): major = 'text' else: assert not minor and not charset return (self.UNKNOWN,) if not minor: minor = "*" result = "%s/%s" % (major, minor) if charset: result += "; charset=%s" % charset return (result,) _ContentType('Content-Type', 'entity', 'RFC 2616, 14.17') class _ContentLength(_SingleValueHeader): """ Content-Length, RFC 2616 section 14.13 Unlike other headers, use the CGI variable instead. """ version = "1.0" _environ_name = 'CONTENT_LENGTH' _ContentLength('Content-Length', 'entity', 'RFC 2616, 14.13') class _ContentDisposition(_SingleValueHeader): """ Content-Disposition, RFC 2183 (use ``CONTENT_DISPOSITION``) This header can be constructed (using keyword arguments), by first specifying one of the following mechanisms: ``attachment`` if True, this specifies that the content should not be shown in the browser and should be handled externally, even if the browser could render the content ``inline`` exclusive with attachment; indicates that the content should be rendered in the browser if possible, but otherwise it should be handled externally Only one of the above 2 may be True. If both are None, then the disposition is assumed to be an ``attachment``. These are distinct fields since support for field enumeration may be added in the future. ``filename`` the filename parameter, if any, to be reported; if this is None, then the current object's filename attribute is used The usage of ``apply()`` on this header has side-effects. If filename is provided, and Content-Type is not set or is 'application/octet-stream', then the mimetypes.guess is used to upgrade the Content-Type setting. """ def _compose(self, attachment=None, inline=None, filename=None): result = [] if inline is True: assert not attachment result.append('inline') else: assert not inline result.append('attachment') if filename: assert '"' not in filename filename = filename.split("/")[-1] filename = filename.split("\\")[-1] result.append('filename="%s"' % filename) return (("; ".join(result),), filename) def compose(self, **kwargs): (result, mimetype) = self._compose(**kwargs) return result def apply(self, collection, **kwargs): """ return the new Content-Type side-effect value """ (result, filename) = self._compose(**kwargs) mimetype = CONTENT_TYPE(collection) if filename and (not mimetype or CONTENT_TYPE.UNKNOWN == mimetype): mimetype, _ = mimetypes.guess_type(filename) if mimetype and CONTENT_TYPE.UNKNOWN != mimetype: CONTENT_TYPE.update(collection, mimetype) self.update(collection, *result) return mimetype _ContentDisposition('Content-Disposition', 'entity', 'RFC 2183') class _IfModifiedSince(_DateHeader): """ If-Modified-Since, RFC 2616 section 14.25 """ version = '1.0' def __call__(self, *args, **kwargs): """ Split the value on ';' incase the header includes extra attributes. E.g. IE 6 is known to send: If-Modified-Since: Sun, 25 Jun 2006 20:36:35 GMT; length=1506 """ return _DateHeader.__call__(self, *args, **kwargs).split(';', 1)[0] def parse(self, *args, **kwargs): value = _DateHeader.parse(self, *args, **kwargs) if value and value > now(): raise HTTPBadRequest(( "Please check your system clock.\r\n" "According to this server, the time provided in the\r\n" "%s header is in the future.\r\n") % self.name) return value _IfModifiedSince('If-Modified-Since', 'request', 'RFC 2616, 14.25') class _Range(_MultiValueHeader): """ Range, RFC 2616 14.35 (use ``RANGE``) According to section 14.16, the response to this message should be a 206 Partial Content and that if multiple non-overlapping byte ranges are requested (it is an error to request multiple overlapping ranges) the result should be sent as multipart/byteranges mimetype. The server should respond with '416 Requested Range Not Satisfiable' if the requested ranges are out-of-bounds. The specification also indicates that a syntax error in the Range request should result in the header being ignored rather than a '400 Bad Request'. """ def parse(self, *args, **kwargs): """ Returns a tuple (units, list), where list is a sequence of (begin, end) tuples; and end is None if it was not provided. """ value = self.__call__(*args, **kwargs) if not value: return None ranges = [] last_end = -1 try: (units, range) = value.split("=", 1) units = units.strip().lower() for item in range.split(","): (begin, end) = item.split("-") if not begin.strip(): begin = 0 else: begin = int(begin) if begin <= last_end: raise ValueError() if not end.strip(): end = None else: end = int(end) last_end = end ranges.append((begin, end)) except ValueError: # In this case where the Range header is malformed, # section 14.16 says to treat the request as if the # Range header was not present. How do I log this? return None return (units, ranges) _Range('Range', 'request', 'RFC 2616, 14.35') class _AcceptLanguage(_MultiValueHeader): """ Accept-Language, RFC 2616 section 14.4 """ def parse(self, *args, **kwargs): """ Return a list of language tags sorted by their "q" values. For example, "en-us,en;q=0.5" should return ``["en-us", "en"]``. If there is no ``Accept-Language`` header present, default to ``[]``. """ header = self.__call__(*args, **kwargs) if header is None: return [] langs = [v for v in header.split(",") if v] qs = [] for lang in langs: pieces = lang.split(";") lang, params = pieces[0].strip().lower(), pieces[1:] q = 1 for param in params: if '=' not in param: # Malformed request; probably a bot, we'll ignore continue lvalue, rvalue = param.split("=") lvalue = lvalue.strip().lower() rvalue = rvalue.strip() if lvalue == "q": q = float(rvalue) qs.append((lang, q)) qs.sort(lambda a, b: -cmp(a[1], b[1])) return [lang for (lang, q) in qs] _AcceptLanguage('Accept-Language', 'request', 'RFC 2616, 14.4') class _AcceptRanges(_MultiValueHeader): """ Accept-Ranges, RFC 2616 section 14.5 """ def compose(self, none=None, bytes=None): if bytes: return ('bytes',) return ('none',) _AcceptRanges('Accept-Ranges', 'response', 'RFC 2616, 14.5') class _ContentRange(_SingleValueHeader): """ Content-Range, RFC 2616 section 14.6 """ def compose(self, first_byte=None, last_byte=None, total_length=None): retval = "bytes %d-%d/%d" % (first_byte, last_byte, total_length) assert last_byte == -1 or first_byte <= last_byte assert last_byte < total_length return (retval,) _ContentRange('Content-Range', 'entity', 'RFC 2616, 14.6') class _Authorization(_SingleValueHeader): """ Authorization, RFC 2617 (RFC 2616, 14.8) """ def compose(self, digest=None, basic=None, username=None, password=None, challenge=None, path=None, method=None): assert username and password if basic or not challenge: assert not digest userpass = "%s:%s" % (username.strip(), password.strip()) return "Basic %s" % userpass.encode('base64').strip() assert challenge and not basic path = path or "/" (_, realm) = challenge.split('realm="') (realm, _) = realm.split('"', 1) auth = urllib2.AbstractDigestAuthHandler() auth.add_password(realm, path, username, password) (token, challenge) = challenge.split(' ', 1) chal = urllib2.parse_keqv_list(urllib2.parse_http_list(challenge)) class FakeRequest(object): def get_full_url(self): return path def has_data(self): return False def get_method(self): return method or "GET" get_selector = get_full_url retval = "Digest %s" % auth.get_authorization(FakeRequest(), chal) return (retval,) _Authorization('Authorization', 'request', 'RFC 2617') # # For now, construct a minimalistic version of the field-names; at a # later date more complicated headers may sprout content constructors. # The items commented out have concrete variants. # for (name, category, version, style, comment) in \ (("Accept" ,'request' ,'1.1','multi-value','RFC 2616, 14.1' ) ,("Accept-Charset" ,'request' ,'1.1','multi-value','RFC 2616, 14.2' ) ,("Accept-Encoding" ,'request' ,'1.1','multi-value','RFC 2616, 14.3' ) #,("Accept-Language" ,'request' ,'1.1','multi-value','RFC 2616, 14.4' ) #,("Accept-Ranges" ,'response','1.1','multi-value','RFC 2616, 14.5' ) ,("Age" ,'response','1.1','singular' ,'RFC 2616, 14.6' ) ,("Allow" ,'entity' ,'1.0','multi-value','RFC 2616, 14.7' ) #,("Authorization" ,'request' ,'1.0','singular' ,'RFC 2616, 14.8' ) #,("Cache-Control" ,'general' ,'1.1','multi-value','RFC 2616, 14.9' ) ,("Cookie" ,'request' ,'1.0','multi-value','RFC 2109/Netscape') ,("Connection" ,'general' ,'1.1','multi-value','RFC 2616, 14.10') ,("Content-Encoding" ,'entity' ,'1.0','multi-value','RFC 2616, 14.11') #,("Content-Disposition",'entity' ,'1.1','multi-value','RFC 2616, 15.5' ) ,("Content-Language" ,'entity' ,'1.1','multi-value','RFC 2616, 14.12') #,("Content-Length" ,'entity' ,'1.0','singular' ,'RFC 2616, 14.13') ,("Content-Location" ,'entity' ,'1.1','singular' ,'RFC 2616, 14.14') ,("Content-MD5" ,'entity' ,'1.1','singular' ,'RFC 2616, 14.15') #,("Content-Range" ,'entity' ,'1.1','singular' ,'RFC 2616, 14.16') #,("Content-Type" ,'entity' ,'1.0','singular' ,'RFC 2616, 14.17') ,("Date" ,'general' ,'1.0','date-header','RFC 2616, 14.18') ,("ETag" ,'response','1.1','singular' ,'RFC 2616, 14.19') ,("Expect" ,'request' ,'1.1','multi-value','RFC 2616, 14.20') ,("Expires" ,'entity' ,'1.0','date-header','RFC 2616, 14.21') ,("From" ,'request' ,'1.0','singular' ,'RFC 2616, 14.22') ,("Host" ,'request' ,'1.1','singular' ,'RFC 2616, 14.23') ,("If-Match" ,'request' ,'1.1','multi-value','RFC 2616, 14.24') #,("If-Modified-Since" ,'request' ,'1.0','date-header','RFC 2616, 14.25') ,("If-None-Match" ,'request' ,'1.1','multi-value','RFC 2616, 14.26') ,("If-Range" ,'request' ,'1.1','singular' ,'RFC 2616, 14.27') ,("If-Unmodified-Since",'request' ,'1.1','date-header' ,'RFC 2616, 14.28') ,("Last-Modified" ,'entity' ,'1.0','date-header','RFC 2616, 14.29') ,("Location" ,'response','1.0','singular' ,'RFC 2616, 14.30') ,("Max-Forwards" ,'request' ,'1.1','singular' ,'RFC 2616, 14.31') ,("Pragma" ,'general' ,'1.0','multi-value','RFC 2616, 14.32') ,("Proxy-Authenticate" ,'response','1.1','multi-value','RFC 2616, 14.33') ,("Proxy-Authorization",'request' ,'1.1','singular' ,'RFC 2616, 14.34') #,("Range" ,'request' ,'1.1','multi-value','RFC 2616, 14.35') ,("Referer" ,'request' ,'1.0','singular' ,'RFC 2616, 14.36') ,("Retry-After" ,'response','1.1','singular' ,'RFC 2616, 14.37') ,("Server" ,'response','1.0','singular' ,'RFC 2616, 14.38') ,("Set-Cookie" ,'response','1.0','multi-entry','RFC 2109/Netscape') ,("TE" ,'request' ,'1.1','multi-value','RFC 2616, 14.39') ,("Trailer" ,'general' ,'1.1','multi-value','RFC 2616, 14.40') ,("Transfer-Encoding" ,'general' ,'1.1','multi-value','RFC 2616, 14.41') ,("Upgrade" ,'general' ,'1.1','multi-value','RFC 2616, 14.42') ,("User-Agent" ,'request' ,'1.0','singular' ,'RFC 2616, 14.43') ,("Vary" ,'response','1.1','multi-value','RFC 2616, 14.44') ,("Via" ,'general' ,'1.1','multi-value','RFC 2616, 14.45') ,("Warning" ,'general' ,'1.1','multi-entry','RFC 2616, 14.46') ,("WWW-Authenticate" ,'response','1.0','multi-entry','RFC 2616, 14.47')): klass = {'multi-value': _MultiValueHeader, 'multi-entry': _MultiEntryHeader, 'date-header': _DateHeader, 'singular' : _SingleValueHeader}[style] klass(name, category, comment, version).__doc__ = comment del klass for head in _headers.values(): headname = head.name.replace("-","_").upper() locals()[headname] = head __all__.append(headname) __pudge_all__ = __all__[:] for _name, _obj in globals().items(): if isinstance(_obj, type) and issubclass(_obj, HTTPHeader): __pudge_all__.append(_name) Paste-1.7.5.1/paste/recursive.py0000664000175000017500000003451011437647620015057 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Middleware to make internal requests and forward requests internally. When applied, several keys are added to the environment that will allow you to trigger recursive redirects and forwards. paste.recursive.include: When you call ``environ['paste.recursive.include'](new_path_info)`` a response will be returned. The response has a ``body`` attribute, a ``status`` attribute, and a ``headers`` attribute. paste.recursive.script_name: The ``SCRIPT_NAME`` at the point that recursive lives. Only paths underneath this path can be redirected to. paste.recursive.old_path_info: A list of previous ``PATH_INFO`` values from previous redirects. Raise ``ForwardRequestException(new_path_info)`` to do a forward (aborting the current request). """ from cStringIO import StringIO import warnings __all__ = ['RecursiveMiddleware'] __pudge_all__ = ['RecursiveMiddleware', 'ForwardRequestException'] class RecursionLoop(AssertionError): # Subclasses AssertionError for legacy reasons """Raised when a recursion enters into a loop""" class CheckForRecursionMiddleware(object): def __init__(self, app, env): self.app = app self.env = env def __call__(self, environ, start_response): path_info = environ.get('PATH_INFO','') if path_info in self.env.get( 'paste.recursive.old_path_info', []): raise RecursionLoop( "Forwarding loop detected; %r visited twice (internal " "redirect path: %s)" % (path_info, self.env['paste.recursive.old_path_info'])) old_path_info = self.env.setdefault('paste.recursive.old_path_info', []) old_path_info.append(self.env.get('PATH_INFO', '')) return self.app(environ, start_response) class RecursiveMiddleware(object): """ A WSGI middleware that allows for recursive and forwarded calls. All these calls go to the same 'application', but presumably that application acts differently with different URLs. The forwarded URLs must be relative to this container. Interface is entirely through the ``paste.recursive.forward`` and ``paste.recursive.include`` environmental keys. """ def __init__(self, application, global_conf=None): self.application = application def __call__(self, environ, start_response): environ['paste.recursive.forward'] = Forwarder( self.application, environ, start_response) environ['paste.recursive.include'] = Includer( self.application, environ, start_response) environ['paste.recursive.include_app_iter'] = IncluderAppIter( self.application, environ, start_response) my_script_name = environ.get('SCRIPT_NAME', '') environ['paste.recursive.script_name'] = my_script_name try: return self.application(environ, start_response) except ForwardRequestException, e: middleware = CheckForRecursionMiddleware( e.factory(self), environ) return middleware(environ, start_response) class ForwardRequestException(Exception): """ Used to signal that a request should be forwarded to a different location. ``url`` The URL to forward to starting with a ``/`` and relative to ``RecursiveMiddleware``. URL fragments can also contain query strings so ``/error?code=404`` would be a valid URL fragment. ``environ`` An altertative WSGI environment dictionary to use for the forwarded request. If specified is used *instead* of the ``url_fragment`` ``factory`` If specifed ``factory`` is used instead of ``url`` or ``environ``. ``factory`` is a callable that takes a WSGI application object as the first argument and returns an initialised WSGI middleware which can alter the forwarded response. Basic usage (must have ``RecursiveMiddleware`` present) : .. code-block:: python from paste.recursive import ForwardRequestException def app(environ, start_response): if environ['PATH_INFO'] == '/hello': start_response("200 OK", [('Content-type', 'text/plain')]) return ['Hello World!'] elif environ['PATH_INFO'] == '/error': start_response("404 Not Found", [('Content-type', 'text/plain')]) return ['Page not found'] else: raise ForwardRequestException('/error') from paste.recursive import RecursiveMiddleware app = RecursiveMiddleware(app) If you ran this application and visited ``/hello`` you would get a ``Hello World!`` message. If you ran the application and visited ``/not_found`` a ``ForwardRequestException`` would be raised and the caught by the ``RecursiveMiddleware``. The ``RecursiveMiddleware`` would then return the headers and response from the ``/error`` URL but would display a ``404 Not found`` status message. You could also specify an ``environ`` dictionary instead of a url. Using the same example as before: .. code-block:: python def app(environ, start_response): ... same as previous example ... else: new_environ = environ.copy() new_environ['PATH_INFO'] = '/error' raise ForwardRequestException(environ=new_environ) Finally, if you want complete control over every aspect of the forward you can specify a middleware factory. For example to keep the old status code but use the headers and resposne body from the forwarded response you might do this: .. code-block:: python from paste.recursive import ForwardRequestException from paste.recursive import RecursiveMiddleware from paste.errordocument import StatusKeeper def app(environ, start_response): if environ['PATH_INFO'] == '/hello': start_response("200 OK", [('Content-type', 'text/plain')]) return ['Hello World!'] elif environ['PATH_INFO'] == '/error': start_response("404 Not Found", [('Content-type', 'text/plain')]) return ['Page not found'] else: def factory(app): return StatusKeeper(app, status='404 Not Found', url='/error') raise ForwardRequestException(factory=factory) app = RecursiveMiddleware(app) """ def __init__( self, url=None, environ={}, factory=None, path_info=None): # Check no incompatible options have been chosen if factory and url: raise TypeError( 'You cannot specify factory and a url in ' 'ForwardRequestException') elif factory and environ: raise TypeError( 'You cannot specify factory and environ in ' 'ForwardRequestException') if url and environ: raise TypeError( 'You cannot specify environ and url in ' 'ForwardRequestException') # set the path_info or warn about its use. if path_info: if not url: warnings.warn( "ForwardRequestException(path_info=...) has been deprecated; please " "use ForwardRequestException(url=...)", DeprecationWarning, 2) else: raise TypeError('You cannot use url and path_info in ForwardRequestException') self.path_info = path_info # If the url can be treated as a path_info do that if url and not '?' in str(url): self.path_info = url # Base middleware class ForwardRequestExceptionMiddleware(object): def __init__(self, app): self.app = app # Otherwise construct the appropriate middleware factory if hasattr(self, 'path_info'): p = self.path_info def factory_(app): class PathInfoForward(ForwardRequestExceptionMiddleware): def __call__(self, environ, start_response): environ['PATH_INFO'] = p return self.app(environ, start_response) return PathInfoForward(app) self.factory = factory_ elif url: def factory_(app): class URLForward(ForwardRequestExceptionMiddleware): def __call__(self, environ, start_response): environ['PATH_INFO'] = url.split('?')[0] environ['QUERY_STRING'] = url.split('?')[1] return self.app(environ, start_response) return URLForward(app) self.factory = factory_ elif environ: def factory_(app): class EnvironForward(ForwardRequestExceptionMiddleware): def __call__(self, environ_, start_response): return self.app(environ, start_response) return EnvironForward(app) self.factory = factory_ else: self.factory = factory class Recursive(object): def __init__(self, application, environ, start_response): self.application = application self.original_environ = environ.copy() self.previous_environ = environ self.start_response = start_response def __call__(self, path, extra_environ=None): """ `extra_environ` is an optional dictionary that is also added to the forwarded request. E.g., ``{'HTTP_HOST': 'new.host'}`` could be used to forward to a different virtual host. """ environ = self.original_environ.copy() if extra_environ: environ.update(extra_environ) environ['paste.recursive.previous_environ'] = self.previous_environ base_path = self.original_environ.get('SCRIPT_NAME') if path.startswith('/'): assert path.startswith(base_path), ( "You can only forward requests to resources under the " "path %r (not %r)" % (base_path, path)) path = path[len(base_path)+1:] assert not path.startswith('/') path_info = '/' + path environ['PATH_INFO'] = path_info environ['REQUEST_METHOD'] = 'GET' environ['CONTENT_LENGTH'] = '0' environ['CONTENT_TYPE'] = '' environ['wsgi.input'] = StringIO('') return self.activate(environ) def activate(self, environ): raise NotImplementedError def __repr__(self): return '<%s.%s from %s>' % ( self.__class__.__module__, self.__class__.__name__, self.original_environ.get('SCRIPT_NAME') or '/') class Forwarder(Recursive): """ The forwarder will try to restart the request, except with the new `path` (replacing ``PATH_INFO`` in the request). It must not be called after and headers have been returned. It returns an iterator that must be returned back up the call stack, so it must be used like: .. code-block:: python return environ['paste.recursive.forward'](path) Meaningful transformations cannot be done, since headers are sent directly to the server and cannot be inspected or rewritten. """ def activate(self, environ): warnings.warn( "recursive.Forwarder has been deprecated; please use " "ForwardRequestException", DeprecationWarning, 2) return self.application(environ, self.start_response) class Includer(Recursive): """ Starts another request with the given path and adding or overwriting any values in the `extra_environ` dictionary. Returns an IncludeResponse object. """ def activate(self, environ): response = IncludedResponse() def start_response(status, headers, exc_info=None): if exc_info: raise exc_info[0], exc_info[1], exc_info[2] response.status = status response.headers = headers return response.write app_iter = self.application(environ, start_response) try: for s in app_iter: response.write(s) finally: if hasattr(app_iter, 'close'): app_iter.close() response.close() return response class IncludedResponse(object): def __init__(self): self.headers = None self.status = None self.output = StringIO() self.str = None def close(self): self.str = self.output.getvalue() self.output.close() self.output = None def write(self, s): assert self.output is not None, ( "This response has already been closed and no further data " "can be written.") self.output.write(s) def __str__(self): return self.body def body__get(self): if self.str is None: return self.output.getvalue() else: return self.str body = property(body__get) class IncluderAppIter(Recursive): """ Like Includer, but just stores the app_iter response (be sure to call close on the response!) """ def activate(self, environ): response = IncludedAppIterResponse() def start_response(status, headers, exc_info=None): if exc_info: raise exc_info[0], exc_info[1], exc_info[2] response.status = status response.headers = headers return response.write app_iter = self.application(environ, start_response) response.app_iter = app_iter return response class IncludedAppIterResponse(object): def __init__(self): self.status = None self.headers = None self.accumulated = [] self.app_iter = None self._closed = False def close(self): assert not self._closed, ( "Tried to close twice") if hasattr(self.app_iter, 'close'): self.app_iter.close() def write(self, s): self.accumulated.append def make_recursive_middleware(app, global_conf): return RecursiveMiddleware(app) make_recursive_middleware.__doc__ = __doc__ Paste-1.7.5.1/paste/exceptions/0000775000175000017500000000000011354703364014650 5ustar ianbianbPaste-1.7.5.1/paste/exceptions/reporter.py0000664000175000017500000001073611354703364017073 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php from email.MIMEText import MIMEText from email.MIMEMultipart import MIMEMultipart import smtplib import time try: from socket import sslerror except ImportError: sslerror = None from paste.exceptions import formatter class Reporter(object): def __init__(self, **conf): for name, value in conf.items(): if not hasattr(self, name): raise TypeError( "The keyword argument %s was not expected" % name) setattr(self, name, value) self.check_params() def check_params(self): pass def format_date(self, exc_data): return time.strftime('%c', exc_data.date) def format_html(self, exc_data, **kw): return formatter.format_html(exc_data, **kw) def format_text(self, exc_data, **kw): return formatter.format_text(exc_data, **kw) class EmailReporter(Reporter): to_addresses = None from_address = None smtp_server = 'localhost' smtp_username = None smtp_password = None smtp_use_tls = False subject_prefix = '' def report(self, exc_data): msg = self.assemble_email(exc_data) server = smtplib.SMTP(self.smtp_server) if self.smtp_use_tls: server.ehlo() server.starttls() server.ehlo() if self.smtp_username and self.smtp_password: server.login(self.smtp_username, self.smtp_password) server.sendmail(self.from_address, self.to_addresses, msg.as_string()) try: server.quit() except sslerror: # sslerror is raised in tls connections on closing sometimes pass def check_params(self): if not self.to_addresses: raise ValueError("You must set to_addresses") if not self.from_address: raise ValueError("You must set from_address") if isinstance(self.to_addresses, (str, unicode)): self.to_addresses = [self.to_addresses] def assemble_email(self, exc_data): short_html_version = self.format_html( exc_data, show_hidden_frames=False) long_html_version = self.format_html( exc_data, show_hidden_frames=True) text_version = self.format_text( exc_data, show_hidden_frames=False) msg = MIMEMultipart() msg.set_type('multipart/alternative') msg.preamble = msg.epilogue = '' text_msg = MIMEText(text_version) text_msg.set_type('text/plain') text_msg.set_param('charset', 'ASCII') msg.attach(text_msg) html_msg = MIMEText(short_html_version) html_msg.set_type('text/html') # @@: Correct character set? html_msg.set_param('charset', 'UTF-8') html_long = MIMEText(long_html_version) html_long.set_type('text/html') html_long.set_param('charset', 'UTF-8') msg.attach(html_msg) msg.attach(html_long) subject = '%s: %s' % (exc_data.exception_type, formatter.truncate(str(exc_data.exception_value))) msg['Subject'] = self.subject_prefix + subject msg['From'] = self.from_address msg['To'] = ', '.join(self.to_addresses) return msg class LogReporter(Reporter): filename = None show_hidden_frames = True def check_params(self): assert self.filename is not None, ( "You must give a filename") def report(self, exc_data): text = self.format_text( exc_data, show_hidden_frames=self.show_hidden_frames) f = open(self.filename, 'a') try: f.write(text + '\n' + '-'*60 + '\n') finally: f.close() class FileReporter(Reporter): file = None show_hidden_frames = True def check_params(self): assert self.file is not None, ( "You must give a file object") def report(self, exc_data): text = self.format_text( exc_data, show_hidden_frames=self.show_hidden_frames) self.file.write(text + '\n' + '-'*60 + '\n') class WSGIAppReporter(Reporter): def __init__(self, exc_data): self.exc_data = exc_data def __call__(self, environ, start_response): start_response('500 Server Error', [('Content-type', 'text/html')]) return [formatter.format_html(self.exc_data)] Paste-1.7.5.1/paste/exceptions/collector.py0000664000175000017500000004650211354703364017217 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php ############################################################################## # # Copyright (c) 2001, 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## ## Originally zExceptions.ExceptionFormatter from Zope; ## Modified by Ian Bicking, Imaginary Landscape, 2005 """ An exception collector that finds traceback information plus supplements """ import sys import traceback import time try: from cStringIO import StringIO except ImportError: from StringIO import StringIO import linecache from paste.exceptions import serial_number_generator import warnings DEBUG_EXCEPTION_FORMATTER = True DEBUG_IDENT_PREFIX = 'E-' FALLBACK_ENCODING = 'UTF-8' __all__ = ['collect_exception', 'ExceptionCollector'] class ExceptionCollector(object): """ Produces a data structure that can be used by formatters to display exception reports. Magic variables: If you define one of these variables in your local scope, you can add information to tracebacks that happen in that context. This allows applications to add all sorts of extra information about the context of the error, including URLs, environmental variables, users, hostnames, etc. These are the variables we look for: ``__traceback_supplement__``: You can define this locally or globally (unlike all the other variables, which must be defined locally). ``__traceback_supplement__`` is a tuple of ``(factory, arg1, arg2...)``. When there is an exception, ``factory(arg1, arg2, ...)`` is called, and the resulting object is inspected for supplemental information. ``__traceback_info__``: This information is added to the traceback, usually fairly literally. ``__traceback_hide__``: If set and true, this indicates that the frame should be hidden from abbreviated tracebacks. This way you can hide some of the complexity of the larger framework and let the user focus on their own errors. By setting it to ``'before'``, all frames before this one will be thrown away. By setting it to ``'after'`` then all frames after this will be thrown away until ``'reset'`` is found. In each case the frame where it is set is included, unless you append ``'_and_this'`` to the value (e.g., ``'before_and_this'``). Note that formatters will ignore this entirely if the frame that contains the error wouldn't normally be shown according to these rules. ``__traceback_reporter__``: This should be a reporter object (see the reporter module), or a list/tuple of reporter objects. All reporters found this way will be given the exception, innermost first. ``__traceback_decorator__``: This object (defined in a local or global scope) will get the result of this function (the CollectedException defined below). It may modify this object in place, or return an entirely new object. This gives the object the ability to manipulate the traceback arbitrarily. The actually interpretation of these values is largely up to the reporters and formatters. ``collect_exception(*sys.exc_info())`` will return an object with several attributes: ``frames``: A list of frames ``exception_formatted``: The formatted exception, generally a full traceback ``exception_type``: The type of the exception, like ``ValueError`` ``exception_value``: The string value of the exception, like ``'x not in list'`` ``identification_code``: A hash of the exception data meant to identify the general exception, so that it shares this code with other exceptions that derive from the same problem. The code is a hash of all the module names and function names in the traceback, plus exception_type. This should be shown to users so they can refer to the exception later. (@@: should it include a portion that allows identification of the specific instance of the exception as well?) The list of frames goes innermost first. Each frame has these attributes; some values may be None if they could not be determined. ``modname``: the name of the module ``filename``: the filename of the module ``lineno``: the line of the error ``revision``: the contents of __version__ or __revision__ ``name``: the function name ``supplement``: an object created from ``__traceback_supplement__`` ``supplement_exception``: a simple traceback of any exception ``__traceback_supplement__`` created ``traceback_info``: the str() of any ``__traceback_info__`` variable found in the local scope (@@: should it str()-ify it or not?) ``traceback_hide``: the value of any ``__traceback_hide__`` variable ``traceback_log``: the value of any ``__traceback_log__`` variable ``__traceback_supplement__`` is thrown away, but a fixed set of attributes are captured; each of these attributes is optional. ``object``: the name of the object being visited ``source_url``: the original URL requested ``line``: the line of source being executed (for interpreters, like ZPT) ``column``: the column of source being executed ``expression``: the expression being evaluated (also for interpreters) ``warnings``: a list of (string) warnings to be displayed ``getInfo``: a function/method that takes no arguments, and returns a string describing any extra information ``extraData``: a function/method that takes no arguments, and returns a dictionary. The contents of this dictionary will not be displayed in the context of the traceback, but globally for the exception. Results will be grouped by the keys in the dictionaries (which also serve as titles). The keys can also be tuples of (importance, title); in this case the importance should be ``important`` (shows up at top), ``normal`` (shows up somewhere; unspecified), ``supplemental`` (shows up at bottom), or ``extra`` (shows up hidden or not at all). These are used to create an object with attributes of the same names (``getInfo`` becomes a string attribute, not a method). ``__traceback_supplement__`` implementations should be careful to produce values that are relatively static and unlikely to cause further errors in the reporting system -- any complex introspection should go in ``getInfo()`` and should ultimately return a string. Note that all attributes are optional, and under certain circumstances may be None or may not exist at all -- the collector can only do a best effort, but must avoid creating any exceptions itself. Formatters may want to use ``__traceback_hide__`` as a hint to hide frames that are part of the 'framework' or underlying system. There are a variety of rules about special values for this variables that formatters should be aware of. TODO: More attributes in __traceback_supplement__? Maybe an attribute that gives a list of local variables that should also be collected? Also, attributes that would be explicitly meant for the entire request, not just a single frame. Right now some of the fixed set of attributes (e.g., source_url) are meant for this use, but there's no explicit way for the supplement to indicate new values, e.g., logged-in user, HTTP referrer, environment, etc. Also, the attributes that do exist are Zope/Web oriented. More information on frames? cgitb, for instance, produces extensive information on local variables. There exists the possibility that getting this information may cause side effects, which can make debugging more difficult; but it also provides fodder for post-mortem debugging. However, the collector is not meant to be configurable, but to capture everything it can and let the formatters be configurable. Maybe this would have to be a configuration value, or maybe it could be indicated by another magical variable (which would probably mean 'show all local variables below this frame') """ show_revisions = 0 def __init__(self, limit=None): self.limit = limit def getLimit(self): limit = self.limit if limit is None: limit = getattr(sys, 'tracebacklimit', None) return limit def getRevision(self, globals): if not self.show_revisions: return None revision = globals.get('__revision__', None) if revision is None: # Incorrect but commonly used spelling revision = globals.get('__version__', None) if revision is not None: try: revision = str(revision).strip() except: revision = '???' return revision def collectSupplement(self, supplement, tb): result = {} for name in ('object', 'source_url', 'line', 'column', 'expression', 'warnings'): result[name] = getattr(supplement, name, None) func = getattr(supplement, 'getInfo', None) if func: result['info'] = func() else: result['info'] = None func = getattr(supplement, 'extraData', None) if func: result['extra'] = func() else: result['extra'] = None return SupplementaryData(**result) def collectLine(self, tb, extra_data): f = tb.tb_frame lineno = tb.tb_lineno co = f.f_code filename = co.co_filename name = co.co_name globals = f.f_globals locals = f.f_locals if not hasattr(locals, 'has_key'): # Something weird about this frame; it's not a real dict warnings.warn( "Frame %s has an invalid locals(): %r" % ( globals.get('__name__', 'unknown'), locals)) locals = {} data = {} data['modname'] = globals.get('__name__', None) data['filename'] = filename data['lineno'] = lineno data['revision'] = self.getRevision(globals) data['name'] = name data['tbid'] = id(tb) # Output a traceback supplement, if any. if locals.has_key('__traceback_supplement__'): # Use the supplement defined in the function. tbs = locals['__traceback_supplement__'] elif globals.has_key('__traceback_supplement__'): # Use the supplement defined in the module. # This is used by Scripts (Python). tbs = globals['__traceback_supplement__'] else: tbs = None if tbs is not None: factory = tbs[0] args = tbs[1:] try: supp = factory(*args) data['supplement'] = self.collectSupplement(supp, tb) if data['supplement'].extra: for key, value in data['supplement'].extra.items(): extra_data.setdefault(key, []).append(value) except: if DEBUG_EXCEPTION_FORMATTER: out = StringIO() traceback.print_exc(file=out) text = out.getvalue() data['supplement_exception'] = text # else just swallow the exception. try: tbi = locals.get('__traceback_info__', None) if tbi is not None: data['traceback_info'] = str(tbi) except: pass marker = [] for name in ('__traceback_hide__', '__traceback_log__', '__traceback_decorator__'): try: tbh = locals.get(name, globals.get(name, marker)) if tbh is not marker: data[name[2:-2]] = tbh except: pass return data def collectExceptionOnly(self, etype, value): return traceback.format_exception_only(etype, value) def collectException(self, etype, value, tb, limit=None): # The next line provides a way to detect recursion. __exception_formatter__ = 1 frames = [] ident_data = [] traceback_decorators = [] if limit is None: limit = self.getLimit() n = 0 extra_data = {} while tb is not None and (limit is None or n < limit): if tb.tb_frame.f_locals.get('__exception_formatter__'): # Stop recursion. @@: should make a fake ExceptionFrame frames.append('(Recursive formatException() stopped)\n') break data = self.collectLine(tb, extra_data) frame = ExceptionFrame(**data) frames.append(frame) if frame.traceback_decorator is not None: traceback_decorators.append(frame.traceback_decorator) ident_data.append(frame.modname or '?') ident_data.append(frame.name or '?') tb = tb.tb_next n = n + 1 ident_data.append(str(etype)) ident = serial_number_generator.hash_identifier( ' '.join(ident_data), length=5, upper=True, prefix=DEBUG_IDENT_PREFIX) result = CollectedException( frames=frames, exception_formatted=self.collectExceptionOnly(etype, value), exception_type=etype, exception_value=self.safeStr(value), identification_code=ident, date=time.localtime(), extra_data=extra_data) if etype is ImportError: extra_data[('important', 'sys.path')] = [sys.path] for decorator in traceback_decorators: try: new_result = decorator(result) if new_result is not None: result = new_result except: pass return result def safeStr(self, obj): try: return str(obj) except UnicodeEncodeError: try: return unicode(obj).encode(FALLBACK_ENCODING, 'replace') except UnicodeEncodeError: # This is when something is really messed up, but this can # happen when the __str__ of an object has to handle unicode return repr(obj) limit = 200 class Bunch(object): """ A generic container """ def __init__(self, **attrs): for name, value in attrs.items(): setattr(self, name, value) def __repr__(self): name = '<%s ' % self.__class__.__name__ name += ' '.join(['%s=%r' % (name, str(value)[:30]) for name, value in self.__dict__.items() if not name.startswith('_')]) return name + '>' class CollectedException(Bunch): """ This is the result of collection the exception; it contains copies of data of interest. """ # A list of frames (ExceptionFrame instances), innermost last: frames = [] # The result of traceback.format_exception_only; this looks # like a normal traceback you'd see in the interactive interpreter exception_formatted = None # The *string* representation of the type of the exception # (@@: should we give the # actual class? -- we can't keep the # actual exception around, but the class should be safe) # Something like 'ValueError' exception_type = None # The string representation of the exception, from ``str(e)``. exception_value = None # An identifier which should more-or-less classify this particular # exception, including where in the code it happened. identification_code = None # The date, as time.localtime() returns: date = None # A dictionary of supplemental data: extra_data = {} class SupplementaryData(Bunch): """ The result of __traceback_supplement__. We don't keep the supplement object around, for fear of GC problems and whatnot. (@@: Maybe I'm being too superstitious about copying only specific information over) """ # These attributes are copied from the object, or left as None # if the object doesn't have these attributes: object = None source_url = None line = None column = None expression = None warnings = None # This is the *return value* of supplement.getInfo(): info = None class ExceptionFrame(Bunch): """ This represents one frame of the exception. Each frame is a context in the call stack, typically represented by a line number and module name in the traceback. """ # The name of the module; can be None, especially when the code # isn't associated with a module. modname = None # The filename (@@: when no filename, is it None or '?'?) filename = None # Line number lineno = None # The value of __revision__ or __version__ -- but only if # show_revision = True (by defaut it is false). (@@: Why not # collect this?) revision = None # The name of the function with the error (@@: None or '?' when # unknown?) name = None # A SupplementaryData object, if __traceback_supplement__ was found # (and produced no errors) supplement = None # If accessing __traceback_supplement__ causes any error, the # plain-text traceback is stored here supplement_exception = None # The str() of any __traceback_info__ value found traceback_info = None # The value of __traceback_hide__ traceback_hide = False # The value of __traceback_decorator__ traceback_decorator = None # The id() of the traceback scope, can be used to reference the # scope for use elsewhere tbid = None def get_source_line(self, context=0): """ Return the source of the current line of this frame. You probably want to .strip() it as well, as it is likely to have leading whitespace. If context is given, then that many lines on either side will also be returned. E.g., context=1 will give 3 lines. """ if not self.filename or not self.lineno: return None lines = [] for lineno in range(self.lineno-context, self.lineno+context+1): lines.append(linecache.getline(self.filename, lineno)) return ''.join(lines) if hasattr(sys, 'tracebacklimit'): limit = min(limit, sys.tracebacklimit) col = ExceptionCollector() def collect_exception(t, v, tb, limit=None): """ Collection an exception from ``sys.exc_info()``. Use like:: try: blah blah except: exc_data = collect_exception(*sys.exc_info()) """ return col.collectException(t, v, tb, limit=limit) Paste-1.7.5.1/paste/exceptions/errormiddleware.py0000664000175000017500000004102111354703364020407 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Error handler middleware """ import sys import traceback import cgi try: from cStringIO import StringIO except ImportError: from StringIO import StringIO from paste.exceptions import formatter, collector, reporter from paste import wsgilib from paste import request __all__ = ['ErrorMiddleware', 'handle_exception'] class _NoDefault(object): def __repr__(self): return '' NoDefault = _NoDefault() class ErrorMiddleware(object): """ Error handling middleware Usage:: error_catching_wsgi_app = ErrorMiddleware(wsgi_app) Settings: ``debug``: If true, then tracebacks will be shown in the browser. ``error_email``: an email address (or list of addresses) to send exception reports to ``error_log``: a filename to append tracebacks to ``show_exceptions_in_wsgi_errors``: If true, then errors will be printed to ``wsgi.errors`` (frequently a server error log, or stderr). ``from_address``, ``smtp_server``, ``error_subject_prefix``, ``smtp_username``, ``smtp_password``, ``smtp_use_tls``: variables to control the emailed exception reports ``error_message``: When debug mode is off, the error message to show to users. ``xmlhttp_key``: When this key (default ``_``) is in the request GET variables (not POST!), expect that this is an XMLHttpRequest, and the response should be more minimal; it should not be a complete HTML page. Environment Configuration: ``paste.throw_errors``: If this setting in the request environment is true, then this middleware is disabled. This can be useful in a testing situation where you don't want errors to be caught and transformed. ``paste.expected_exceptions``: When this middleware encounters an exception listed in this environment variable and when the ``start_response`` has not yet occurred, the exception will be re-raised instead of being caught. This should generally be set by middleware that may (but probably shouldn't be) installed above this middleware, and wants to get certain exceptions. Exceptions raised after ``start_response`` have been called are always caught since by definition they are no longer expected. """ def __init__(self, application, global_conf=None, debug=NoDefault, error_email=None, error_log=None, show_exceptions_in_wsgi_errors=NoDefault, from_address=None, smtp_server=None, smtp_username=None, smtp_password=None, smtp_use_tls=False, error_subject_prefix=None, error_message=None, xmlhttp_key=None): from paste.util import converters self.application = application # @@: global_conf should be handled elsewhere in a separate # function for the entry point if global_conf is None: global_conf = {} if debug is NoDefault: debug = converters.asbool(global_conf.get('debug')) if show_exceptions_in_wsgi_errors is NoDefault: show_exceptions_in_wsgi_errors = converters.asbool(global_conf.get('show_exceptions_in_wsgi_errors')) self.debug_mode = converters.asbool(debug) if error_email is None: error_email = (global_conf.get('error_email') or global_conf.get('admin_email') or global_conf.get('webmaster_email') or global_conf.get('sysadmin_email')) self.error_email = converters.aslist(error_email) self.error_log = error_log self.show_exceptions_in_wsgi_errors = show_exceptions_in_wsgi_errors if from_address is None: from_address = global_conf.get('error_from_address', 'errors@localhost') self.from_address = from_address if smtp_server is None: smtp_server = global_conf.get('smtp_server', 'localhost') self.smtp_server = smtp_server self.smtp_username = smtp_username or global_conf.get('smtp_username') self.smtp_password = smtp_password or global_conf.get('smtp_password') self.smtp_use_tls = smtp_use_tls or converters.asbool(global_conf.get('smtp_use_tls')) self.error_subject_prefix = error_subject_prefix or '' if error_message is None: error_message = global_conf.get('error_message') self.error_message = error_message if xmlhttp_key is None: xmlhttp_key = global_conf.get('xmlhttp_key', '_') self.xmlhttp_key = xmlhttp_key def __call__(self, environ, start_response): """ The WSGI application interface. """ # We want to be careful about not sending headers twice, # and the content type that the app has committed to (if there # is an exception in the iterator body of the response) if environ.get('paste.throw_errors'): return self.application(environ, start_response) environ['paste.throw_errors'] = True try: __traceback_supplement__ = Supplement, self, environ sr_checker = ResponseStartChecker(start_response) app_iter = self.application(environ, sr_checker) return self.make_catching_iter(app_iter, environ, sr_checker) except: exc_info = sys.exc_info() try: for expect in environ.get('paste.expected_exceptions', []): if isinstance(exc_info[1], expect): raise start_response('500 Internal Server Error', [('content-type', 'text/html')], exc_info) # @@: it would be nice to deal with bad content types here response = self.exception_handler(exc_info, environ) return [response] finally: # clean up locals... exc_info = None def make_catching_iter(self, app_iter, environ, sr_checker): if isinstance(app_iter, (list, tuple)): # These don't raise return app_iter return CatchingIter(app_iter, environ, sr_checker, self) def exception_handler(self, exc_info, environ): simple_html_error = False if self.xmlhttp_key: get_vars = wsgilib.parse_querystring(environ) if dict(get_vars).get(self.xmlhttp_key): simple_html_error = True return handle_exception( exc_info, environ['wsgi.errors'], html=True, debug_mode=self.debug_mode, error_email=self.error_email, error_log=self.error_log, show_exceptions_in_wsgi_errors=self.show_exceptions_in_wsgi_errors, error_email_from=self.from_address, smtp_server=self.smtp_server, smtp_username=self.smtp_username, smtp_password=self.smtp_password, smtp_use_tls=self.smtp_use_tls, error_subject_prefix=self.error_subject_prefix, error_message=self.error_message, simple_html_error=simple_html_error) class ResponseStartChecker(object): def __init__(self, start_response): self.start_response = start_response self.response_started = False def __call__(self, *args): self.response_started = True self.start_response(*args) class CatchingIter(object): """ A wrapper around the application iterator that will catch exceptions raised by the a generator, or by the close method, and display or report as necessary. """ def __init__(self, app_iter, environ, start_checker, error_middleware): self.app_iterable = app_iter self.app_iterator = iter(app_iter) self.environ = environ self.start_checker = start_checker self.error_middleware = error_middleware self.closed = False def __iter__(self): return self def next(self): __traceback_supplement__ = ( Supplement, self.error_middleware, self.environ) if self.closed: raise StopIteration try: return self.app_iterator.next() except StopIteration: self.closed = True close_response = self._close() if close_response is not None: return close_response else: raise StopIteration except: self.closed = True close_response = self._close() exc_info = sys.exc_info() response = self.error_middleware.exception_handler( exc_info, self.environ) if close_response is not None: response += ( '
Error in .close():
%s' % close_response) if not self.start_checker.response_started: self.start_checker('500 Internal Server Error', [('content-type', 'text/html')], exc_info) return response def close(self): # This should at least print something to stderr if the # close method fails at this point if not self.closed: self._close() def _close(self): """Close and return any error message""" if not hasattr(self.app_iterable, 'close'): return None try: self.app_iterable.close() return None except: close_response = self.error_middleware.exception_handler( sys.exc_info(), self.environ) return close_response class Supplement(object): """ This is a supplement used to display standard WSGI information in the traceback. """ def __init__(self, middleware, environ): self.middleware = middleware self.environ = environ self.source_url = request.construct_url(environ) def extraData(self): data = {} cgi_vars = data[('extra', 'CGI Variables')] = {} wsgi_vars = data[('extra', 'WSGI Variables')] = {} hide_vars = ['paste.config', 'wsgi.errors', 'wsgi.input', 'wsgi.multithread', 'wsgi.multiprocess', 'wsgi.run_once', 'wsgi.version', 'wsgi.url_scheme'] for name, value in self.environ.items(): if name.upper() == name: if value: cgi_vars[name] = value elif name not in hide_vars: wsgi_vars[name] = value if self.environ['wsgi.version'] != (1, 0): wsgi_vars['wsgi.version'] = self.environ['wsgi.version'] proc_desc = tuple([int(bool(self.environ[key])) for key in ('wsgi.multiprocess', 'wsgi.multithread', 'wsgi.run_once')]) wsgi_vars['wsgi process'] = self.process_combos[proc_desc] wsgi_vars['application'] = self.middleware.application if 'paste.config' in self.environ: data[('extra', 'Configuration')] = dict(self.environ['paste.config']) return data process_combos = { # multiprocess, multithread, run_once (0, 0, 0): 'Non-concurrent server', (0, 1, 0): 'Multithreaded', (1, 0, 0): 'Multiprocess', (1, 1, 0): 'Multi process AND threads (?)', (0, 0, 1): 'Non-concurrent CGI', (0, 1, 1): 'Multithread CGI (?)', (1, 0, 1): 'CGI', (1, 1, 1): 'Multi thread/process CGI (?)', } def handle_exception(exc_info, error_stream, html=True, debug_mode=False, error_email=None, error_log=None, show_exceptions_in_wsgi_errors=False, error_email_from='errors@localhost', smtp_server='localhost', smtp_username=None, smtp_password=None, smtp_use_tls=False, error_subject_prefix='', error_message=None, simple_html_error=False, ): """ For exception handling outside of a web context Use like:: import sys from paste.exceptions.errormiddleware import handle_exception try: do stuff except: handle_exception( sys.exc_info(), sys.stderr, html=False, ...other config...) If you want to report, but not fully catch the exception, call ``raise`` after ``handle_exception``, which (when given no argument) will reraise the exception. """ reported = False exc_data = collector.collect_exception(*exc_info) extra_data = '' if error_email: rep = reporter.EmailReporter( to_addresses=error_email, from_address=error_email_from, smtp_server=smtp_server, smtp_username=smtp_username, smtp_password=smtp_password, smtp_use_tls=smtp_use_tls, subject_prefix=error_subject_prefix) rep_err = send_report(rep, exc_data, html=html) if rep_err: extra_data += rep_err else: reported = True if error_log: rep = reporter.LogReporter( filename=error_log) rep_err = send_report(rep, exc_data, html=html) if rep_err: extra_data += rep_err else: reported = True if show_exceptions_in_wsgi_errors: rep = reporter.FileReporter( file=error_stream) rep_err = send_report(rep, exc_data, html=html) if rep_err: extra_data += rep_err else: reported = True else: error_stream.write('Error - %s: %s\n' % ( exc_data.exception_type, exc_data.exception_value)) if html: if debug_mode and simple_html_error: return_error = formatter.format_html( exc_data, include_hidden_frames=False, include_reusable=False, show_extra_data=False) reported = True elif debug_mode and not simple_html_error: error_html = formatter.format_html( exc_data, include_hidden_frames=True, include_reusable=False) head_html = formatter.error_css + formatter.hide_display_js return_error = error_template( head_html, error_html, extra_data) extra_data = '' reported = True else: msg = error_message or ''' An error occurred. See the error logs for more information. (Turn debug on to display exception reports here) ''' return_error = error_template('', msg, '') else: return_error = None if not reported and error_stream: err_report = formatter.format_text(exc_data, show_hidden_frames=True) err_report += '\n' + '-'*60 + '\n' error_stream.write(err_report) if extra_data: error_stream.write(extra_data) return return_error def send_report(rep, exc_data, html=True): try: rep.report(exc_data) except: output = StringIO() traceback.print_exc(file=output) if html: return """

Additionally an error occurred while sending the %s report:

%s

""" % ( cgi.escape(str(rep)), output.getvalue()) else: return ( "Additionally an error occurred while sending the " "%s report:\n%s" % (str(rep), output.getvalue())) else: return '' def error_template(head_html, exception, extra): return ''' Server Error %s

Server Error

%s %s ''' % (head_html, exception, extra) def make_error_middleware(app, global_conf, **kw): return ErrorMiddleware(app, global_conf=global_conf, **kw) doc_lines = ErrorMiddleware.__doc__.splitlines(True) for i in range(len(doc_lines)): if doc_lines[i].strip().startswith('Settings'): make_error_middleware.__doc__ = ''.join(doc_lines[i:]) break del i, doc_lines Paste-1.7.5.1/paste/exceptions/formatter.py0000664000175000017500000004601311354703364017231 0ustar ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php """ Formatters for the exception data that comes from ExceptionCollector. """ # @@: TODO: # Use this: http://www.zope.org/Members/tino/VisualTraceback/VisualTracebackNews import cgi import re from paste.util import PySourceColor def html_quote(s): return cgi.escape(str(s), True) class AbstractFormatter(object): general_data_order = ['object', 'source_url'] def __init__(self, show_hidden_frames=False, include_reusable=True, show_extra_data=True, trim_source_paths=()): self.show_hidden_frames = show_hidden_frames self.trim_source_paths = trim_source_paths self.include_reusable = include_reusable self.show_extra_data = show_extra_data def format_collected_data(self, exc_data): general_data = {} if self.show_extra_data: for name, value_list in exc_data.extra_data.items(): if isinstance(name, tuple): importance, title = name else: importance, title = 'normal', name for value in value_list: general_data[(importance, name)] = self.format_extra_data( importance, title, value) lines = [] frames = self.filter_frames(exc_data.frames) for frame in frames: sup = frame.supplement if sup: if sup.object: general_data[('important', 'object')] = self.format_sup_object( sup.object) if sup.source_url: general_data[('important', 'source_url')] = self.format_sup_url( sup.source_url) if sup.line: lines.append(self.format_sup_line_pos(sup.line, sup.column)) if sup.expression: lines.append(self.format_sup_expression(sup.expression)) if sup.warnings: for warning in sup.warnings: lines.append(self.format_sup_warning(warning)) if sup.info: lines.extend(self.format_sup_info(sup.info)) if frame.supplement_exception: lines.append('Exception in supplement:') lines.append(self.quote_long(frame.supplement_exception)) if frame.traceback_info: lines.append(self.format_traceback_info(frame.traceback_info)) filename = frame.filename if filename and self.trim_source_paths: for path, repl in self.trim_source_paths: if filename.startswith(path): filename = repl + filename[len(path):] break lines.append(self.format_source_line(filename or '?', frame)) source = frame.get_source_line() long_source = frame.get_source_line(2) if source: lines.append(self.format_long_source( source, long_source)) etype = exc_data.exception_type if not isinstance(etype, basestring): etype = etype.__name__ exc_info = self.format_exception_info( etype, exc_data.exception_value) data_by_importance = {'important': [], 'normal': [], 'supplemental': [], 'extra': []} for (importance, name), value in general_data.items(): data_by_importance[importance].append( (name, value)) for value in data_by_importance.values(): value.sort() return self.format_combine(data_by_importance, lines, exc_info) def filter_frames(self, frames): """ Removes any frames that should be hidden, according to the values of traceback_hide, self.show_hidden_frames, and the hidden status of the final frame. """ if self.show_hidden_frames: return frames new_frames = [] hidden = False for frame in frames: hide = frame.traceback_hide # @@: It would be nice to signal a warning if an unknown # hide string was used, but I'm not sure where to put # that warning. if hide == 'before': new_frames = [] hidden = False elif hide == 'before_and_this': new_frames = [] hidden = False continue elif hide == 'reset': hidden = False elif hide == 'reset_and_this': hidden = False continue elif hide == 'after': hidden = True elif hide == 'after_and_this': hidden = True continue elif hide: continue elif hidden: continue new_frames.append(frame) if frames[-1] not in new_frames: # We must include the last frame; that we don't indicates # that the error happened where something was "hidden", # so we just have to show everything return frames return new_frames def pretty_string_repr(self, s): """ Formats the string as a triple-quoted string when it contains newlines. """ if '\n' in s: s = repr(s) s = s[0]*3 + s[1:-1] + s[-1]*3 s = s.replace('\\n', '\n') return s else: return repr(s) def long_item_list(self, lst): """ Returns true if the list contains items that are long, and should be more nicely formatted. """ how_many = 0 for item in lst: if len(repr(item)) > 40: how_many += 1 if how_many >= 3: return True return False class TextFormatter(AbstractFormatter): def quote(self, s): return s def quote_long(self, s): return s def emphasize(self, s): return s def format_sup_object(self, obj): return 'In object: %s' % self.emphasize(self.quote(repr(obj))) def format_sup_url(self, url): return 'URL: %s' % self.quote(url) def format_sup_line_pos(self, line, column): if column: return self.emphasize('Line %i, Column %i' % (line, column)) else: return self.emphasize('Line %i' % line) def format_sup_expression(self, expr): return self.emphasize('In expression: %s' % self.quote(expr)) def format_sup_warning(self, warning): return 'Warning: %s' % self.quote(warning) def format_sup_info(self, info): return [self.quote_long(info)] def format_source_line(self, filename, frame): return 'File %r, line %s in %s' % ( filename, frame.lineno or '?', frame.name or '?') def format_long_source(self, source, long_source): return self.format_source(source) def format_source(self, source_line): return ' ' + self.quote(source_line.strip()) def format_exception_info(self, etype, evalue): return self.emphasize( '%s: %s' % (self.quote(etype), self.quote(evalue))) def format_traceback_info(self, info): return info def format_combine(self, data_by_importance, lines, exc_info): lines[:0] = [value for n, value in data_by_importance['important']] lines.append(exc_info) for name in 'normal', 'supplemental', 'extra': lines.extend([value for n, value in data_by_importance[name]]) return self.format_combine_lines(lines) def format_combine_lines(self, lines): return '\n'.join(lines) def format_extra_data(self, importance, title, value): if isinstance(value, str): s = self.pretty_string_repr(value) if '\n' in s: return '%s:\n%s' % (title, s) else: return '%s: %s' % (title, s) elif isinstance(value, dict): lines = ['\n', title, '-'*len(title)] items = value.items() items.sort() for n, v in items: try: v = repr(v) except Exception, e: v = 'Cannot display: %s' % e v = truncate(v) lines.append(' %s: %s' % (n, v)) return '\n'.join(lines) elif (isinstance(value, (list, tuple)) and self.long_item_list(value)): parts = [truncate(repr(v)) for v in value] return '%s: [\n %s]' % ( title, ',\n '.join(parts)) else: return '%s: %s' % (title, truncate(repr(value))) class HTMLFormatter(TextFormatter): def quote(self, s): return html_quote(s) def quote_long(self, s): return '
%s
' % self.quote(s) def emphasize(self, s): return '%s' % s def format_sup_url(self, url): return 'URL: %s' % (url, url) def format_combine_lines(self, lines): return '
\n'.join(lines) def format_source_line(self, filename, frame): name = self.quote(frame.name or '?') return 'Module %s:%s in %s' % ( filename, frame.modname or '?', frame.lineno or '?', name) return 'File %r, line %s in %s' % ( filename, frame.lineno, name) def format_long_source(self, source, long_source): q_long_source = str2html(long_source, False, 4, True) q_source = str2html(source, True, 0, False) return ('' '>>  %s' % (q_long_source, q_source)) def format_source(self, source_line): return '  %s' % self.quote(source_line.strip()) def format_traceback_info(self, info): return '
%s
' % self.quote(info) def format_extra_data(self, importance, title, value): if isinstance(value, str): s = self.pretty_string_repr(value) if '\n' in s: return '%s:
%s
' % (title, self.quote(s)) else: return '%s: %s' % (title, self.quote(s)) elif isinstance(value, dict): return self.zebra_table(title, value) elif (isinstance(value, (list, tuple)) and self.long_item_list(value)): return '%s: [
\n    %s]
' % ( title, ',
    '.join(map(self.quote, map(repr, value)))) else: return '%s: %s' % (title, self.quote(repr(value))) def format_combine(self, data_by_importance, lines, exc_info): lines[:0] = [value for n, value in data_by_importance['important']] lines.append(exc_info) for name in 'normal', 'supplemental': lines.extend([value for n, value in data_by_importance[name]]) if data_by_importance['extra']: lines.append( '\n' + '
\n') lines.extend([value for n, value in data_by_importance['extra']]) lines.append('
') text = self.format_combine_lines(lines) if self.include_reusable: return error_css + hide_display_js + text else: # Usually because another error is already on this page, # and so the js & CSS are unneeded return text def zebra_table(self, title, rows, table_class="variables"): if isinstance(rows, dict): rows = rows.items() rows.sort() table = ['' % table_class, '' % self.quote(title)] odd = False for name, value in rows: try: value = repr(value) except Exception, e: value = 'Cannot print: %s' % e odd = not odd table.append( '' % (odd and 'odd' or 'even', self.quote(name))) table.append( '' % make_wrappable(self.quote(truncate(value)))) table.append('
%s
%s%s
') return '\n'.join(table) hide_display_js = r''' ''' error_css = """ """ def format_html(exc_data, include_hidden_frames=False, **ops): if not include_hidden_frames: return HTMLFormatter(**ops).format_collected_data(exc_data) short_er = format_html(exc_data, show_hidden_frames=False, **ops) # @@: This should have a way of seeing if the previous traceback # was actually trimmed at all ops['include_reusable'] = False ops['show_extra_data'] = False long_er = format_html(exc_data, show_hidden_frames=True, **ops) text_er = format_text(exc_data, show_hidden_frames=True, **ops) return """ %s
%s

""" % (short_er, long_er, cgi.escape(text_er)) def format_text(exc_data, **ops): return TextFormatter(**ops).format_collected_data(exc_data) whitespace_re = re.compile(r' +') pre_re = re.compile(r'') error_re = re.compile(r'

ERROR: .*?

') def str2html(src, strip=False, indent_subsequent=0, highlight_inner=False): """ Convert a string to HTML. Try to be really safe about it, returning a quoted version of the string if nothing else works. """ try: return _str2html(src, strip=strip, indent_subsequent=indent_subsequent, highlight_inner=highlight_inner) except: return html_quote(src) def _str2html(src, strip=False, indent_subsequent=0, highlight_inner=False): if strip: src = src.strip() orig_src = src try: src = PySourceColor.str2html(src, form='snip') src = error_re.sub('', src) src = pre_re.sub('', src) src = re.sub(r'^[\n\r]{0,1}', '', src) src = re.sub(r'[\n\r]{0,1}$', '', src) except: src = html_quote(orig_src) lines = src.splitlines() if len(lines) == 1: return lines[0] indent = ' '*indent_subsequent for i in range(1, len(lines)): lines[i] = indent+lines[i] if highlight_inner and i == len(lines)/2: lines[i] = '%s' % lines[i] src = '
\n'.join(lines) src = whitespace_re.sub( lambda m: ' '*(len(m.group(0))-1) + ' ', src) return src def truncate(string, limit=1000): """ Truncate the string to the limit number of characters """ if len(string) > limit: return string[:limit-20]+'...'+string[-17:] else: return string def make_wrappable(html, wrap_limit=60, split_on=';?&@!$#-/\\"\''): # Currently using , maybe should use ​ # http://www.cs.tut.fi/~jkorpela/html/nobr.html if len(html) <= wrap_limit: return html words = html.split() new_words = [] for word in words: wrapped_word = '' while len(word) > wrap_limit: for char in split_on: if char in word: first, rest = word.split(char, 1) wrapped_word += first+char+'' word = rest break else: for i in range(0, len(word), wrap_limit): wrapped_word += word[i:i+wrap_limit]+'' word = '' wrapped_word += word new_words.append(wrapped_word) return ' '.join(new_words) def make_pre_wrappable(html, wrap_limit=60, split_on=';?&@!$#-/\\"\''): """ Like ``make_wrappable()`` but intended for text that will go in a ``
`` block, so wrap on a line-by-line basis.
    """
    lines = html.splitlines()
    new_lines = []
    for line in lines:
        if len(line) > wrap_limit:
            for char in split_on:
                if char in line:
                    parts = line.split(char)
                    line = ''.join(parts)
                    break
        new_lines.append(line)
    return '\n'.join(lines)
Paste-1.7.5.1/paste/exceptions/serial_number_generator.py0000664000175000017500000000762011354703364022124 0ustar  ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php

"""
Creates a human-readable identifier, using numbers and digits,
avoiding ambiguous numbers and letters.  hash_identifier can be used
to create compact representations that are unique for a certain string
(or concatenation of strings)
"""

try:
    from hashlib import md5
except ImportError:
    from md5 import md5

good_characters = "23456789abcdefghjkmnpqrtuvwxyz"

base = len(good_characters)

def make_identifier(number):
    """
    Encodes a number as an identifier.
    """
    if not isinstance(number, (int, long)):
        raise ValueError(
            "You can only make identifiers out of integers (not %r)"
            % number)
    if number < 0:
        raise ValueError(
            "You cannot make identifiers out of negative numbers: %r"
            % number)
    result = []
    while number:
        next = number % base
        result.append(good_characters[next])
        # Note, this depends on integer rounding of results:
        number = number / base
    return ''.join(result)

def hash_identifier(s, length, pad=True, hasher=md5, prefix='',
                    group=None, upper=False):
    """
    Hashes the string (with the given hashing module), then turns that
    hash into an identifier of the given length (using modulo to
    reduce the length of the identifier).  If ``pad`` is False, then
    the minimum-length identifier will be used; otherwise the
    identifier will be padded with 0's as necessary.

    ``prefix`` will be added last, and does not count towards the
    target length.  ``group`` will group the characters with ``-`` in
    the given lengths, and also does not count towards the target
    length.  E.g., ``group=4`` will cause a identifier like
    ``a5f3-hgk3-asdf``.  Grouping occurs before the prefix.
    """
    if not callable(hasher):
        # Accept sha/md5 modules as well as callables
        hasher = hasher.new
    if length > 26 and hasher is md5:
        raise ValueError, (
            "md5 cannot create hashes longer than 26 characters in "
            "length (you gave %s)" % length)
    if isinstance(s, unicode):
        s = s.encode('utf-8')
    h = hasher(str(s))
    bin_hash = h.digest()
    modulo = base ** length
    number = 0
    for c in list(bin_hash):
        number = (number * 256 + ord(c)) % modulo
    ident = make_identifier(number)
    if pad:
        ident = good_characters[0]*(length-len(ident)) + ident
    if group:
        parts = []
        while ident:
            parts.insert(0, ident[-group:])
            ident = ident[:-group]
        ident = '-'.join(parts)
    if upper:
        ident = ident.upper()
    return prefix + ident

# doctest tests:
__test__ = {
    'make_identifier': """
    >>> make_identifier(0)
    ''
    >>> make_identifier(1000)
    'c53'
    >>> make_identifier(-100)
    Traceback (most recent call last):
        ...
    ValueError: You cannot make identifiers out of negative numbers: -100
    >>> make_identifier('test')
    Traceback (most recent call last):
        ...
    ValueError: You can only make identifiers out of integers (not 'test')
    >>> make_identifier(1000000000000)
    'c53x9rqh3'
    """,
    'hash_identifier': """
    >>> hash_identifier(0, 5)
    'cy2dr'
    >>> hash_identifier(0, 10)
    'cy2dr6rg46'
    >>> hash_identifier('this is a test of a long string', 5)
    'awatu'
    >>> hash_identifier(0, 26)
    'cy2dr6rg46cx8t4w2f3nfexzk4'
    >>> hash_identifier(0, 30)
    Traceback (most recent call last):
        ...
    ValueError: md5 cannot create hashes longer than 26 characters in length (you gave 30)
    >>> hash_identifier(0, 10, group=4)
    'cy-2dr6-rg46'
    >>> hash_identifier(0, 10, group=4, upper=True, prefix='M-')
    'M-CY-2DR6-RG46'
    """}

if __name__ == '__main__':
    import doctest
    doctest.testmod()
    
Paste-1.7.5.1/paste/exceptions/__init__.py0000664000175000017500000000037411354703364016765 0ustar  ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
"""
Package for catching exceptions and displaying annotated exception
reports
"""
Paste-1.7.5.1/paste/httpserver.py0000775000175000017500000015463511443715765015277 0ustar  ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
# (c) 2005 Clark C. Evans
# This module is part of the Python Paste Project and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
# This code was written with funding by http://prometheusresearch.com
"""
WSGI HTTP Server

This is a minimalistic WSGI server using Python's built-in BaseHTTPServer;
if pyOpenSSL is installed, it also provides SSL capabilities.
"""

# @@: add in protection against HTTP/1.0 clients who claim to
#     be 1.1 but do not send a Content-Length

# @@: add support for chunked encoding, this is not a 1.1 server
#     till this is completed.

import atexit
import traceback
import socket, sys, threading, urlparse, Queue, urllib
import posixpath
import time
import thread
import os
from itertools import count
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from SocketServer import ThreadingMixIn
from paste.util import converters
import logging
try:
    from paste.util import killthread
except ImportError:
    # Not available, probably no ctypes
    killthread = None

__all__ = ['WSGIHandlerMixin', 'WSGIServer', 'WSGIHandler', 'serve']
__version__ = "0.5"

class ContinueHook(object):
    """
    When a client request includes a 'Expect: 100-continue' header, then
    it is the responsibility of the server to send 100 Continue when it
    is ready for the content body.  This allows authentication, access
    levels, and other exceptions to be detected *before* bandwith is
    spent on the request body.

    This is a rfile wrapper that implements this functionality by
    sending 100 Continue to the client immediately after the user
    requests the content via a read() operation on the rfile stream.
    After this response is sent, it becomes a pass-through object.
    """

    def __init__(self, rfile, write):
        self._ContinueFile_rfile = rfile
        self._ContinueFile_write = write
        for attr in ('close', 'closed', 'fileno', 'flush',
                     'mode', 'bufsize', 'softspace'):
            if hasattr(rfile, attr):
                setattr(self, attr, getattr(rfile, attr))
        for attr in ('read', 'readline', 'readlines'):
            if hasattr(rfile, attr):
                setattr(self, attr, getattr(self, '_ContinueFile_' + attr))

    def _ContinueFile_send(self):
        self._ContinueFile_write("HTTP/1.1 100 Continue\r\n\r\n")
        rfile = self._ContinueFile_rfile
        for attr in ('read', 'readline', 'readlines'):
            if hasattr(rfile, attr):
                setattr(self, attr, getattr(rfile, attr))

    def _ContinueFile_read(self, size=-1):
        self._ContinueFile_send()
        return self._ContinueFile_rfile.read(size)

    def _ContinueFile_readline(self, size=-1):
        self._ContinueFile_send()
        return self._ContinueFile_rfile.readline(size)

    def _ContinueFile_readlines(self, sizehint=0):
        self._ContinueFile_send()
        return self._ContinueFile_rfile.readlines(sizehint)

class WSGIHandlerMixin:
    """
    WSGI mix-in for HTTPRequestHandler

    This class is a mix-in to provide WSGI functionality to any
    HTTPRequestHandler derivative (as provided in Python's BaseHTTPServer).
    This assumes a ``wsgi_application`` handler on ``self.server``.
    """
    lookup_addresses = True

    def log_request(self, *args, **kwargs):
        """ disable success request logging

        Logging transactions should not be part of a WSGI server,
        if you want logging; look at paste.translogger
        """
        pass

    def log_message(self, *args, **kwargs):
        """ disable error message logging

        Logging transactions should not be part of a WSGI server,
        if you want logging; look at paste.translogger
        """
        pass

    def version_string(self):
        """ behavior that BaseHTTPServer should have had """
        if not self.sys_version:
            return self.server_version
        else:
            return self.server_version + ' ' + self.sys_version

    def wsgi_write_chunk(self, chunk):
        """
        Write a chunk of the output stream; send headers if they
        have not already been sent.
        """
        if not self.wsgi_headers_sent and not self.wsgi_curr_headers:
            raise RuntimeError(
                "Content returned before start_response called")
        if not self.wsgi_headers_sent:
            self.wsgi_headers_sent = True
            (status, headers) = self.wsgi_curr_headers
            code, message = status.split(" ", 1)
            self.send_response(int(code), message)
            #
            # HTTP/1.1 compliance; either send Content-Length or
            # signal that the connection is being closed.
            #
            send_close = True
            for (k, v) in  headers:
                lk = k.lower()
                if 'content-length' == lk:
                    send_close = False
                if 'connection' == lk:
                    if 'close' == v.lower():
                        self.close_connection = 1
                        send_close = False
                self.send_header(k, v)
            if send_close:
                self.close_connection = 1
                self.send_header('Connection', 'close')

            self.end_headers()
        self.wfile.write(chunk)

    def wsgi_start_response(self, status, response_headers, exc_info=None):
        if exc_info:
            try:
                if self.wsgi_headers_sent:
                    raise exc_info[0], exc_info[1], exc_info[2]
                else:
                    # In this case, we're going to assume that the
                    # higher-level code is currently handling the
                    # issue and returning a resonable response.
                    # self.log_error(repr(exc_info))
                    pass
            finally:
                exc_info = None
        elif self.wsgi_curr_headers:
            assert 0, "Attempt to set headers a second time w/o an exc_info"
        self.wsgi_curr_headers = (status, response_headers)
        return self.wsgi_write_chunk

    def wsgi_setup(self, environ=None):
        """
        Setup the member variables used by this WSGI mixin, including
        the ``environ`` and status member variables.

        After the basic environment is created; the optional ``environ``
        argument can be used to override any settings.
        """

        (scheme, netloc, path, query, fragment) = urlparse.urlsplit(self.path)
        path = urllib.unquote(path)
        endslash = path.endswith('/')
        path = posixpath.normpath(path)
        if endslash and path != '/':
            # Put the slash back...
            path += '/'
        (server_name, server_port) = self.server.server_address[:2]

        rfile = self.rfile
        if 'HTTP/1.1' == self.protocol_version and \
                '100-continue' == self.headers.get('Expect','').lower():
            rfile = ContinueHook(rfile, self.wfile.write)
        else:
            # We can put in the protection to keep from over-reading the
            # file
            try:
                content_length = int(self.headers.get('Content-Length', '0'))
            except ValueError:
                content_length = 0
            if not hasattr(self.connection, 'get_context'):
                # @@: LimitedLengthFile is currently broken in connection
                # with SSL (sporatic errors that are diffcult to trace, but
                # ones that go away when you don't use LimitedLengthFile)
                rfile = LimitedLengthFile(rfile, content_length)

        remote_address = self.client_address[0]
        self.wsgi_environ = {
                'wsgi.version': (1,0)
               ,'wsgi.url_scheme': 'http'
               ,'wsgi.input': rfile
               ,'wsgi.errors': sys.stderr
               ,'wsgi.multithread': True
               ,'wsgi.multiprocess': False
               ,'wsgi.run_once': False
               # CGI variables required by PEP-333
               ,'REQUEST_METHOD': self.command
               ,'SCRIPT_NAME': '' # application is root of server
               ,'PATH_INFO': path
               ,'QUERY_STRING': query
               ,'CONTENT_TYPE': self.headers.get('Content-Type', '')
               ,'CONTENT_LENGTH': self.headers.get('Content-Length', '0')
               ,'SERVER_NAME': server_name
               ,'SERVER_PORT': str(server_port)
               ,'SERVER_PROTOCOL': self.request_version
               # CGI not required by PEP-333
               ,'REMOTE_ADDR': remote_address
               }
        if scheme:
            self.wsgi_environ['paste.httpserver.proxy.scheme'] = scheme
        if netloc:
            self.wsgi_environ['paste.httpserver.proxy.host'] = netloc

        if self.lookup_addresses:
            # @@: make lookup_addreses actually work, at this point
            #     it has been address_string() is overriden down in
            #     file and hence is a noop
            if remote_address.startswith("192.168.") \
            or remote_address.startswith("10.") \
            or remote_address.startswith("172.16."):
                pass
            else:
                address_string = None # self.address_string()
                if address_string:
                    self.wsgi_environ['REMOTE_HOST'] = address_string

        if hasattr(self.server, 'thread_pool'):
            # Now that we know what the request was for, we should
            # tell the thread pool what its worker is working on
            self.server.thread_pool.worker_tracker[thread.get_ident()][1] = self.wsgi_environ
            self.wsgi_environ['paste.httpserver.thread_pool'] = self.server.thread_pool

        for k, v in self.headers.items():
            key = 'HTTP_' + k.replace("-","_").upper()
            if key in ('HTTP_CONTENT_TYPE','HTTP_CONTENT_LENGTH'):
                continue
            self.wsgi_environ[key] = ','.join(self.headers.getheaders(k))

        if hasattr(self.connection,'get_context'):
            self.wsgi_environ['wsgi.url_scheme'] = 'https'
            # @@: extract other SSL parameters from pyOpenSSL at...
            # http://www.modssl.org/docs/2.8/ssl_reference.html#ToC25

        if environ:
            assert isinstance(environ, dict)
            self.wsgi_environ.update(environ)
            if 'on' == environ.get('HTTPS'):
                self.wsgi_environ['wsgi.url_scheme'] = 'https'

        self.wsgi_curr_headers = None
        self.wsgi_headers_sent = False

    def wsgi_connection_drop(self, exce, environ=None):
        """
        Override this if you're interested in socket exceptions, such
        as when the user clicks 'Cancel' during a file download.
        """
        pass

    def wsgi_execute(self, environ=None):
        """
        Invoke the server's ``wsgi_application``.
        """

        self.wsgi_setup(environ)

        try:
            result = self.server.wsgi_application(self.wsgi_environ,
                                                  self.wsgi_start_response)
            try:
                for chunk in result:
                    self.wsgi_write_chunk(chunk)
                if not self.wsgi_headers_sent:
                    self.wsgi_write_chunk('')
            finally:
                if hasattr(result,'close'):
                    result.close()
                result = None
        except socket.error, exce:
            self.wsgi_connection_drop(exce, environ)
            return
        except:
            if not self.wsgi_headers_sent:
                error_msg = "Internal Server Error\n"
                self.wsgi_curr_headers = (
                    '500 Internal Server Error',
                    [('Content-type', 'text/plain'),
                     ('Content-length', str(len(error_msg)))])
                self.wsgi_write_chunk("Internal Server Error\n")
            raise

#
# SSL Functionality
#
# This implementation was motivated by Sebastien Martini's SSL example
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
#
try:
    from OpenSSL import SSL, tsafe
    SocketErrors = (socket.error, SSL.ZeroReturnError, SSL.SysCallError)
except ImportError:
    # Do not require pyOpenSSL to be installed, but disable SSL
    # functionality in that case.
    SSL = None
    SocketErrors = (socket.error,)
    class SecureHTTPServer(HTTPServer):
        def __init__(self, server_address, RequestHandlerClass,
                     ssl_context=None, request_queue_size=None):
            assert not ssl_context, "pyOpenSSL not installed"
            HTTPServer.__init__(self, server_address, RequestHandlerClass)
            if request_queue_size:
                self.socket.listen(request_queue_size)
else:

    class _ConnFixer(object):
        """ wraps a socket connection so it implements makefile """
        def __init__(self, conn):
            self.__conn = conn
        def makefile(self, mode, bufsize):
            return socket._fileobject(self.__conn, mode, bufsize)
        def __getattr__(self, attrib):
            return getattr(self.__conn, attrib)

    class SecureHTTPServer(HTTPServer):
        """
        Provides SSL server functionality on top of the BaseHTTPServer
        by overriding _private_ members of Python's standard
        distribution. The interface for this instance only changes by
        adding a an optional ssl_context attribute to the constructor:

              cntx = SSL.Context(SSL.SSLv23_METHOD)
              cntx.use_privatekey_file("host.pem")
              cntx.use_certificate_file("host.pem")

        """

        def __init__(self, server_address, RequestHandlerClass,
                     ssl_context=None, request_queue_size=None):
            # This overrides the implementation of __init__ in python's
            # SocketServer.TCPServer (which BaseHTTPServer.HTTPServer
            # does not override, thankfully).
            HTTPServer.__init__(self, server_address, RequestHandlerClass)
            self.socket = socket.socket(self.address_family,
                                        self.socket_type)
            self.ssl_context = ssl_context
            if ssl_context:
                class TSafeConnection(tsafe.Connection):
                    def settimeout(self, *args):
                        self._lock.acquire()
                        try:
                            return self._ssl_conn.settimeout(*args)
                        finally:
                            self._lock.release()
                    def gettimeout(self):
                        self._lock.acquire()
                        try:
                            return self._ssl_conn.gettimeout()
                        finally:
                            self._lock.release()
                self.socket = TSafeConnection(ssl_context, self.socket)
            self.server_bind()
            if request_queue_size:
                self.socket.listen(request_queue_size)
            self.server_activate()

        def get_request(self):
            # The default SSL request object does not seem to have a
            # ``makefile(mode, bufsize)`` method as expected by
            # Socketserver.StreamRequestHandler.
            (conn, info) = self.socket.accept()
            if self.ssl_context:
                conn = _ConnFixer(conn)
            return (conn, info)

    def _auto_ssl_context():
        import OpenSSL, time, random
        pkey = OpenSSL.crypto.PKey()
        pkey.generate_key(OpenSSL.crypto.TYPE_RSA, 768)

        cert = OpenSSL.crypto.X509()

        cert.set_serial_number(random.randint(0, sys.maxint))
        cert.gmtime_adj_notBefore(0)
        cert.gmtime_adj_notAfter(60 * 60 * 24 * 365)
        cert.get_subject().CN = '*'
        cert.get_subject().O = 'Dummy Certificate'
        cert.get_issuer().CN = 'Untrusted Authority'
        cert.get_issuer().O = 'Self-Signed'
        cert.set_pubkey(pkey)
        cert.sign(pkey, 'md5')

        ctx = SSL.Context(SSL.SSLv23_METHOD)
        ctx.use_privatekey(pkey)
        ctx.use_certificate(cert)

        return ctx

class WSGIHandler(WSGIHandlerMixin, BaseHTTPRequestHandler):
    """
    A WSGI handler that overrides POST, GET and HEAD to delegate
    requests to the server's ``wsgi_application``.
    """
    server_version = 'PasteWSGIServer/' + __version__

    def handle_one_request(self):
        """Handle a single HTTP request.

        You normally don't need to override this method; see the class
        __doc__ string for information on how to handle specific HTTP
        commands such as GET and POST.

        """
        self.raw_requestline = self.rfile.readline()
        if not self.raw_requestline:
            self.close_connection = 1
            return
        if not self.parse_request(): # An error code has been sent, just exit
            return
        self.wsgi_execute()

    def handle(self):
        # don't bother logging disconnects while handling a request
        try:
            BaseHTTPRequestHandler.handle(self)
        except SocketErrors, exce:
            self.wsgi_connection_drop(exce)

    def address_string(self):
        """Return the client address formatted for logging.

        This is overridden so that no hostname lookup is done.
        """
        return ''

class LimitedLengthFile(object):
    def __init__(self, file, length):
        self.file = file
        self.length = length
        self._consumed = 0
        if hasattr(self.file, 'seek'):
            self.seek = self._seek

    def __repr__(self):
        base_repr = repr(self.file)
        return base_repr[:-1] + ' length=%s>' % self.length

    def read(self, length=None):
        left = self.length - self._consumed
        if length is None:
            length = left
        else:
            length = min(length, left)
        # next two lines are hnecessary only if read(0) blocks
        if not left:
            return ''
        data = self.file.read(length)
        self._consumed += len(data)
        return data

    def readline(self, *args):
        max_read = self.length - self._consumed
        if len(args):
            max_read = min(args[0], max_read)
        data = self.file.readline(max_read)
        self._consumed += len(data)
        return data

    def readlines(self, hint=None):
        data = self.file.readlines(hint)
        for chunk in data:
            self._consumed += len(chunk)
        return data

    def __iter__(self):
        return self

    def next(self):
        if self.length - self._consumed <= 0:
            raise StopIteration
        return self.readline()

    ## Optional methods ##

    def _seek(self, place):
        self.file.seek(place)
        self._consumed = place

    def tell(self):
        if hasattr(self.file, 'tell'):
            return self.file.tell()
        else:
            return self._consumed

class ThreadPool(object):
    """
    Generic thread pool with a queue of callables to consume.

    Keeps a notion of the status of its worker threads:

    idle: worker thread with nothing to do

    busy: worker thread doing its job

    hung: worker thread that's been doing a job for too long

    dying: a hung thread that has been killed, but hasn't died quite
    yet.

    zombie: what was a worker thread that we've tried to kill but
    isn't dead yet.

    At any time you can call track_threads, to get a dictionary with
    these keys and lists of thread_ids that fall in that status.  All
    keys will be present, even if they point to emty lists.

    hung threads are threads that have been busy more than
    hung_thread_limit seconds.  Hung threads are killed when they live
    longer than kill_thread_limit seconds.  A thread is then
    considered dying for dying_limit seconds, if it is still alive
    after that it is considered a zombie.

    When there are no idle workers and a request comes in, another
    worker *may* be spawned.  If there are less than spawn_if_under
    threads in the busy state, another thread will be spawned.  So if
    the limit is 5, and there are 4 hung threads and 6 busy threads,
    no thread will be spawned.

    When there are more than max_zombie_threads_before_die zombie
    threads, a SystemExit exception will be raised, stopping the
    server.  Use 0 or None to never raise this exception.  Zombie
    threads *should* get cleaned up, but killing threads is no
    necessarily reliable.  This is turned off by default, since it is
    only a good idea if you've deployed the server with some process
    watching from above (something similar to daemontools or zdaemon).

    Each worker thread only processes ``max_requests`` tasks before it
    dies and replaces itself with a new worker thread.
    """


    SHUTDOWN = object()

    def __init__(
        self, nworkers, name="ThreadPool", daemon=False,
        max_requests=100, # threads are killed after this many requests
        hung_thread_limit=30, # when a thread is marked "hung"
        kill_thread_limit=1800, # when you kill that hung thread
        dying_limit=300, # seconds that a kill should take to go into effect (longer than this and the thread is a "zombie")
        spawn_if_under=5, # spawn if there's too many hung threads
        max_zombie_threads_before_die=0, # when to give up on the process
        hung_check_period=100, # every 100 requests check for hung workers
        logger=None, # Place to log messages to
        error_email=None, # Person(s) to notify if serious problem occurs
        ):
        """
        Create thread pool with `nworkers` worker threads.
        """
        self.nworkers = nworkers
        self.max_requests = max_requests
        self.name = name
        self.queue = Queue.Queue()
        self.workers = []
        self.daemon = daemon
        if logger is None:
            logger = logging.getLogger('paste.httpserver.ThreadPool')
        if isinstance(logger, basestring):
            logger = logging.getLogger(logger)
        self.logger = logger
        self.error_email = error_email
        self._worker_count = count()

        assert (not kill_thread_limit
                or kill_thread_limit >= hung_thread_limit), (
            "kill_thread_limit (%s) should be higher than hung_thread_limit (%s)"
            % (kill_thread_limit, hung_thread_limit))
        if not killthread:
            kill_thread_limit = 0
            self.logger.info(
                "Cannot use kill_thread_limit as ctypes/killthread is not available")
        self.kill_thread_limit = kill_thread_limit
        self.dying_limit = dying_limit
        self.hung_thread_limit = hung_thread_limit
        assert spawn_if_under <= nworkers, (
            "spawn_if_under (%s) should be less than nworkers (%s)"
            % (spawn_if_under, nworkers))
        self.spawn_if_under = spawn_if_under
        self.max_zombie_threads_before_die = max_zombie_threads_before_die
        self.hung_check_period = hung_check_period
        self.requests_since_last_hung_check = 0
        # Used to keep track of what worker is doing what:
        self.worker_tracker = {}
        # Used to keep track of the workers not doing anything:
        self.idle_workers = []
        # Used to keep track of threads that have been killed, but maybe aren't dead yet:
        self.dying_threads = {}
        # This is used to track when we last had to add idle workers;
        # we shouldn't cull extra workers until some time has passed
        # (hung_thread_limit) since workers were added:
        self._last_added_new_idle_workers = 0
        if not daemon:
            atexit.register(self.shutdown)
        for i in range(self.nworkers):
            self.add_worker_thread(message='Initial worker pool')

    def add_task(self, task):
        """
        Add a task to the queue
        """
        self.logger.debug('Added task (%i tasks queued)', self.queue.qsize())
        if self.hung_check_period:
            self.requests_since_last_hung_check += 1
            if self.requests_since_last_hung_check > self.hung_check_period:
                self.requests_since_last_hung_check = 0
                self.kill_hung_threads()
        if not self.idle_workers and self.spawn_if_under:
            # spawn_if_under can come into effect...
            busy = 0
            now = time.time()
            self.logger.debug('No idle workers for task; checking if we need to make more workers')
            for worker in self.workers:
                if not hasattr(worker, 'thread_id'):
                    # Not initialized
                    continue
                time_started, info = self.worker_tracker.get(worker.thread_id,
                                                             (None, None))
                if time_started is not None:
                    if now - time_started < self.hung_thread_limit:
                        busy += 1
            if busy < self.spawn_if_under:
                self.logger.info(
                    'No idle tasks, and only %s busy tasks; adding %s more '
                    'workers', busy, self.spawn_if_under-busy)
                self._last_added_new_idle_workers = time.time()
                for i in range(self.spawn_if_under - busy):
                    self.add_worker_thread(message='Response to lack of idle workers')
            else:
                self.logger.debug(
                    'No extra workers needed (%s busy workers)',
                    busy)
        if (len(self.workers) > self.nworkers
            and len(self.idle_workers) > 3
            and time.time()-self._last_added_new_idle_workers > self.hung_thread_limit):
            # We've spawned worers in the past, but they aren't needed
            # anymore; kill off some
            self.logger.info(
                'Culling %s extra workers (%s idle workers present)',
                len(self.workers)-self.nworkers, len(self.idle_workers))
            self.logger.debug(
                'Idle workers: %s', self.idle_workers)
            for i in range(len(self.workers) - self.nworkers):
                self.queue.put(self.SHUTDOWN)
        self.queue.put(task)

    def track_threads(self):
        """
        Return a dict summarizing the threads in the pool (as
        described in the ThreadPool docstring).
        """
        result = dict(idle=[], busy=[], hung=[], dying=[], zombie=[])
        now = time.time()
        for worker in self.workers:
            if not hasattr(worker, 'thread_id'):
                # The worker hasn't fully started up, we should just
                # ignore it
                continue
            time_started, info = self.worker_tracker.get(worker.thread_id,
                                                         (None, None))
            if time_started is not None:
                if now - time_started > self.hung_thread_limit:
                    result['hung'].append(worker)
                else:
                    result['busy'].append(worker)
            else:
                result['idle'].append(worker)
        for thread_id, (time_killed, worker) in self.dying_threads.items():
            if not self.thread_exists(thread_id):
                # Cull dying threads that are actually dead and gone
                self.logger.info('Killed thread %s no longer around',
                                 thread_id)
                try:
                    del self.dying_threads[thread_id]
                except KeyError:
                    pass
                continue
            if now - time_killed > self.dying_limit:
                result['zombie'].append(worker)
            else:
                result['dying'].append(worker)
        return result

    def kill_worker(self, thread_id):
        """
        Removes the worker with the given thread_id from the pool, and
        replaces it with a new worker thread.

        This should only be done for mis-behaving workers.
        """
        if killthread is None:
            raise RuntimeError(
                "Cannot kill worker; killthread/ctypes not available")
        thread_obj = threading._active.get(thread_id)
        killthread.async_raise(thread_id, SystemExit)
        try:
            del self.worker_tracker[thread_id]
        except KeyError:
            pass
        self.logger.info('Killing thread %s', thread_id)
        if thread_obj in self.workers:
            self.workers.remove(thread_obj)
        self.dying_threads[thread_id] = (time.time(), thread_obj)
        self.add_worker_thread(message='Replacement for killed thread %s' % thread_id)

    def thread_exists(self, thread_id):
        """
        Returns true if a thread with this id is still running
        """
        return thread_id in threading._active

    def add_worker_thread(self, *args, **kwargs):
        index = self._worker_count.next()
        worker = threading.Thread(target=self.worker_thread_callback,
                                  args=args, kwargs=kwargs,
                                  name=("worker %d" % index))
        worker.setDaemon(self.daemon)
        worker.start()

    def kill_hung_threads(self):
        """
        Tries to kill any hung threads
        """
        if not self.kill_thread_limit:
            # No killing should occur
            return
        now = time.time()
        max_time = 0
        total_time = 0
        idle_workers = 0
        starting_workers = 0
        working_workers = 0
        killed_workers = 0
        for worker in self.workers:
            if not hasattr(worker, 'thread_id'):
                # Not setup yet
                starting_workers += 1
                continue
            time_started, info = self.worker_tracker.get(worker.thread_id,
                                                         (None, None))
            if time_started is None:
                # Must be idle
                idle_workers += 1
                continue
            working_workers += 1
            max_time = max(max_time, now-time_started)
            total_time += now-time_started
            if now - time_started > self.kill_thread_limit:
                self.logger.warning(
                    'Thread %s hung (working on task for %i seconds)',
                    worker.thread_id, now - time_started)
                try:
                    import pprint
                    info_desc = pprint.pformat(info)
                except:
                    out = StringIO()
                    traceback.print_exc(file=out)
                    info_desc = 'Error:\n%s' % out.getvalue()
                self.notify_problem(
                    "Killing worker thread (id=%(thread_id)s) because it has been \n"
                    "working on task for %(time)s seconds (limit is %(limit)s)\n"
                    "Info on task:\n"
                    "%(info)s"
                    % dict(thread_id=worker.thread_id,
                           time=now - time_started,
                           limit=self.kill_thread_limit,
                           info=info_desc))
                self.kill_worker(worker.thread_id)
                killed_workers += 1
        if working_workers:
            ave_time = float(total_time) / working_workers
            ave_time = '%.2fsec' % ave_time
        else:
            ave_time = 'N/A'
        self.logger.info(
            "kill_hung_threads status: %s threads (%s working, %s idle, %s starting) "
            "ave time %s, max time %.2fsec, killed %s workers"
            % (idle_workers + starting_workers + working_workers,
               working_workers, idle_workers, starting_workers,
               ave_time, max_time, killed_workers))
        self.check_max_zombies()

    def check_max_zombies(self):
        """
        Check if we've reached max_zombie_threads_before_die; if so
        then kill the entire process.
        """
        if not self.max_zombie_threads_before_die:
            return
        found = []
        now = time.time()
        for thread_id, (time_killed, worker) in self.dying_threads.items():
            if not self.thread_exists(thread_id):
                # Cull dying threads that are actually dead and gone
                try:
                    del self.dying_threads[thread_id]
                except KeyError:
                    pass
                continue
            if now - time_killed > self.dying_limit:
                found.append(thread_id)
        if found:
            self.logger.info('Found %s zombie threads', found)
        if len(found) > self.max_zombie_threads_before_die:
            self.logger.fatal(
                'Exiting process because %s zombie threads is more than %s limit',
                len(found), self.max_zombie_threads_before_die)
            self.notify_problem(
                "Exiting process because %(found)s zombie threads "
                "(more than limit of %(limit)s)\n"
                "Bad threads (ids):\n"
                "  %(ids)s\n"
                % dict(found=len(found),
                       limit=self.max_zombie_threads_before_die,
                       ids="\n  ".join(map(str, found))),
                subject="Process restart (too many zombie threads)")
            self.shutdown(10)
            print 'Shutting down', threading.currentThread()
            raise ServerExit(3)

    def worker_thread_callback(self, message=None):
        """
        Worker thread should call this method to get and process queued
        callables.
        """
        thread_obj = threading.currentThread()
        thread_id = thread_obj.thread_id = thread.get_ident()
        self.workers.append(thread_obj)
        self.idle_workers.append(thread_id)
        requests_processed = 0
        add_replacement_worker = False
        self.logger.debug('Started new worker %s: %s', thread_id, message)
        try:
            while True:
                if self.max_requests and self.max_requests < requests_processed:
                    # Replace this thread then die
                    self.logger.debug('Thread %s processed %i requests (limit %s); stopping thread'
                                      % (thread_id, requests_processed, self.max_requests))
                    add_replacement_worker = True
                    break
                runnable = self.queue.get()
                if runnable is ThreadPool.SHUTDOWN:
                    self.logger.debug('Worker %s asked to SHUTDOWN', thread_id)
                    break
                try:
                    self.idle_workers.remove(thread_id)
                except ValueError:
                    pass
                self.worker_tracker[thread_id] = [time.time(), None]
                requests_processed += 1
                try:
                    try:
                        runnable()
                    except:
                        # We are later going to call sys.exc_clear(),
                        # removing all remnants of any exception, so
                        # we should log it now.  But ideally no
                        # exception should reach this level
                        print >> sys.stderr, (
                            'Unexpected exception in worker %r' % runnable)
                        traceback.print_exc()
                    if thread_id in self.dying_threads:
                        # That last exception was intended to kill me
                        break
                finally:
                    try:
                        del self.worker_tracker[thread_id]
                    except KeyError:
                        pass
                    sys.exc_clear()
                self.idle_workers.append(thread_id)
        finally:
            try:
                del self.worker_tracker[thread_id]
            except KeyError:
                pass
            try:
                self.idle_workers.remove(thread_id)
            except ValueError:
                pass
            try:
                self.workers.remove(thread_obj)
            except ValueError:
                pass
            try:
                del self.dying_threads[thread_id]
            except KeyError:
                pass
            if add_replacement_worker:
                self.add_worker_thread(message='Voluntary replacement for thread %s' % thread_id)

    def shutdown(self, force_quit_timeout=0):
        """
        Shutdown the queue (after finishing any pending requests).
        """
        self.logger.info('Shutting down threadpool')
        # Add a shutdown request for every worker
        for i in range(len(self.workers)):
            self.queue.put(ThreadPool.SHUTDOWN)
        # Wait for each thread to terminate
        hung_workers = []
        for worker in self.workers:
            worker.join(0.5)
            if worker.isAlive():
                hung_workers.append(worker)
        zombies = []
        for thread_id in self.dying_threads:
            if self.thread_exists(thread_id):
                zombies.append(thread_id)
        if hung_workers or zombies:
            self.logger.info("%s workers didn't stop properly, and %s zombies",
                             len(hung_workers), len(zombies))
            if hung_workers:
                for worker in hung_workers:
                    self.kill_worker(worker.thread_id)
                self.logger.info('Workers killed forcefully')
            if force_quit_timeout:
                hung = []
                timed_out = False
                need_force_quit = bool(zombies)
                for workers in self.workers:
                    if not timed_out and worker.isAlive():
                        timed_out = True
                        worker.join(force_quit_timeout)
                    if worker.isAlive():
                        print "Worker %s won't die" % worker
                        need_force_quit = True
                if need_force_quit:
                    import atexit
                    # Remove the threading atexit callback
                    for callback in list(atexit._exithandlers):
                        func = getattr(callback[0], 'im_func', None)
                        if not func:
                            continue
                        globs = getattr(func, 'func_globals', {})
                        mod = globs.get('__name__')
                        if mod == 'threading':
                            atexit._exithandlers.remove(callback)
                    atexit._run_exitfuncs()
                    print 'Forcefully exiting process'
                    os._exit(3)
                else:
                    self.logger.info('All workers eventually killed')
        else:
            self.logger.info('All workers stopped')

    def notify_problem(self, msg, subject=None, spawn_thread=True):
        """
        Called when there's a substantial problem.  msg contains the
        body of the notification, subject the summary.

        If spawn_thread is true, then the email will be send in
        another thread (so this doesn't block).
        """
        if not self.error_email:
            return
        if spawn_thread:
            t = threading.Thread(
                target=self.notify_problem,
                args=(msg, subject, False))
            t.start()
            return
        from_address = 'errors@localhost'
        if not subject:
            subject = msg.strip().splitlines()[0]
            subject = subject[:50]
            subject = '[http threadpool] %s' % subject
        headers = [
            "To: %s" % self.error_email,
            "From: %s" % from_address,
            "Subject: %s" % subject,
            ]
        try:
            system = ' '.join(os.uname())
        except:
            system = '(unknown)'
        body = (
            "An error has occurred in the paste.httpserver.ThreadPool\n"
            "Error:\n"
            "  %(msg)s\n"
            "Occurred at: %(time)s\n"
            "PID: %(pid)s\n"
            "System: %(system)s\n"
            "Server .py file: %(file)s\n"
            % dict(msg=msg,
                   time=time.strftime("%c"),
                   pid=os.getpid(),
                   system=system,
                   file=os.path.abspath(__file__),
                   ))
        message = '\n'.join(headers) + "\n\n" + body
        import smtplib
        server = smtplib.SMTP('localhost')
        error_emails = [
            e.strip() for e in self.error_email.split(",")
            if e.strip()]
        server.sendmail(from_address, error_emails, message)
        server.quit()
        print 'email sent to', error_emails, message

class ThreadPoolMixIn(object):
    """
    Mix-in class to process requests from a thread pool
    """
    def __init__(self, nworkers, daemon=False, **threadpool_options):
        # Create and start the workers
        self.running = True
        assert nworkers > 0, "ThreadPoolMixIn servers must have at least one worker"
        self.thread_pool = ThreadPool(
            nworkers,
            "ThreadPoolMixIn HTTP server on %s:%d"
            % (self.server_name, self.server_port),
            daemon,
            **threadpool_options)

    def process_request(self, request, client_address):
        """
        Queue the request to be processed by on of the thread pool threads
        """
        # This sets the socket to blocking mode (and no timeout) since it
        # may take the thread pool a little while to get back to it. (This
        # is the default but since we set a timeout on the parent socket so
        # that we can trap interrupts we need to restore this,.)
        request.setblocking(1)
        # Queue processing of the request
        self.thread_pool.add_task(
             lambda: self.process_request_in_thread(request, client_address))

    def handle_error(self, request, client_address):
        exc_class, exc, tb = sys.exc_info()
        if exc_class is ServerExit:
            # This is actually a request to stop the server
            raise
        return super(ThreadPoolMixIn, self).handle_error(request, client_address)

    def process_request_in_thread(self, request, client_address):
        """
        The worker thread should call back here to do the rest of the
        request processing. Error handling normaller done in 'handle_request'
        must be done here.
        """
        try:
            self.finish_request(request, client_address)
            self.close_request(request)
        except:
            self.handle_error(request, client_address)
            self.close_request(request)
            exc = sys.exc_info()[1]
            if isinstance(exc, (MemoryError, KeyboardInterrupt)):
                raise

    def serve_forever(self):
        """
        Overrides `serve_forever` to shut the threadpool down cleanly.
        """
        try:
            while self.running:
                try:
                    self.handle_request()
                except socket.timeout:
                    # Timeout is expected, gives interrupts a chance to
                    # propogate, just keep handling
                    pass
        finally:
            self.thread_pool.shutdown()

    def server_activate(self):
        """
        Overrides server_activate to set timeout on our listener socket.
        """
        # We set the timeout here so that we can trap interrupts on windows
        self.socket.settimeout(1)

    def server_close(self):
        """
        Finish pending requests and shutdown the server.
        """
        self.running = False
        self.socket.close()
        self.thread_pool.shutdown(60)

class WSGIServerBase(SecureHTTPServer):
    def __init__(self, wsgi_application, server_address,
                 RequestHandlerClass=None, ssl_context=None,
                 request_queue_size=None):
        SecureHTTPServer.__init__(self, server_address,
                                  RequestHandlerClass, ssl_context,
                                  request_queue_size=request_queue_size)
        self.wsgi_application = wsgi_application
        self.wsgi_socket_timeout = None

    def get_request(self):
        # If there is a socket_timeout, set it on the accepted
        (conn,info) = SecureHTTPServer.get_request(self)
        if self.wsgi_socket_timeout:
            conn.settimeout(self.wsgi_socket_timeout)
        return (conn, info)

class WSGIServer(ThreadingMixIn, WSGIServerBase):
    daemon_threads = False

class WSGIThreadPoolServer(ThreadPoolMixIn, WSGIServerBase):
    def __init__(self, wsgi_application, server_address,
                 RequestHandlerClass=None, ssl_context=None,
                 nworkers=10, daemon_threads=False,
                 threadpool_options=None, request_queue_size=None):
        WSGIServerBase.__init__(self, wsgi_application, server_address,
                                RequestHandlerClass, ssl_context,
                                request_queue_size=request_queue_size)
        if threadpool_options is None:
            threadpool_options = {}
        ThreadPoolMixIn.__init__(self, nworkers, daemon_threads,
                                 **threadpool_options)

class ServerExit(SystemExit):
    """
    Raised to tell the server to really exit (SystemExit is normally
    caught)
    """

def serve(application, host=None, port=None, handler=None, ssl_pem=None,
          ssl_context=None, server_version=None, protocol_version=None,
          start_loop=True, daemon_threads=None, socket_timeout=None,
          use_threadpool=None, threadpool_workers=10,
          threadpool_options=None, request_queue_size=5):
    """
    Serves your ``application`` over HTTP(S) via WSGI interface

    ``host``

        This is the ipaddress to bind to (or a hostname if your
        nameserver is properly configured).  This defaults to
        127.0.0.1, which is not a public interface.

    ``port``

        The port to run on, defaults to 8080 for HTTP, or 4443 for
        HTTPS. This can be a string or an integer value.

    ``handler``

        This is the HTTP request handler to use, it defaults to
        ``WSGIHandler`` in this module.

    ``ssl_pem``

        This an optional SSL certificate file (via OpenSSL). You can
        supply ``*`` and a development-only certificate will be
        created for you, or you can generate a self-signed test PEM
        certificate file as follows::

            $ openssl genrsa 1024 > host.key
            $ chmod 400 host.key
            $ openssl req -new -x509 -nodes -sha1 -days 365  \\
                          -key host.key > host.cert
            $ cat host.cert host.key > host.pem
            $ chmod 400 host.pem

    ``ssl_context``

        This an optional SSL context object for the server.  A SSL
        context will be automatically constructed for you if you supply
        ``ssl_pem``.  Supply this to use a context of your own
        construction.

    ``server_version``

        The version of the server as reported in HTTP response line. This
        defaults to something like "PasteWSGIServer/0.5".  Many servers
        hide their code-base identity with a name like 'Amnesiac/1.0'

    ``protocol_version``

        This sets the protocol used by the server, by default
        ``HTTP/1.0``. There is some support for ``HTTP/1.1``, which
        defaults to nicer keep-alive connections.  This server supports
        ``100 Continue``, but does not yet support HTTP/1.1 Chunked
        Encoding. Hence, if you use HTTP/1.1, you're somewhat in error
        since chunked coding is a mandatory requirement of a HTTP/1.1
        server.  If you specify HTTP/1.1, every response *must* have a
        ``Content-Length`` and you must be careful not to read past the
        end of the socket.

    ``start_loop``

        This specifies if the server loop (aka ``server.serve_forever()``)
        should be called; it defaults to ``True``.

    ``daemon_threads``

        This flag specifies if when your webserver terminates all
        in-progress client connections should be droppped.  It defaults
        to ``False``.   You might want to set this to ``True`` if you
        are using ``HTTP/1.1`` and don't set a ``socket_timeout``.

    ``socket_timeout``

        This specifies the maximum amount of time that a connection to a
        given client will be kept open.  At this time, it is a rude
        disconnect, but at a later time it might follow the RFC a bit
        more closely.

    ``use_threadpool``

        Server requests from a pool of worker threads (``threadpool_workers``)
        rather than creating a new thread for each request. This can
        substantially reduce latency since there is a high cost associated
        with thread creation.

    ``threadpool_workers``

        Number of worker threads to create when ``use_threadpool`` is true. This
        can be a string or an integer value.

    ``threadpool_options``

        A dictionary of options to be used when instantiating the
        threadpool.  See paste.httpserver.ThreadPool for specific
        options (``threadpool_workers`` is a specific option that can
        also go here).

    ``request_queue_size``

        The 'backlog' argument to socket.listen(); specifies the
        maximum number of queued connections.

    """
    is_ssl = False
    if ssl_pem or ssl_context:
        assert SSL, "pyOpenSSL is not installed"
        is_ssl = True
        port = int(port or 4443)
        if not ssl_context:
            if ssl_pem == '*':
                ssl_context = _auto_ssl_context()
            else:
                ssl_context = SSL.Context(SSL.SSLv23_METHOD)
                ssl_context.use_privatekey_file(ssl_pem)
                ssl_context.use_certificate_chain_file(ssl_pem)

    host = host or '127.0.0.1'
    if port is None:
        if ':' in host:
            host, port = host.split(':', 1)
        else:
            port = 8080
    server_address = (host, int(port))

    if not handler:
        handler = WSGIHandler
    if server_version:
        handler.server_version = server_version
        handler.sys_version = None
    if protocol_version:
        assert protocol_version in ('HTTP/0.9', 'HTTP/1.0', 'HTTP/1.1')
        handler.protocol_version = protocol_version

    if use_threadpool is None:
        use_threadpool = True

    if converters.asbool(use_threadpool):
        server = WSGIThreadPoolServer(application, server_address, handler,
                                      ssl_context, int(threadpool_workers),
                                      daemon_threads,
                                      threadpool_options=threadpool_options,
                                      request_queue_size=request_queue_size)
    else:
        server = WSGIServer(application, server_address, handler, ssl_context,
                            request_queue_size=request_queue_size)
        if daemon_threads:
            server.daemon_threads = daemon_threads

    if socket_timeout:
        server.wsgi_socket_timeout = int(socket_timeout)

    if converters.asbool(start_loop):
        protocol = is_ssl and 'https' or 'http'
        host, port = server.server_address[:2]
        if host == '0.0.0.0':
            print 'serving on 0.0.0.0:%s view at %s://127.0.0.1:%s' % \
                (port, protocol, port)
        else:
            print "serving on %s://%s:%s" % (protocol, host, port)
        try:
            server.serve_forever()
        except KeyboardInterrupt:
            # allow CTRL+C to shutdown
            pass
    return server

# For paste.deploy server instantiation (egg:Paste#http)
# Note: this gets a separate function because it has to expect string
# arguments (though that's not much of an issue yet, ever?)
def server_runner(wsgi_app, global_conf, **kwargs):
    from paste.deploy.converters import asbool
    for name in ['port', 'socket_timeout', 'threadpool_workers',
                 'threadpool_hung_thread_limit',
                 'threadpool_kill_thread_limit',
                 'threadpool_dying_limit', 'threadpool_spawn_if_under',
                 'threadpool_max_zombie_threads_before_die',
                 'threadpool_hung_check_period',
                 'threadpool_max_requests', 'request_queue_size']:
        if name in kwargs:
            kwargs[name] = int(kwargs[name])
    for name in ['use_threadpool', 'daemon_threads']:
        if name in kwargs:
            kwargs[name] = asbool(kwargs[name])
    threadpool_options = {}
    for name, value in kwargs.items():
        if name.startswith('threadpool_') and name != 'threadpool_workers':
            threadpool_options[name[len('threadpool_'):]] = value
            del kwargs[name]
    if ('error_email' not in threadpool_options
        and 'error_email' in global_conf):
        threadpool_options['error_email'] = global_conf['error_email']
    kwargs['threadpool_options'] = threadpool_options
    serve(wsgi_app, **kwargs)

server_runner.__doc__ = (serve.__doc__ or '') + """

    You can also set these threadpool options:

    ``threadpool_max_requests``:

        The maximum number of requests a worker thread will process
        before dying (and replacing itself with a new worker thread).
        Default 100.

    ``threadpool_hung_thread_limit``:

        The number of seconds a thread can work on a task before it is
        considered hung (stuck).  Default 30 seconds.

    ``threadpool_kill_thread_limit``:

        The number of seconds a thread can work before you should kill it
        (assuming it will never finish).  Default 600 seconds (10 minutes).

    ``threadpool_dying_limit``:

        The length of time after killing a thread that it should actually
        disappear.  If it lives longer than this, it is considered a
        "zombie".  Note that even in easy situations killing a thread can
        be very slow.  Default 300 seconds (5 minutes).

    ``threadpool_spawn_if_under``:

        If there are no idle threads and a request comes in, and there are
        less than this number of *busy* threads, then add workers to the
        pool.  Busy threads are threads that have taken less than
        ``threadpool_hung_thread_limit`` seconds so far.  So if you get
        *lots* of requests but they complete in a reasonable amount of time,
        the requests will simply queue up (adding more threads probably
        wouldn't speed them up).  But if you have lots of hung threads and
        one more request comes in, this will add workers to handle it.
        Default 5.

    ``threadpool_max_zombie_threads_before_die``:

        If there are more zombies than this, just kill the process.  This is
        only good if you have a monitor that will automatically restart
        the server.  This can clean up the mess.  Default 0 (disabled).

    `threadpool_hung_check_period``:

        Every X requests, check for hung threads that need to be killed,
        or for zombie threads that should cause a restart.  Default 100
        requests.

    ``threadpool_logger``:

        Logging messages will go the logger named here.

    ``threadpool_error_email`` (or global ``error_email`` setting):

        When threads are killed or the process restarted, this email
        address will be contacted (using an SMTP server on localhost).

"""


if __name__ == '__main__':
    from paste.wsgilib import dump_environ
    #serve(dump_environ, ssl_pem="test.pem")
    serve(dump_environ, server_version="Wombles/1.0",
          protocol_version="HTTP/1.1", port="8888")
Paste-1.7.5.1/paste/progress.py0000775000175000017500000001774211354703364014723 0ustar  ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
# (c) 2005 Clark C. Evans
# This module is part of the Python Paste Project and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php
# This code was written with funding by http://prometheusresearch.com
"""
Upload Progress Monitor

This is a WSGI middleware component which monitors the status of files
being uploaded.  It includes a small query application which will return
a list of all files being uploaded by particular session/user.

>>> from paste.httpserver import serve
>>> from paste.urlmap import URLMap
>>> from paste.auth.basic import AuthBasicHandler
>>> from paste.debug.debugapp import SlowConsumer, SimpleApplication
>>> # from paste.progress import *
>>> realm = 'Test Realm'
>>> def authfunc(username, password):
...     return username == password
>>> map = URLMap({})
>>> ups = UploadProgressMonitor(map, threshold=1024)
>>> map['/upload'] = SlowConsumer()
>>> map['/simple'] = SimpleApplication()
>>> map['/report'] = UploadProgressReporter(ups)
>>> serve(AuthBasicHandler(ups, realm, authfunc))
serving on...

.. note::

   This is experimental, and will change in the future.
"""
import time
from paste.wsgilib import catch_errors

DEFAULT_THRESHOLD = 1024 * 1024  # one megabyte
DEFAULT_TIMEOUT   = 60*5         # five minutes
ENVIRON_RECEIVED  = 'paste.bytes_received'
REQUEST_STARTED   = 'paste.request_started'
REQUEST_FINISHED  = 'paste.request_finished'

class _ProgressFile(object):
    """
    This is the input-file wrapper used to record the number of
    ``paste.bytes_received`` for the given request.
    """

    def __init__(self, environ, rfile):
        self._ProgressFile_environ = environ
        self._ProgressFile_rfile   = rfile
        self.flush = rfile.flush
        self.write = rfile.write
        self.writelines = rfile.writelines

    def __iter__(self):
        environ = self._ProgressFile_environ
        riter = iter(self._ProgressFile_rfile)
        def iterwrap():
            for chunk in riter:
                environ[ENVIRON_RECEIVED] += len(chunk)
                yield chunk
        return iter(iterwrap)

    def read(self, size=-1):
        chunk = self._ProgressFile_rfile.read(size)
        self._ProgressFile_environ[ENVIRON_RECEIVED] += len(chunk)
        return chunk

    def readline(self):
        chunk = self._ProgressFile_rfile.readline()
        self._ProgressFile_environ[ENVIRON_RECEIVED] += len(chunk)
        return chunk

    def readlines(self, hint=None):
        chunk = self._ProgressFile_rfile.readlines(hint)
        self._ProgressFile_environ[ENVIRON_RECEIVED] += len(chunk)
        return chunk

class UploadProgressMonitor(object):
    """
    monitors and reports on the status of uploads in progress

    Parameters:

        ``application``

            This is the next application in the WSGI stack.

        ``threshold``

            This is the size in bytes that is needed for the
            upload to be included in the monitor.

        ``timeout``

            This is the amount of time (in seconds) that a upload
            remains in the monitor after it has finished.

    Methods:

        ``uploads()``

            This returns a list of ``environ`` dict objects for each
            upload being currently monitored, or finished but whose time
            has not yet expired.

    For each request ``environ`` that is monitored, there are several
    variables that are stored:

        ``paste.bytes_received``

            This is the total number of bytes received for the given
            request; it can be compared with ``CONTENT_LENGTH`` to
            build a percentage complete.  This is an integer value.

        ``paste.request_started``

            This is the time (in seconds) when the request was started
            as obtained from ``time.time()``.  One would want to format
            this for presentation to the user, if necessary.

        ``paste.request_finished``

            This is the time (in seconds) when the request was finished,
            canceled, or otherwise disconnected.  This is None while
            the given upload is still in-progress.

    TODO: turn monitor into a queue and purge queue of finished
          requests that have passed the timeout period.
    """
    def __init__(self, application, threshold=None, timeout=None):
        self.application = application
        self.threshold = threshold or DEFAULT_THRESHOLD
        self.timeout   = timeout   or DEFAULT_TIMEOUT
        self.monitor   = []

    def __call__(self, environ, start_response):
        length = environ.get('CONTENT_LENGTH', 0)
        if length and int(length) > self.threshold:
            # replace input file object
            self.monitor.append(environ)
            environ[ENVIRON_RECEIVED] = 0
            environ[REQUEST_STARTED] = time.time()
            environ[REQUEST_FINISHED] = None
            environ['wsgi.input'] = \
                _ProgressFile(environ, environ['wsgi.input'])
            def finalizer(exc_info=None):
                environ[REQUEST_FINISHED] = time.time()
            return catch_errors(self.application, environ,
                       start_response, finalizer, finalizer)
        return self.application(environ, start_response)

    def uploads(self):
        return self.monitor

class UploadProgressReporter(object):
    """
    reports on the progress of uploads for a given user

    This reporter returns a JSON file (for use in AJAX) listing the
    uploads in progress for the given user.  By default, this reporter
    uses the ``REMOTE_USER`` environment to compare between the current
    request and uploads in-progress.  If they match, then a response
    record is formed.

        ``match()``

            This member function can be overriden to provide alternative
            matching criteria.  It takes two environments, the first
            is the current request, the second is a current upload.

        ``report()``

            This member function takes an environment and builds a
            ``dict`` that will be used to create a JSON mapping for
            the given upload.  By default, this just includes the
            percent complete and the request url.

    """
    def __init__(self, monitor):
        self.monitor   = monitor

    def match(self, search_environ, upload_environ):
        if search_environ.get('REMOTE_USER', None) == \
           upload_environ.get('REMOTE_USER', 0):
            return True
        return False

    def report(self, environ):
        retval = { 'started': time.strftime("%Y-%m-%d %H:%M:%S",
                                time.gmtime(environ[REQUEST_STARTED])),
                   'finished': '',
                   'content_length': environ.get('CONTENT_LENGTH'),
                   'bytes_received': environ[ENVIRON_RECEIVED],
                   'path_info': environ.get('PATH_INFO',''),
                   'query_string': environ.get('QUERY_STRING','')}
        finished = environ[REQUEST_FINISHED]
        if finished:
            retval['finished'] = time.strftime("%Y:%m:%d %H:%M:%S",
                                               time.gmtime(finished))
        return retval

    def __call__(self, environ, start_response):
        body = []
        for map in [self.report(env) for env in self.monitor.uploads()
                                             if self.match(environ, env)]:
            parts = []
            for k, v in map.items():
                v = str(v).replace("\\", "\\\\").replace('"', '\\"')
                parts.append('%s: "%s"' % (k, v))
            body.append("{ %s }" % ", ".join(parts))
        body = "[ %s ]" % ", ".join(body)
        start_response("200 OK", [('Content-Type', 'text/plain'),
                                  ('Content-Length', len(body))])
        return [body]

__all__ = ['UploadProgressMonitor', 'UploadProgressReporter']

if "__main__" == __name__:
    import doctest
    doctest.testmod(optionflags=doctest.ELLIPSIS)
Paste-1.7.5.1/paste/__init__.py0000664000175000017500000000105011354703364014574 0ustar  ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
try:
    import pkg_resources
    pkg_resources.declare_namespace(__name__)
except ImportError:
    # don't prevent use of paste if pkg_resources isn't installed
    from pkgutil import extend_path
    __path__ = extend_path(__path__, __name__) 

try:
    import modulefinder
except ImportError:
    pass
else:
    for p in __path__:
        modulefinder.AddPackagePath(__name__, p)
Paste-1.7.5.1/paste/fixture.py0000664000175000017500000016133311443715765014545 0ustar  ianbianb# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
"""
Routines for testing WSGI applications.

Most interesting is the `TestApp `_
for testing WSGI applications, and the `TestFileEnvironment
`_ class for testing the
effects of command-line scripts.
"""

import sys
import random
import urllib
import urlparse
import mimetypes
import time
import cgi
import os
import shutil
import smtplib
import shlex
from Cookie import BaseCookie
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO
import re
try:
    import subprocess
except ImportError:
    from paste.util import subprocess24 as subprocess

from paste import wsgilib
from paste import lint
from paste.response import HeaderDict

def tempnam_no_warning(*args):
    """
    An os.tempnam with the warning turned off, because sometimes
    you just need to use this and don't care about the stupid
    security warning.
    """
    return os.tempnam(*args)

class NoDefault(object):
    pass

def sorted(l):
    l = list(l)
    l.sort()
    return l

class Dummy_smtplib(object):

    existing = None

    def __init__(self, server):
        import warnings
        warnings.warn(
            'Dummy_smtplib is not maintained and is deprecated',
            DeprecationWarning, 2)
        assert not self.existing, (
            "smtplib.SMTP() called again before Dummy_smtplib.existing.reset() "
            "called.")
        self.server = server
        self.open = True
        self.__class__.existing = self

    def quit(self):
        assert self.open, (
            "Called %s.quit() twice" % self)
        self.open = False

    def sendmail(self, from_address, to_addresses, msg):
        self.from_address = from_address
        self.to_addresses = to_addresses
        self.message = msg

    def install(cls):
        smtplib.SMTP = cls

    install = classmethod(install)

    def reset(self):
        assert not self.open, (
            "SMTP connection not quit")
        self.__class__.existing = None

class AppError(Exception):
    pass

class TestApp(object):

    # for py.test
    disabled = True

    def __init__(self, app, namespace=None, relative_to=None,
                 extra_environ=None, pre_request_hook=None,
                 post_request_hook=None):
        """
        Wraps a WSGI application in a more convenient interface for
        testing.

        ``app`` may be an application, or a Paste Deploy app
        URI, like ``'config:filename.ini#test'``.

        ``namespace`` is a dictionary that will be written to (if
        provided).  This can be used with doctest or some other
        system, and the variable ``res`` will be assigned everytime
        you make a request (instead of returning the request).

        ``relative_to`` is a directory, and filenames used for file
        uploads are calculated relative to this.  Also ``config:``
        URIs that aren't absolute.

        ``extra_environ`` is a dictionary of values that should go
        into the environment for each request.  These can provide a
        communication channel with the application.

        ``pre_request_hook`` is a function to be called prior to
        making requests (such as ``post`` or ``get``). This function
        must take one argument (the instance of the TestApp).

        ``post_request_hook`` is a function, similar to
        ``pre_request_hook``, to be called after requests are made.
        """
        if isinstance(app, (str, unicode)):
            from paste.deploy import loadapp
            # @@: Should pick up relative_to from calling module's
            # __file__
            app = loadapp(app, relative_to=relative_to)
        self.app = app
        self.namespace = namespace
        self.relative_to = relative_to
        if extra_environ is None:
            extra_environ = {}
        self.extra_environ = extra_environ
        self.pre_request_hook = pre_request_hook
        self.post_request_hook = post_request_hook
        self.reset()

    def reset(self):
        """
        Resets the state of the application; currently just clears
        saved cookies.
        """
        self.cookies = {}

    def _make_environ(self):
        environ = self.extra_environ.copy()
        environ['paste.throw_errors'] = True
        return environ

    def get(self, url, params=None, headers=None, extra_environ=None,
            status=None, expect_errors=False):
        """
        Get the given url (well, actually a path like
        ``'/page.html'``).

        ``params``:
            A query string, or a dictionary that will be encoded
            into a query string.  You may also include a query
            string on the ``url``.

        ``headers``:
            A dictionary of extra headers to send.

        ``extra_environ``:
            A dictionary of environmental variables that should
            be added to the request.

        ``status``:
            The integer status code you expect (if not 200 or 3xx).
            If you expect a 404 response, for instance, you must give
            ``status=404`` or it will be an error.  You can also give
            a wildcard, like ``'3*'`` or ``'*'``.

        ``expect_errors``:
            If this is not true, then if anything is written to
            ``wsgi.errors`` it will be an error.  If it is true, then
            non-200/3xx responses are also okay.

        Returns a `response object
        `_
        """
        if extra_environ is None:
            extra_environ = {}
        # Hide from py.test:
        __tracebackhide__ = True
        if params:
            if not isinstance(params, (str, unicode)):
                params = urllib.urlencode(params, doseq=True)
            if '?' in url:
                url += '&'
            else:
                url += '?'
            url += params
        environ = self._make_environ()
        url = str(url)
        if '?' in url:
            url, environ['QUERY_STRING'] = url.split('?', 1)
        else:
            environ['QUERY_STRING'] = ''
        self._set_headers(headers, environ)
        environ.update(extra_environ)
        req = TestRequest(url, environ, expect_errors)
        return self.do_request(req, status=status)

    def _gen_request(self, method, url, params='', headers=None, extra_environ=None,
             status=None, upload_files=None, expect_errors=False):
        """
        Do a generic request.
        """
        if headers is None:
            headers = {}
        if extra_environ is None:
            extra_environ = {}
        environ = self._make_environ()
        # @@: Should this be all non-strings?
        if isinstance(params, (list, tuple, dict)):
            params = urllib.urlencode(params)
        if hasattr(params, 'items'):
            # Some other multi-dict like format
            params = urllib.urlencode(params.items())
        if upload_files:
            params = cgi.parse_qsl(params, keep_blank_values=True)
            content_type, params = self.encode_multipart(
                params, upload_files)
            environ['CONTENT_TYPE'] = content_type
        elif params:
            environ.setdefault('CONTENT_TYPE', 'application/x-www-form-urlencoded')
        if '?' in url:
            url, environ['QUERY_STRING'] = url.split('?', 1)
        else:
            environ['QUERY_STRING'] = ''
        environ['CONTENT_LENGTH'] = str(len(params))
        environ['REQUEST_METHOD'] = method
        environ['wsgi.input'] = StringIO(params)
        self._set_headers(headers, environ)
        environ.update(extra_environ)
        req = TestRequest(url, environ, expect_errors)
        return self.do_request(req, status=status)

    def post(self, url, params='', headers=None, extra_environ=None,
             status=None, upload_files=None, expect_errors=False):
        """
        Do a POST request.  Very like the ``.get()`` method.
        ``params`` are put in the body of the request.

        ``upload_files`` is for file uploads.  It should be a list of
        ``[(fieldname, filename, file_content)]``.  You can also use
        just ``[(fieldname, filename)]`` and the file content will be
        read from disk.

        Returns a `response object
        `_
        """
        return self._gen_request('POST', url, params=params, headers=headers,
                                 extra_environ=extra_environ,status=status,
                                 upload_files=upload_files,
                                 expect_errors=expect_errors)

    def put(self, url, params='', headers=None, extra_environ=None,
             status=None, upload_files=None, expect_errors=False):
        """
        Do a PUT request.  Very like the ``.get()`` method.
        ``params`` are put in the body of the request.

        ``upload_files`` is for file uploads.  It should be a list of
        ``[(fieldname, filename, file_content)]``.  You can also use
        just ``[(fieldname, filename)]`` and the file content will be
        read from disk.

        Returns a `response object
        `_
        """
        return self._gen_request('PUT', url, params=params, headers=headers,
                                 extra_environ=extra_environ,status=status,
                                 upload_files=upload_files,
                                 expect_errors=expect_errors)

    def delete(self, url, params='', headers=None, extra_environ=None,
               status=None, expect_errors=False):
        """
        Do a DELETE request.  Very like the ``.get()`` method.
        ``params`` are put in the body of the request.

        Returns a `response object
        `_
        """
        return self._gen_request('DELETE', url, params=params, headers=headers,
                                 extra_environ=extra_environ,status=status,
                                 upload_files=None, expect_errors=expect_errors)




    def _set_headers(self, headers, environ):
        """
        Turn any headers into environ variables
        """
        if not headers:
            return
        for header, value in headers.items():
            if header.lower() == 'content-type':
                var = 'CONTENT_TYPE'
            elif header.lower() == 'content-length':
                var = 'CONTENT_LENGTH'
            else:
                var = 'HTTP_%s' % header.replace('-', '_').upper()
            environ[var] = value

    def encode_multipart(self, params, files):
        """
        Encodes a set of parameters (typically a name/value list) and
        a set of files (a list of (name, filename, file_body)) into a
        typical POST body, returning the (content_type, body).
        """
        boundary = '----------a_BoUnDaRy%s$' % random.random()
        lines = []
        for key, value in params:
            lines.append('--'+boundary)
            lines.append('Content-Disposition: form-data; name="%s"' % key)
            lines.append('')
            lines.append(value)
        for file_info in files:
            key, filename, value = self._get_file_info(file_info)
            lines.append('--'+boundary)
            lines.append('Content-Disposition: form-data; name="%s"; filename="%s"'
                         % (key, filename))
            fcontent = mimetypes.guess_type(filename)[0]
            lines.append('Content-Type: %s' %
                         fcontent or 'application/octet-stream')
            lines.append('')
            lines.append(value)
        lines.append('--' + boundary + '--')
        lines.append('')
        body = '\r\n'.join(lines)
        content_type = 'multipart/form-data; boundary=%s' % boundary
        return content_type, body

    def _get_file_info(self, file_info):
        if len(file_info) == 2:
            # It only has a filename
            filename = file_info[1]
            if self.relative_to:
                filename = os.path.join(self.relative_to, filename)
            f = open(filename, 'rb')
            content = f.read()
            f.close()
            return (file_info[0], filename, content)
        elif len(file_info) == 3:
            return file_info
        else:
            raise ValueError(
                "upload_files need to be a list of tuples of (fieldname, "
                "filename, filecontent) or (fieldname, filename); "
                "you gave: %r"
                % repr(file_info)[:100])

    def do_request(self, req, status):
        """
        Executes the given request (``req``), with the expected
        ``status``.  Generally ``.get()`` and ``.post()`` are used
        instead.
        """
        if self.pre_request_hook:
            self.pre_request_hook(self)
        __tracebackhide__ = True
        if self.cookies:
            c = BaseCookie()
            for name, value in self.cookies.items():
                c[name] = value
            hc = '; '.join(['='.join([m.key, m.value]) for m in c.values()])
            req.environ['HTTP_COOKIE'] = hc
        req.environ['paste.testing'] = True
        req.environ['paste.testing_variables'] = {}
        app = lint.middleware(self.app)
        old_stdout = sys.stdout
        out = CaptureStdout(old_stdout)
        try:
            sys.stdout = out
            start_time = time.time()
            raise_on_wsgi_error = not req.expect_errors
            raw_res = wsgilib.raw_interactive(
                app, req.url,
                raise_on_wsgi_error=raise_on_wsgi_error,
                **req.environ)
            end_time = time.time()
        finally:
            sys.stdout = old_stdout
            sys.stderr.write(out.getvalue())
        res = self._make_response(raw_res, end_time - start_time)
        res.request = req
        for name, value in req.environ['paste.testing_variables'].items():
            if hasattr(res, name):
                raise ValueError(
                    "paste.testing_variables contains the variable %r, but "
                    "the response object already has an attribute by that "
                    "name" % name)
            setattr(res, name, value)
        if self.namespace is not None:
            self.namespace['res'] = res
        if not req.expect_errors:
            self._check_status(status, res)
            self._check_errors(res)
        res.cookies_set = {}
        for header in res.all_headers('set-cookie'):
            c = BaseCookie(header)
            for key, morsel in c.items():
                self.cookies[key] = morsel.value
                res.cookies_set[key] = morsel.value
        if self.post_request_hook:
            self.post_request_hook(self)
        if self.namespace is None:
            # It's annoying to return the response in doctests, as it'll
            # be printed, so we only return it is we couldn't assign
            # it anywhere
            return res

    def _check_status(self, status, res):
        __tracebackhide__ = True
        if status == '*':
            return
        if isinstance(status, (list, tuple)):
            if res.status not in status:
                raise AppError(
                    "Bad response: %s (not one of %s for %s)\n%s"
                    % (res.full_status, ', '.join(map(str, status)),
                       res.request.url, res.body))
            return
        if status is None:
            if res.status >= 200 and res.status < 400:
                return
            raise AppError(
                "Bad response: %s (not 200 OK or 3xx redirect for %s)\n%s"
                % (res.full_status, res.request.url,
                   res.body))
        if status != res.status:
            raise AppError(
                "Bad response: %s (not %s)" % (res.full_status, status))

    def _check_errors(self, res):
        if res.errors:
            raise AppError(
                "Application had errors logged:\n%s" % res.errors)

    def _make_response(self, (status, headers, body, errors), total_time):
        return TestResponse(self, status, headers, body, errors,
                            total_time)

class CaptureStdout(object):

    def __init__(self, actual):
        self.captured = StringIO()
        self.actual = actual

    def write(self, s):
        self.captured.write(s)
        self.actual.write(s)

    def flush(self):
        self.actual.flush()

    def writelines(self, lines):
        for item in lines:
            self.write(item)

    def getvalue(self):
        return self.captured.getvalue()

class TestResponse(object):

    # for py.test
    disabled = True

    """
    Instances of this class are return by `TestApp
    `_
    """

    def __init__(self, test_app, status, headers, body, errors,
                 total_time):
        self.test_app = test_app
        self.status = int(status.split()[0])
        self.full_status = status
        self.headers = headers
        self.header_dict = HeaderDict.fromlist(self.headers)
        self.body = body
        self.errors = errors
        self._normal_body = None
        self.time = total_time
        self._forms_indexed = None

    def forms__get(self):
        """
        Returns a dictionary of ``Form`` objects.  Indexes are both in
        order (from zero) and by form id (if the form is given an id).
        """
        if self._forms_indexed is None:
            self._parse_forms()
        return self._forms_indexed

    forms = property(forms__get,
                     doc="""
                     A list of 
s found on the page (instances of `Form `_) """) def form__get(self): forms = self.forms if not forms: raise TypeError( "You used response.form, but no forms exist") if 1 in forms: # There is more than one form raise TypeError( "You used response.form, but more than one form exists") return forms[0] form = property(form__get, doc=""" Returns a single `Form `_ instance; it is an error if there are multiple forms on the page. """) _tag_re = re.compile(r'<(/?)([:a-z0-9_\-]*)(.*?)>', re.S|re.I) def _parse_forms(self): forms = self._forms_indexed = {} form_texts = [] started = None for match in self._tag_re.finditer(self.body): end = match.group(1) == '/' tag = match.group(2).lower() if tag != 'form': continue if end: assert started, ( " unexpected at %s" % match.start()) form_texts.append(self.body[started:match.end()]) started = None else: assert not started, ( "Nested form tags at %s" % match.start()) started = match.start() assert not started, ( "Danging form: %r" % self.body[started:]) for i, text in enumerate(form_texts): form = Form(self, text) forms[i] = form if form.id: forms[form.id] = form def header(self, name, default=NoDefault): """ Returns the named header; an error if there is not exactly one matching header (unless you give a default -- always an error if there is more than one header) """ found = None for cur_name, value in self.headers: if cur_name.lower() == name.lower(): assert not found, ( "Ambiguous header: %s matches %r and %r" % (name, found, value)) found = value if found is None: if default is NoDefault: raise KeyError( "No header found: %r (from %s)" % (name, ', '.join([n for n, v in self.headers]))) else: return default return found def all_headers(self, name): """ Gets all headers by the ``name``, returns as a list """ found = [] for cur_name, value in self.headers: if cur_name.lower() == name.lower(): found.append(value) return found def follow(self, **kw): """ If this request is a redirect, follow that redirect. It is an error if this is not a redirect response. Returns another response object. """ assert self.status >= 300 and self.status < 400, ( "You can only follow redirect responses (not %s)" % self.full_status) location = self.header('location') type, rest = urllib.splittype(location) host, path = urllib.splithost(rest) # @@: We should test that it's not a remote redirect return self.test_app.get(location, **kw) def click(self, description=None, linkid=None, href=None, anchor=None, index=None, verbose=False): """ Click the link as described. Each of ``description``, ``linkid``, and ``url`` are *patterns*, meaning that they are either strings (regular expressions), compiled regular expressions (objects with a ``search`` method), or callables returning true or false. All the given patterns are ANDed together: * ``description`` is a pattern that matches the contents of the anchor (HTML and all -- everything between ```` and ````) * ``linkid`` is a pattern that matches the ``id`` attribute of the anchor. It will receive the empty string if no id is given. * ``href`` is a pattern that matches the ``href`` of the anchor; the literal content of that attribute, not the fully qualified attribute. * ``anchor`` is a pattern that matches the entire anchor, with its contents. If more than one link matches, then the ``index`` link is followed. If ``index`` is not given and more than one link matches, or if no link matches, then ``IndexError`` will be raised. If you give ``verbose`` then messages will be printed about each link, and why it does or doesn't match. If you use ``app.click(verbose=True)`` you'll see a list of all the links. You can use multiple criteria to essentially assert multiple aspects about the link, e.g., where the link's destination is. """ __tracebackhide__ = True found_html, found_desc, found_attrs = self._find_element( tag='a', href_attr='href', href_extract=None, content=description, id=linkid, href_pattern=href, html_pattern=anchor, index=index, verbose=verbose) return self.goto(found_attrs['uri']) def clickbutton(self, description=None, buttonid=None, href=None, button=None, index=None, verbose=False): """ Like ``.click()``, except looks for link-like buttons. This kind of button should look like ``