diff --git a/spyce-2.1/CHANGES b/spyce-2.1/CHANGES new file mode 100755 index 0000000000000000000000000000000000000000..726af01baadadddcc8659a8842dc39611991b950 --- /dev/null +++ b/spyce-2.1/CHANGES @@ -0,0 +1,747 @@ +Change Log +* denotes potential backwards incompatibility + +v2.1.3 + db module requires PK definition up-front (instead of erroring out later) + default login render tags give better feedback on unsuccessful login + raise NameError instead of string exception for invalid eval'd tag attributes + fixes for spyceProject + fix kwargs render-through on spy: list and table tags + fix auto-selecting of multiple defaults in compound controls + fix for class-based exceptions in old-style tag exception handlers + +v2.1.2 + fix support for threadless python builds + fix using compiled tags within other tags + fix f:textarea + fix spyceUtil.extractValue for incomplete dict work-alikes + fix session1 + fix tempdir on SF site + +v2.1 + * Python 2.3 required now! + + # New features # + SqlAlchemy integration + spy:login, spy:loginrequired tags + HandlerError integration with forms + :list, :bool, and :int hints to handler arg-marshalling + intelligent ValidationError + "data" option to f:select; added f:checkboxlist, f:radiolist + * removed "value" and "default" parameters to f:form + * changed semantics of "default" parameter to f:select + spy:ul, spy:ol, spy:dl, spy:table tags + new session module based on work by Conan Albrecht + pyweboff demo + * spy:parent searches for parent.spi in current directory before using config.defaultparent + * indexExtensions superceded by indexFiles in spyceconf + cached parent template lookup + improved ability to report certain types of Spyce syntax errors + Fixes for "special characters" in cookies + improved Programmatic Interface docs (Iwan Vosloo) + (f)cgi improvements by Pauli Virtanen + + # New & updated examples (see docs/eg.html) # + db.spy + formtag.spy + formintro.spy + login-optional.spy + login-required.spy + handlerintro.spy + handlervalidate.spy + handlervalidate2.spy + session2.spy + + # Deprecations / incompatibilities # + template module moved to contrib/ + flow control from "core" taglib moved to contrib/flow.py + * these will probably be gone entirely in Spyce 2.2 + * session [the old one] module renamed to contrib/session1.py + To make your old code happy, change [[.import name=session]] to [[.import name=session1 as=session]] + * Removed javascript generation done by f:submit; too "black magical" + * removed check_modules_no_restart option + * indexExtensions superceded by indexFiles in spyceconf + + # Bug fixes # + module/tag cache obeys check_mtime + fix f:date javascript + KeyboardInterrupt fix for spyceWWW in restart-for-changes mode + spyceNotFound error can be customized again when using Spyce webserver + fix for multiline string constants in files with weird line endings + + +v2.0.3 + fix pool bug if server not in concurrency=threaded mode; reported by "Dude" + documentation improvements + avoid stomping on user python modules named 'config'; reported by Betty Li + (John Reese) fixed bad interaction of "redirect if directory path doesn't + end in /" and "look for default index.$[index extensions] files" code + in Spyce webserver + default concurrency mode for Spyce webserver is 'threading' instead of None + spyceProject script to automate new-project creation; see + http://spyce.sourceforge.net/docs/doc-conf_next.html + +v2.0.2 + session_dir uses config.tmp by default if no directory is specified + fix for session_dir pickling on win32 + fix for fileCache pickling bug on win32 reported by Jaros³aw Zabiello + fix for sessions + handlers problem reported by Jonathan Taylor + * all module init() methods are now run before handlers are called. + +v2.0.1 + use tempfile.gettempdir() as default config.tmp + add originalsyspath to spyceconf + bugfixes for Active Tag compiler + portability & other bugfixes (John Reese) + +v2.0 + +Important new features: + new-style Active Tags + - http://spyce.sourceforge.net/docs/doc-tag_new2.html + OpenACS-like (Tiles-ish, for you JSP people) parent/child layout system + - roughly the same speed as include.spyce, so using one parent template + has about 1/2 the overhead as the old standard + two-includes-for-header-and-footer. + - http://spyce.sourceforge.net/docs/doc-tag_core.html#parent + Active Handlers + reusable components without the leaky abstraction of ASP.NET et al. + - http://spyce.sourceforge.net/docs/doc-lang_handlers.html + +Important incompatibilities with version 1.3: + * replaced spyce.conf with spyceconf.py; functionality retained + * request.get and request.post now return [] instead of None by default + if name is specified but nothing was submitted corresponding to it + - "if request.get('foo'):" will work the same since [] is false for boolean purposes + - motivation: now you can simply write "for item in request.get('foo')" + without worrying about TypeError + many absolute paths are relative to spyce docroot now + - redirect.internal, includes, error.setFileHandler + * this will break your code if you were using absolute filesystem paths; + relative paths will be unaffected + * active tag attributes are evaluated in spyceProcess context + - no more explicit tagcontext + +Changes since 2.0-beta: + - mod_python fix + - fix sometimes ignoring config.debug option + - updated concurrency protection for more general case of all spyce_cache accesses + - added indexExtensions option to spyceconf + - included code by John Reese + - added contrib/spyce.el (Emacs Spyce mode) + - added form:date tag + +Other changes: + new config options for spyceWWW mode (-l): + check_modules_and_restart (default), check_modules_no_restart + - detects changed python modules and restarts/reloads to apply the changes + add "class chunk" language construct: [[! or <%! + - enables inlining handler callbacks + - http://spyce.sourceforge.net/docs/doc-lang_chunkc.html + add globaltags configuration option + - no longer have to specify [[.taglib]] on each page + spyceCompile.py now optimizes away taglib loads if the tag isn't used on the page + - so no penalty for putting a lot of stuff into globaltags + add form:date tag + add scheduler [python] module + spyceTag export method allows sending variables back to spyceProcess context + * core:let only supports singleton form; core:unlet removed + pool module now works with non-spyceWWW deployments, via pickle + * writeExpr emits empty string for None by default + - so you can write [[= request['foo'] ]] instead of + [[= request.getpost1('foo', '') ]] + param|file request filters + evaluate imports in spyceConfig as the very last part of spyceServer __init__ + - so imported module can proceed to "import spyce; spyce.getServer()..." + added request.stack method + - infinite-loop checking for includes, parent templates + * removed include.fromFile field + added ipaddr WWW config option, for machines w/ multiple IPs + * spyceTagPlus.syntaxExists removed (equivalent checking now done automatically) + - only custom tag authors are affected by this + * spyce Modules are no longer allowed to redirect.internal during finish() + - it potentially prevented other modules' finish methods from being called + an active tag's directory is pushed onto sys.path for the duration of its use + - means tag can import from .py files in its directory no matter where installed + fix: apply --conf option to CGI mode + fix: follow symlinks to find correct mtime of .spy files to compare against cached + - also call normpath in FileHandler + - patch by Francisco Javier Cabello + fix: potential race condition in commonHandler/newWrapper call + - (only could cause problems when running spyceWWW/threaded concurrency) + * fix: pool module now reflects server globals, as indicated by documentation + fix: changed spyce modules are reloaded + - wasn't a huge deal to not have this before since not many people + wrote active tags or modified existing Spyce modules; more important now + that (I expect) more people will be using the 2.0-style active tags + + +v1.3.14 + fix: mod_python environment dictionary does not support iteration, causing + problems with cgi.py library in Python >2.3. Fix is to convert to + regular Python dictionary. Thanks to Jan Kujawa + +v1.3.13 + improved performance (approx. 3x) of single-threaded print + exceptions in non-default modules reported like errors in script code + fix: mysession.spy: session depends on pool, so switched import order + fix: faulty session handlers thrown out on error + - takes care of annoying NameError: 'pool' not found + fix: sys.path always returned to original state, even on error + globals accessible via python modules + - applied patch contributed by: Niko Matsakis + iterable objects allowed within 'for' tag + - modified patch submitted by: Stefan Behnel + spyce lambdas can return values + updated spyceLock.py to eliminate Python FutureWarning for large constants + added include.spyceStr(), idea from Santtu Pajukanta + fix: error module performs response.clearFilters + - thanks to Colin Gillespie for reporting this + turning on spyce.DEBUG_ERROR will also display when modules start/finish + +v1.3.12 + added fix to prevent reparsing of POST stream on + internal redirect (modified Conan Albrecht's contribution) + documentation updates + fix: parsing totally empty files threw exception + user request: iterate over request object + modified semantics of active tags + - singleton tags now behave like paired tags with empty bodies + - defined a number of flags in spyceTag to reduce level of indentation + in generated python code where unnecessary: + conditional, loops, catches, mustend + - see tag documentation + updated core tag library accordingly + fix: dump_handler with binary files truncating on Windows + fix: files with DOS linebreaks + fix: both <%. and <%@ can now begin a spyce directive + directory listings of Spyce webserver look nicer, Apache-like + added form active tag library + added PATH_INFO functionality to spyceWWW + +v1.3.11 + user request: daemon webserver mode + fix: mod_python flush problem + performance: rewrote tokenizer/parser + - no longer using clusmy parser generator package + - still pure python, compilation times between 2-6x faster + user request: expose functionality to define spyceProcess function + with arbitrary parameters, and pass in parameters... helps + Coil framework with Spyce integration + fix: spyce webserver not performing path manipulations correctly + on Windows + fix: spyce.mime file not copied for .rpm and Windows installers + +v1.3.10 + Default development configuration changed to: + Apache 2.0.40 and Python 2.2.x + Release testing will be performed: + both on Linux and Windows + under CGI, FastCGI and mod_python + Other versions of Apache and Python should continue to work, but + will not be tested. I am depending on user feedback to catch any + errant bugs under these older configurations. + fix: spyceWWW properly deals with directory URLs that don't end in '/' + fix: request.getpost1/postget1() now accept default values + fix: memory cache checks file permission as well as modification time + fix: makefile was including .pyc/.pyo files in tarball + fix: spyce.vim syntax highlighting for spyce lambdas + fix: error module should be loaded last to avoid stdout module being + unloaded on error, thereby causing print statements to no longer go + to the browser during error handling + fix: error module setHandler used incorrect variable name, causing + setHandler to fail + updated spyce.vim syntax file for JSP/ASP like delimeters + spyce.vim now included in vim distribution + rpm generates spyceParserTable.py + (allowing for different versions of python) + added 'no-store' and 'must-revalidate' to response.uncacheable() + added pageerror configuration option to modify default page-level handler + rpm now requires http >2.0 and python >2.2 installed + +v1.3.9 + spyceWWW web server improved + - configuration options integrated into spyce.conf + - handler mechanism created + - defined spyce and dump handlers + - reads Apache format mime-type definition files + - .spy files ==> spyce handler; rest ==> dump handler + - can display directory listings + - configuration options added accordingly + - corresponding documentation changed + documentation restructured to explain common configuration file + options in the runtime section + fix: docs/examples/*.gif added to rpm and windows installer + expanded section on how to get Spyce running under IIS via CGI + +v1.3.8 + user request: + modified request.get/post/get1/post1/env() to accept default values + (note: will break code that provided caseInsensitive parameter by position) + added request.getpost/getpost1/postget/postget1/default() methods + bug fixes: python 1.5 backwards compatibility issues in the following + online examples: gif.spy, myPortal.spy, mysession.spy + +v1.3.7 + support for ASP-style delimeters -- <% %> + use of Bastion eliminated, due to Python deprecation + +v1.3.6 + info.spy example updated to deal with implicitly loaded taglib module + minor documentation fix for doc-mod_include + quotes for the PythonPath in httpd.conf + +v1.3.5 + taglib and spylambda modules loaded implicitly only when needed + (i.e. when tags or spyce lambdas are actually used in a given file) + make install made more portable; removed install -D switch + EOFError now handled for file-based spyce caching (strange Windows bug?) + improvements to automaton module + +v1.3.4 + doc updates - session module + minor mod_python bug - filename attribute used over environment + fix - windows installer unable to find python executable in some cases + +v1.3.3 + examples/info.spy added + keep track of spyce entry point, added to spyce header + fix - CGI fails (only on Apache2.0!) with GET info due v1.3.2 changes + fix - typo in core:if tag + +v1.3.2 + mod_python 3.0.1 compatibility + - switched to sre module, despite stack limits, because + pre module conflicts with pcre shared object that apache uses + (actually, just fails on some complicate reg.exps!) + This implies that very, very long spyce files might fail, until + sre module implements a state-machine-based reg.exp engine. + - apacheRequest.connection.child_num mysteriously removed, + therefore using os.getpid() in spyceModpyRequest.getServerID() + spyceApache.conf tweaked (should be more compatible) + installHelper.py converts backslash to forward slash + for httpd.conf on Windows + switched from pre to sre module in spyceCompile.py + - reason: Apache 2.0.x uses different pcre library from Python + causing failure under mod_python + - pre was used over the default (sre) because sre implementation is + stack-based and encountered overruns... Oh, well! Don't write + Spyce files that blow the stack until sre is fixed. + +v1.3.1 + fix - wrapped thread-unsafe yacc-like package with lock + renamed util module to spyceUtil.py + - conflict with python1.5 site-package + renamed cache.py, lock.py (just in case) + make website update script faster + +v1.3.0 + active tags introduced + - see: http://spyce.sourceforge.net/doc-tag.html + - [[.taglib]] directive added + - taglib spyce module added + - compiler changes to deal with active tags + - tagging infrastructure (spyceTag, spyceTagPlus, spyceTagLibrary) + - see: spyceTags.py + - user-defined active tag libraries possible + - see: http://spyce.sourceforge.net/doc-tag_new.html + - core active tag library + see: tags/core.py + see: http://spyce.sourceforge.net/doc-tag_core.html + - tag libraries loaded from same path as modules + - compiler syntax checking improved + - check for unbalanced parens + - check for unbalanced active tags + - extensible syntax checking for active tags + +v1.2.10 + bugfix - typo in spyceWWW caused threading mode startup failure + +v1.2.9 + stdout.push() can now accept no file argument + stdout.pop() now returns captured output + stdout.capture() added + see: examples/stdout.spy and stdout module docs + session_user session handler added in session module + see: examples/mysession.spy and session module docs + spylambda.define() can now memoize + see: http://spyce.sourceforge.net/doc-mod_lambda.html + memoized spyce lambda syntax: [[spy! ...: ...]] + see: http://spyce.sourceforge.net/doc-lang_lambda.html + slight modification to spyce.vim syntax file + response.addHeader() now support replacement + response.timestamp(), expire(), expireRel(), lastModified() + and uncacheable() methods added + see: http://spyce.sourceforge.net/doc-mod_response.html + performance! + +v1.2.8 + links page added + spyce VIM syntax file updated; deals with spyce lambdas + include module improvements + - 'vars' field added + - included file can return value + - documentation updated, specifically regarding use of 'context' + +v1.2.7 + internal restructuring continues + - separated spyce exceptions + - separated spyce configuration + - expanded spyce API and spyceServer + - spyce.spyceDone now imported as spyceDone + simplified spyceModule + - renamed wrapper field, to _api + - old spyceModule available as spyceModulePlus + - all standard modules updated + fixed - syntax errors were not reported properly + file-based spyce caching, with config option + performance improvements + +v1.2.6 + single and multi-page documentation + minor fixes: + - NoCloseOut.flush() added + - BufferedOutput.flush() flushes sub-stream + - template module pointed at new location of cache code + +v1.2.5 + spyceAPI defined: module access to spyceWrapper object restricted + - see: http://spyce.sourceforge.net/doc-mod_new.html + - (in general, will be moving towards restricted execution space) + toc module improved; add level(), l1()...l9() methods + server-level debug option added to config file + - see: http://spyce.sourceforge.net/doc-conf_common_debug.html + - debug Spyce module deprecated + engine now supports recursive requests (include spyce from itself) + sys.stdout (and therefore print statements) made thread-safe + spyce engine supports concurrent requests + server-level concurrency option added to config file + - see: http://spyce.sourceforge.net/doc-conf_common_concurrency.html + - spyce webserver operates in single, forking and threading modes + server-level Spyce module caching + - replaces Spyce-level module caching + - caching-related code separated from wrapper + code compilation seperated from wrapper (spyce.spyceCode) + autodetect when PYTHONOPTIMIZE causes lexer/parser failure + minor fixes and performance tweaks + +v1.2.4 + fix - new PLY parser uses reflection at runtime to read + documentation strings containing grammar, thus you + should not run Python in optimize mode, thus + mod_python option in spyceApache.conf changed. + fix - python 1.5 compatible .spy files for docs + +v1.2.3 + fix - code for new tokenizer/parser made python 1.5.2 compatible + +v1.2.2 + fix - PATH_INFO via CGI + fix - magic (#!) on first line treated as comment + +v1.2.1 + complete rewrite of spyce tokenizer and parser + - using PLY, table-driven + added spyce lambdas to language + +v1.2.0 + contrib section added + support for SPYCE_PATH environment variable + lots of documentation fixes + decided spyce was mature enough for 1.2.0 + +v1.1.46 + feature request: improved examples page on website + +v1.1.45 + site and documentation revamp + refactored the spyceModule class (see spyceModule.py) + altered all standard modules to conform to new internal design + new table-of-contents (toc) module (see docs) + improved stdout module (see docs) + added push() and pop() methods + now loaded implicitly + exception tracebacks in chunks identify specific error lines + file globbing added to -O command-line option + +v1.1.44 + module directive deprecated + replaced with import tag + import tag accepts args attribute + calls module init() method at location of directive + init() methods added to modules: session, compress + see: http://spyce.sourceforge.net/doc_lang_directive.html + http://spyce.sourceforge.net/doc_mod.html + http://spyce.sourceforge.net/doc_mod_compress.html + http://spyce.sourceforge.net/doc_mod_session.html + http://spyce.sourceforge.net/doc_mod_new.html + bugfix - modules finalized on redirect + +v1.1.43 + bugfix - included files not inheriting modules properly + bugfix - transform module inside included file + +v1.1.42 + renamed spyce.conf to spyceApache.conf + renamed spyceApache to spyceModpy + renamed run_spyceApache to run_spyceModpy (affect spyceApache.conf) + added server-level configuration file functionality + server module search path + modules to load at startup + server-level error handler + global server variables + see: docs/doc_conf_common.html + added response.isCancelled() function + see: docs/doc_mod_response.html + bugfix - early client disconnect caused problems under mod_python + +v1.1.41 + extended HTTP response constants to conform to spec + extended HTML entity encoded characters to conform to spec + modified internal buffering semantics to allow eliminiation of special + case code for specific HTTP return codes (redirects) in the common path + performance improvements + convenience functions transform.html_encode() and url_encode() added + error module added: handles errors that occur during spyce processing + bugfix - HTTP return codes propagated correctly under mod_python + +v1.1.40 + bugfix - spyce syntax error propagated properly + response headers cleared on an internal redirect + case insensitive request.get,post,get1,post1,file + +v1.1.39 + modified how filter module injects itself into output stream + added response.addFilter() to allow piped functionality + on the output stream, modules can insert write, writeStatic, + writeExpr, flush and clear handlers + added compress module for dynamic compression functionality + compress module documentation + renamed filter module to transform (name conflict with Python builtin) + sys.path forced to be absolute before changing directory in CGI mode + bugfix - spyce path trimmed to just filename when directory changed for + CGI processing + bugfix - spyce web server closes sockets + +v1.1.38 + spyce can now run as a (proxy) web server + spyce -l [-p port] + +v1.1.37 + spyceDone exception to stop spyce processing + raise spyceDone, see gif.spy, fileupload.spy examples + response.close() deprecated + not needed with spyceDone functionality + cPickle used in session module + improved session serialization performance + +v1.1.36 + redirect.externalRefresh now has url= in string + internal redirect fixed + bug fix - consecutive compact line removal now possible + examples added: hello2.spy, form.spy + handle ISINDEX CGI queries that have extra command-line parameters + Status CGI header used for spyce redirect return codes + +v1.1.35 + bug - fixed cgi chdir in case of local directory + request - invoke spyce engine programmatically with spyce string + source tarball does not contain extra CVS junk + +v1.1.34 + fixed apache config bug in windows installer + +v1.1.33 + appended current Spyce file's directory to sys.path + +v1.1.32 + minor documentation tweaks + names attribute added to [[.module ]] tag + request.__getitem__() added + chdir in cgi mode + +v1.1.31 + windows installer improved: apache configuration and restart + fixed - handling of initial spaces in multi-line strings in python chunks + +v1.1.30 + red page marker in docs + created undefined windows lock variables + +v1.1.29 + documentation split up + rpm is now noarch + +v1.1.28 + include.dump() now has binary option + stdout changed to binary mode on windows for cgi purposes + fixed session_dir handler bug on windows + +v1.1.27 + fcgi implemented on windows too + windows installer + +v1.1.26 + fixed - nasty bug with the new module behaviour + small improvements to documentation and examples + improved request.uri() function + +v1.1.25 + fixed - fcgi module broke on windows + +v1.1.24 + line compaction improved + module behaviour on include.spyce() defined + +v1.1.23 + lots of changes so that: it works on Python 1.5.2 now too! + file-based session handler now uses pid, and file locks + live examples on sourceforge + +v1.1.22 + fixed Python v2.1.1-related bugs. + improved installation process and documentation + rpm more likely to succeed - uses fcgi or drops back to cgi + no longer mod_python based by default + +v1.1.21 (faulty release) + stochastic session clean up; no more threading dependency + documentation: better installation notes + peep-hole optimizer + +v1.1.20 (faulty release) + created explicit (swappable) cache infrastructure + BUG ** Spyce also works on Python v2.1 + request - request.post(),post1() works in includes + documentation: cheetah install, ... + +v1.1.19 + filter module + +v1.1.18 + fcgi support added + X-Spyce header added + documentation: how to write new modules + +v1.1.17 + feature request - compaction algorithm improved + +v1.1.16 + generalised session.setHandler (session handler selection mechanism) + gdbm, bsd db session handlers added + +v1.1.15 + minor makefile and rpm script changes + handling of multi-line strings in python code + response.flush() added + +v1.1.14 + wrappers to check python version + +v1.1.13 + added new language construct: "Python chunks" + +v1.1.12 + stdout module redirects stdout to response object + added writeln() to response module + +v1.1.11 + fixed lots of CGI bugs: + reported bug - headers not sent + session module thread prevented script death + added spyce.ONE_SHOT variable + cookie module fixed + gif.spy example fixed + external redirect fixed + +v1.1.10 + performance: + implemented semantic cache for spyce compilation + templating module performs caching + lots of commenting + +v1.1.9 + templating module (cheetah integration) + documentation + +v1.1.8 + automaton module + documentation + +v1.1.7 + associative array access to session and cookie information + added pool module + documentation + comments emitted as tokens + syntax highlighting function: include.spycecode + documentation + +v1.1.6 + dynamically loading modules + +v1.1.5 + redirect module added + +v1.1.4 + response.unbuffer() + +v1.1.3 + support for file upload + request.get1(),post1() + +v1.1.2 + more reliable exception location reporting + +v1.1.1 + static includes + module search path + +v1.1.0 + Implemented modules -- major rewrite. + Changed includes, sessions, cookies, ... everything into modules + Changed the generated "stub", though this is mostly under-the-covers + Rewrote most of the documentation + +v1.0.5 + CGI support + Expanded install docs + +v1.0.4 + Many doc updates + Autosession support + changed directives tags to use html-like attributes + +v1.0.3 + Automatic session cleanup + Updated pilpel image + +v1.0.2 + Handle 403 - Forbidden + Handle 404 - Not Found + +v1.0.1 + Tracking original spyce code locations in generated code + Reporting runtime exceptions in original spyce code + Reporting syntax (compile) exceptions in original spyce code + +v1.0 - Initial release + Documentation + Added [[.nocompact]] and [[.compact]] + Allowed escaped \[[ and \]] in HTML + Added session support, with on-disk implementation + Realised and implemented command-line + Added cookies + Added http header calls + Added get and post support + Created request and response objects + Added [[.include]] + Added [[.funcion]] and [[./function]] + Create in-memory spyce cache + Wrote a token-based Brace Converter + Added [[ ]] and [[= ]] + Created Spyce compiler shell + Wrote initial mod_python "hello world" handler + Read up on mod_python + Looked at PyServ + Attempted to engineer a WebWare-based solution + diff --git a/spyce-2.1/LICENCE b/spyce-2.1/LICENCE new file mode 100755 index 0000000000000000000000000000000000000000..a38ce43170668aca00fa8187afe62ce597affd64 --- /dev/null +++ b/spyce-2.1/LICENCE @@ -0,0 +1,40 @@ +Copyright (c) 2002-05 Rimon Barr. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice and +this LICENSE in its entirety including the disclaimer. The LICENSE of this +product may only be modified by the Copyright holder. + +2. Redistributions in binary form must reproduce the above copyright notice +and this LICENSE in its entirety including the disclaimer in the documentation +and/or other materials provided with the distribution. + +3. The end-user documentation included with the redistribution, if any, must +include the following acknowledgment: "This product uses Spyce, Copyright +Rimon Barr." Alternately, this acknowledgment may appear in the software +itself, wherever such third-party acknowledgments normally appear. The +documentation must also provide a instructions on how to receive an original +Spyce distribution, preferably a link to the website +http://spyce.sourceforge.net. + +4. The names "Spyce", or "Rimon Barr" must not be used to endorse or promote +products derived from this software without prior written permission. For +written permission, please contact rimon-AT-acm.org. + +5. Products derived from this software may not be called "Spyce", nor may +"Spyce" appear in their names, without prior written permission of the +Copyright holder. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RIMON BARR +OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/spyce-2.1/README b/spyce-2.1/README new file mode 100755 index 0000000000000000000000000000000000000000..e4a2c07bd5f7ce25f27d985eb06a7723eefc7f7f --- /dev/null +++ b/spyce-2.1/README @@ -0,0 +1,15 @@ +SPYCE - Python Server Pages +Copyright 2002-03. Rimon Barr +--------------------------------------------------------------------- + +Description: Python-based dynamic HTML scripting engine. + +Name: SPYCE - Python Server Pages +Author: Rimon Barr + +Please refer to the LICENCE file + +For system documentation: + Run python spyceCmd.py -l + Browse to localhost:8000/index.spy + diff --git a/spyce-2.1/RELEASE b/spyce-2.1/RELEASE new file mode 100755 index 0000000000000000000000000000000000000000..6c47bc6dcfe2bfa653cc5fa19a625bb419c8ba50 --- /dev/null +++ b/spyce-2.1/RELEASE @@ -0,0 +1,21 @@ +To release a new version of spyce: + +- Run: make clean +- Edit spyce.py: change version (possibly release) +- Edit CHANGES +- Run: cvs update; cvs commit +- Perform release testing +- Run: make upload (and do the sourceforge file release) +- Run: make sf; make sfcontrib +- Run: make clean +- post to freshmeat + spyce-users@sourceforge.net + python-announce@python.org + python-list@python.org + python-web-modules@yahoogroups.com + ? cheetahtemplate-discuss@lists.sourceforge.net + ? mod_python@modpython.org + ? webware-discuss@lists.sourceforge.net + ? sixthdev@yahoogroups.com + ? quixote-users@mems-exchange.org + ? php, jsp, apache, cherrypy diff --git a/spyce-2.1/THANKS b/spyce-2.1/THANKS new file mode 100755 index 0000000000000000000000000000000000000000..b3fd1b84dbed6fd0c9b00cd42898aaadde865a82 --- /dev/null +++ b/spyce-2.1/THANKS @@ -0,0 +1,60 @@ +This file contains mention of people that deserve thanks. +If you feel that you should be added, please email me +at + +Thanks (in reverse-chronological order) to: + +John Reese + Several bug fixes to the 2.0 release. + +Brandon Beck + For the daemon mode suggestion + +Conan C. Albrecht + Much help with the tags, the built-in webserver, suggestions, + bug reports, and fixes ... + +Fred Moscicki + Bug reports. + +Adrien Plisson + Lots of bug reports, help with module development, + and input of many ideas. + +The NullSoft crew: + For NSIS SuperPiMP installer + http://www.nullsoft.com/free/nsis/ + +Tino Lange + Inspiring my work to get the Spyce engine and modules down from + Python 2.2 to Python 1.5.2. + +John J Smith + Finding bugs. Email discussions that led to improvements + in the Spyce line compacting mode, and the way modules behave + in included files. + +Piers Lauder + Email discussions that led to Python chunks (ala Poor Man's Zope), + filters (ala Cheetah), and some other ideas. + +The Cheetah team + http://www.cheetahtemplate.org/ + +Natalya Katsnelson : + For the Spyce mascot, "Pilpel". + +Dave Wallace : + Provided initial idea in Webware's PSP implementation to add braces + to Python code, solving the indentation problem. + +Gregory Trubetskoy : + For the mod_python project, upon which this work depends + http://www.modpython.org/ + +The SourceForge team + http://www.sourceforge.net + +The Apache Team + http://www.apache.org/ + diff --git a/spyce-2.1/_eginfo.txt b/spyce-2.1/_eginfo.txt new file mode 100755 index 0000000000000000000000000000000000000000..3a9df352cff465395e9a47abcb40dbe60bf31af7 --- /dev/null +++ b/spyce-2.1/_eginfo.txt @@ -0,0 +1,77 @@ +Language / Python Class Chunks:lang_chunkc:examples/handlerintro.spy:1 +Language / Spyce Lambdas:lang_lambda:examples/spylambda.spy:1 +Runtime / Exceptions:runtime_except:examples/gif.spy:1 +Runtime / Configuration:runtime_common:examples/config.spy:1 +Runtime / Server utilities / The Spyce scheduler:runtime_util_scheduler:examples/scheduling.py:0 +Runtime / Modules / DB (implicit):mod_db:examples/db.spy:1 +Runtime / Modules / Request (implicit):mod_request:examples/filter.py:0 +Runtime / Modules / Request (implicit):mod_request:examples/request.spy:1 +Runtime / Modules / Request (implicit):mod_request:examples/fileupload.spy:1 +Runtime / Modules / Response (implicit):mod_response:examples/gif.spy:1 +Runtime / Modules / Redirect:mod_redirect:examples/redirect.spy:1 +Runtime / Modules / Cookie:mod_cookie:examples/cookie.spy:1 +Runtime / Modules / Session:mod_session:examples/session2.spy:1 +Runtime / Modules / Pool:mod_pool:examples/pool.spy:1 +Runtime / Modules / Transform:mod_transform:examples/transform.spy:1 +Runtime / Modules / Compress:mod_compress:examples/compress.spy:1 +Runtime / Modules / Include:mod_include:examples/include.spy:1 +Runtime / Modules / Include:mod_include:examples/include.spi:0 +Runtime / Modules / Include:mod_include:examples/includestatic.spy:1 +Runtime / Modules / Include:mod_include:examples/includestatic.spi:0 +Runtime / Modules / Internal modules / Error:mod_error:examples/error.spy:1 +Runtime / Modules / Internal modules / Error:mod_error:examples/error.spi:0 +Runtime / Modules / Internal modules / Stdout:mod_stdout:examples/stdout.spy:1 +Runtime / Modules / Writing Modules:mod_new:examples/myModule.py:0 +Runtime / Tags / Core:tag_core:examples/hello-templated.spy:1 +Runtime / Tags / Core:tag_core:examples/login-optional.spy:1 +Runtime / Tags / Core:tag_core:examples/login-required.spy:1 +Runtime / Tags / Form:tag_form:examples/formintro.spy:1 +Runtime / Tags / Form:tag_form:examples/formtag.spy:1 +Runtime / Tags / Active Handlers:tag_handlers:examples/handlerintro.spy:1 +Runtime / Tags / Active Handlers:tag_handlers:examples/handlervalidate.spy:1 +Runtime / Tags / Active Handlers:tag_handlers:examples/handlervalidate2.spy:1 +Runtime / Tags / Active Handlers:tag_handlers:examples/db.spy:1 +Runtime / Tags / Writing Tag Libraries:tag_new2:examples/tagbold.spi:0 +Runtime / Tags / Writing Tag Libraries the hard way:tag_new:examples/myTaglib.py:0 +Runtime / Tags / Writing Tag Libraries the hard way:tag_new:examples/tag.spy:1 +Runtime / Programmatic Interface / Example:runtime_prog_example:examples/programmaticUsage.py:0 +Addenda / Performance:add_perf:examples/hello.spy:1 +:root:examples/handlerintro.spy:1 +:root:examples/spylambda.spy:1 +:root:examples/gif.spy:1 +:root:examples/config.spy:1 +:root:examples/scheduling.py:1 +:root:examples/db.spy:1 +:root:examples/filter.py:1 +:root:examples/request.spy:1 +:root:examples/fileupload.spy:1 +:root:examples/gif.spy:1 +:root:examples/redirect.spy:1 +:root:examples/cookie.spy:1 +:root:examples/session2.spy:1 +:root:examples/pool.spy:1 +:root:examples/transform.spy:1 +:root:examples/compress.spy:1 +:root:examples/include.spy:1 +:root:examples/include.spi:1 +:root:examples/includestatic.spy:1 +:root:examples/includestatic.spi:1 +:root:examples/error.spy:1 +:root:examples/error.spi:1 +:root:examples/stdout.spy:1 +:root:examples/myModule.py:1 +:root:examples/hello-templated.spy:1 +:root:examples/login-optional.spy:1 +:root:examples/login-required.spy:1 +:root:examples/formintro.spy:1 +:root:examples/formtag.spy:1 +:root:examples/handlerintro.spy:1 +:root:examples/handlervalidate.spy:1 +:root:examples/handlervalidate2.spy:1 +:root:examples/db.spy:1 +:root:examples/tagbold.spi:1 +:root:examples/myTaglib.py:1 +:root:examples/tag.spy:1 +:root:examples/programmaticUsage.py:1 +:root:examples/whatsnew.spy:1 +:root:examples/whatsnew2.spy:1 diff --git a/spyce-2.1/contrib/jedit-spyce.xml b/spyce-2.1/contrib/jedit-spyce.xml new file mode 100755 index 0000000000000000000000000000000000000000..0de1e876360a3d948cd229f46c844d8657fec11f --- /dev/null +++ b/spyce-2.1/contrib/jedit-spyce.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + [[-- + --]] + + + + + [[. + ]] + + + + + [[= + ]] + + + + + [[\ + ]] + + + + + [[ + ]] + + + + + <!-- + --> + + + + + <SCRIPT + </SCRIPT> + + + + + <STYLE + </STYLE> + + + + + <! + > + + + + < + > + + + + + & + ; + + + + + + [[-- + --]] + + + + + [[= + ]] + + + + + [[ + ]] + + + + + " + " + + + + ' + ' + + + + include + compact + import + taglib + + file + mode + off + space + line + full + name + from + as + args + names + + + + + + + [[-- + --]] + + + + + [[= + ]] + + + + " + " + + + + ' + ' + + + / + : + : + + + + + [[= + ]] + + + diff --git a/spyce-2.1/contrib/kate-spyce.xml b/spyce-2.1/contrib/kate-spyce.xml new file mode 100755 index 0000000000000000000000000000000000000000..642d7d3173a9665a4d086f3bb8d5c2f0ff3bad2c --- /dev/null +++ b/spyce-2.1/contrib/kate-spyce.xml @@ -0,0 +1,383 @@ + + + +]> + + + + + + + and + assert + break + class + continue + def + del + elif + else + except + exec + finally + for + global + if + in + is + lambda + not + or + pass + print + raise + return + try + while + yield + + + None + self + True + False + NotImplemented + Ellipsis + + + spy + import + session + cookie + pool + include + automaton + taglib + from + as + core + request + response + stdout + transform + redirect + error + spylambda + + + + + abs + apply + buffer + callable + chr + cmp + coerce + compile + complex + copyright + credits + delattr + dir + divmod + eval + execfile + exit + filter + float + getattr + globals + hasattr + hash + hex + id + input + int + intern + isinstance + issubclass + iter + len + license + list + locals + long + map + max + min + oct + open + ord + pow + quit + range + raw_input + reduce + reload + repr + round + setattr + slice + str + tuple + type + unichr + unicode + vars + xrange + zip + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spyce-2.1/contrib/modules/automaton.py b/spyce-2.1/contrib/modules/automaton.py new file mode 100755 index 0000000000000000000000000000000000000000..3b78ded294012b5d9998640cccfc82d30d169fa5 --- /dev/null +++ b/spyce-2.1/contrib/modules/automaton.py @@ -0,0 +1,135 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: automaton.py 860 2006-04-30 01:18:25Z ellisj $ +################################################## + +from spyceModule import spyceModule + +__doc__ = ''' +[[toc.n('Automaton', 'mod_automaton')]] + +The automaton module provides support for state machine-based +application design, which is often useful when designing websites with +application flows. The state machine is a directed, labelled graph. It has +states (nodes with names), and transitions (directed edges with names). One of +the states is defined to be a "begin" state for the machine. Every state +has a "send" function, a "receive" function and a set of outgoing +edges. + +The basic idea behind the operation of the automaton module is as follows: The +application is at some state when a request comes in. The receive function for +that state is invoked to process the input from the browser. Based on this +input the receive function returns some edge label, which takes the +application from the current state to its new state. The send function of this +new state is invoked to emit the appropriate application page. The data that +returns from this page will be processed by the corresponding receive +function, and so on. All you need to remember between requests is which state +the application is in, which can be done via get or post, or via cookies using +the cookie module. Better yet (to keep application states private and on the +server for security reasons), one can store the state label in the session +using the session module. + +A state machine can be defined programmatically using the following functions: + + state ( name, send, recv ) + Add a new state labeled "name" with associated "send" and "recv" + functions. + + transition ( state1, name, state2 ) + Add a new edge labelled "name" from "state1" to "state2". There is + always a self-referencing edge with the label None, but this can + be overidden. + + begin ( state ) + Define a given state to be the begin state. + + define ( sm, begin ) + Define an entire automaton "sm" all at once, where sm is a + hashtable. The keys are the states and the values are triplets + with a send function, a receive function and an edge hashtable. + The edge hashtable has names of the edges as keys and the target + states as values. The "begin" state is given + +To step through the state machine transitions, you call: + + step ( [state] ) + If "state" is specified, then call the receive function of that + state. The receive function returns an edge label, which points to + the new state. If no state is specified, just set the new state to + the begin state of the automaton. Then, call the send function of + the new state. Note that the send function is responsible for + encoding its own state label, for use on the subsequent client + request. + + +Future releases of this module may add support for different types of send and +receive handlers. For example, it is probably useful to be able to internally +redirect to various Spyce pages for send processing, rather than inline +functions. It may also be possible to pass information among the different +functions, which could be useful, for example, in handling error messages +during form processing. It may also be useful to define a sequence of states, +where previous and next are implicit edges. + +See automaton.spy for example usage. +''' + +SEND = 0 +RECV = 1 +EDGES = 2 + +class automaton(spyceModule): + def start(self): + "Initialise an empty automaton" + self.clear() + def clear(self): + self._nodes = {} + self._edges = {} + # defining the automaton + def state(self, name, send, recv): + "Add a new automaton state" + self._nodes[name] = send, recv + self.transition(name, None, name) + def transition(self, state1, edge, state2): + "Add a new automaton transition" + if not self._nodes.has_key(state1): + raise 'state %s does not exist' % state1 + if not self._nodes.has_key(state2): + raise 'state %s does not exist' % state2 + self._edges[(state1, edge)] = state2 + node=state + edge=transition + def begin(self, name): + if not self._nodes.has_key(name): + raise 'state %s does not exist' % name + self._begin = name + def define(self, sm, start): + self.clear() + for s1 in sm.keys(): + self.node(s1, sm[s1][SEND], sm[s1][RECV]) + for s1 in sm.keys(): + for e in sm[s1][EDGES].keys(): + self.edge(s1, e, sm[s1][EDGES][e]) + self.begin(start) + + # running the automaton + def step(self, state=None): + """Run the automaton one step: recv (old state), transition, + send (new state)""" + if state==None: + state = self._begin + else: + try: _, recv = self._nodes[state] + except: raise 'invalid state: %s' % state + edge = recv() + try: state = self._edges[(state, edge)] + except: raise 'invalid transition: %s,%s' % (state, edge) + try: send, _ = self._nodes[state] + except: raise 'invalid state: %s' % state + send() + + +# rimtodo: cached state-machines + diff --git a/spyce-2.1/contrib/modules/automaton.spy b/spyce-2.1/contrib/modules/automaton.spy new file mode 100755 index 0000000000000000000000000000000000000000..38482e79ae370c78f8c117c0baf1a35ec6c3998b --- /dev/null +++ b/spyce-2.1/contrib/modules/automaton.spy @@ -0,0 +1,88 @@ +[[-- sample use of automaton Spyce module; see automaton.py --]] + +[[.import name=automaton]] +[[.import name=session1 args="'session_dir', '/tmp', auto=10"]] +[[\ +if not session.auto: session.auto = { + 'name': '', +} + +step1send = [[spy: + +
+ + + + + + + +
Name:
+
+ ]] +def step1recv(): + if request.post1('dir') == 'next': return 'next' + +step2send = [[spy: + +
+ + + + + + + +
Age:
+
+ ]] +def step2recv(): + if request.post1('dir') == 'prev': return 'prev' + if request.post1('dir') == 'next': return 'next' + +step3send = [[spy: + +
+ step3 + + + +
+ ]] +def step3recv(): + if request.post1('dir') == 'prev': return 'prev' + if request.post1('dir') == 'next': return 'next' + +step4send = [[spy: + + Thanks. + ]] +def step4recv(): + pass + +automaton.define({ + 'step1': ( step1send, step1recv, { + 'next': 'step2', + }), + 'step2': ( step2send, step2recv, { + 'next': 'step3', + 'prev': 'step1', + }), + 'step3': ( step3send, step3recv, { + 'next': 'step4', + 'prev': 'step2', + }), + 'step4': ( step4send, step4recv, { + }), +}, 'step1') + +state = request.post1('state') +automaton.step(state) +]] + +[[-- +spyce file +spyce inline +function or method reference +inline code +--]] diff --git a/spyce-2.1/contrib/modules/etag.py b/spyce-2.1/contrib/modules/etag.py new file mode 100755 index 0000000000000000000000000000000000000000..83e00ab588ed17e0a0def2fc341ef1018267e1d2 --- /dev/null +++ b/spyce-2.1/contrib/modules/etag.py @@ -0,0 +1,147 @@ +# I have tried to implement the same functionality as cgi_buffer. +# http://www.mnot.net/cgi_buffer/ +# Author: Francisco Javier Cabello + +# spyce file: +# -----------------------o----------------------- +# [[.import name=etag]] +# +# Just a test +# +# -----------------------o----------------------- + +# When you import etag module a http header (ETag header)is added to the output +# of your spyce script. The next time your browser ask for the same page to the +# server, it will send the header 'If-None-Match'. The server will check the +# etag given and the one it has computed and when both values will be the same, +# it will send a 'Not Modified' http response. + +# Example: + +# First time: +# ----------------------- +# GET /cgi-bin/main/main.spy HTTP/1.1 +# Host: 192.67.79.171 +# User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.6) Gecko/20040227 +# Firefox/0.8 +# Accept: +# text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,video/x-mng,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1 +# Accept-Language: en-us,en;q=0.5 +# Accept-Encoding: gzip,deflate +# Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 +# Keep-Alive: 300 +# Connection: keep-alive +# Cookie: current_page=main/300_Mstate; user=administrator; +# session=7ef88d81a0ce8d6d7cf8a8d7c01d7498; language=en + +# HTTP/1.1 200 Ok +# Date: Wed, 14 Jul 2004 13:53:14 GMT +# Server: Apache/1.3.27 (Unix) Debian GNU/Linux mod_gzip/1.3.26.1a +# mod_python/2.7.8 Python/2.2.2 +# X-Spyce: Spyce/modpy_1.3.12 Python/2.2 +# ETag: "CTjpbS6wVaFs7SuUOMx8uQ==" +# Keep-Alive: timeout=15, max=91 +# Connection: Keep-Alive +# Content-Type: text/html; charset=iso-8859-1 +# ... page ... + +# ----------------------- + +# Second time: +# ----------------------- +# GET /cgi-bin/main/main.spy HTTP/1.1 +# Host: 192.67.79.171 +# User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.6) Gecko/20040227 +# Firefox/0.8 +# Accept: +# text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,video/x-mng,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1 +# Accept-Language: en-us,en;q=0.5 +# Accept-Encoding: gzip,deflate +# Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 +# Keep-Alive: 300 +# Connection: keep-alive +# Cookie: current_page=main/300_Mstate; user=administrator; +# session=7ef88d81a0ce8d6d7cf8a8d7c01d7498; language=en +# If-None-Match: "CTjpbS6wVaFs7SuUOMx8uQ==" +# Cache-Control: max-age=0 + +# HTTP/1.1 304 Not Modified +# Date: Wed, 14 Jul 2004 13:53:14 GMT +# Server: Apache/1.3.27 (Unix) Debian GNU/Linux mod_gzip/1.3.26.1a +# mod_python/2.7.8 Python/2.2.2 +# X-Spyce: Spyce/modpy_1.3.12 Python/2.2 +# ETag: "CTjpbS6wVaFs7SuUOMx8uQ==" +# Keep-Alive: timeout=15, max=91 +# Connection: Keep-Alive +# Content-Type: text/html; charset=iso-8859-1 +# ----------------------- + +from spyceModule import spyceModule +from cStringIO import StringIO +import base64, md5 + +OUTPUT_POSITION = 94 + +__doc__ = '''ETag module. provides etag header generation and verification.''' + +class etag(spyceModule): + def start(self): + # install compress filter into response module + self._filter = FilterEtag(self) + self._api.getModule('response').addFilter(OUTPUT_POSITION, self._filter) + + def init(self): + pass + + def finish(self, theError=None): + if not theError: + self._filter.close() + +class FilterEtag(Filter): + def __init__(self, module): + self._module = module + self._buf = StringIO() + self._flushed = 0 + + def writeStatic(self, s): + self.write(s) + + def writeExpr(self, s, **kwargs): + self.write(str(s)) + + def write(self, s, *args, **kwargs): + self._buf.write(s) + + def flushImpl(self, final=0): + self._flushed = 1 + body = self._buf.getvalue() + self._buf = StringIO() + + etag = "" + + if final: + # etag = base64(md5(body)) + etag = base64.encodestring(md5.new(str(body)).digest())[:-1] + self._module._api.getModule('response').addHeader('ETag', '"%s"' % str(etag) ) + + if_none_match = self._module._api.getModule('request').getHeader('If-None-Match') + + if not if_none_match or string.find(if_none_match, etag)<0: + # if If-None-Match header hasn't been found or etag sent is different from + # etag computed + self.next.write(body) + else: + # etag sent is the same + self._module._api.getModule('response').setReturnCode( 304 ) + else: + self.next.write(body) + + etag_sent = self._module._api.getModule('request').getHeader('If-None-Match') + + def clearImpl(self): + self._buf = StringIO() + + def close(self): + self.flushImpl(1) + + diff --git a/spyce-2.1/contrib/modules/mail.py b/spyce-2.1/contrib/modules/mail.py new file mode 100755 index 0000000000000000000000000000000000000000..d766f9210da66522f3da45cb651cd1430539bdcd --- /dev/null +++ b/spyce-2.1/contrib/modules/mail.py @@ -0,0 +1,202 @@ +################################################## +# Spyce mail module +# Copyright (c) 2002 Adrien Plisson. +# +# SPYCE is Copyright (c) 2002 Rimon Barr. +# Refer to spyce.py +################################################## + +from spyceModule import spyceModule + +import smtplib +import email + +import types +import string +import quopri + +class mail(spyceModule): + def start(self): + pass + + def init(self, *args, **kwargs): + if args != () or kwargs != {}: + self.setServer(*args, **kwargs) + + def finish(self, error=None): + pass + + def setServer(self, host, port=25, user=None, password=None): + self.host = host + self.port= port + self.user = user + self.password = password + + def send(self, from_field, to_field, subject_field, content, cc_field=None, bcc_field=None): + #parse arguments + from_field = _parse_addrs(from_field) + to_field = _parse_addrs(to_field) + if cc_field != None: + cc_field = _parse_addrs(cc_field) + if bcc_field != None: + bcc_field = _parse_addrs(bcc_field) + + #compose the mail + msg = _build_main_message(from_field, to_field, subject_field, content, cc_field).as_string() + + #build the sender and recipients lists + sender = from_field[0] + if cc_field == None: + recipients = to_field + else: + recipients = to_field + cc_field + sender= _unparse_addr(sender) + for i in range(len(recipients)): + recipients[i] = _unparse_addr(recipients[i]) + + #establish the connection + con = smtplib.SMTP(self.host, self.port) + + if self.user != None and self.password != None: + con.login(self.user, self.password) + + #send the mail first to the recipients in the to and cc fields + con.sendmail(sender, recipients, msg) + #then we send it for each recipient in the bcc field + if bcc_field != None: + for recipient in bcc_field: + con.sendmail(sender, recipient, msg) + + #close the connection + con.quit() + +def _parse_addrs(addrs): + if type(addrs) == types.ListType: + return map(_parse_addr, addrs) + else: + return [_parse_addr(addrs)] + +def _parse_addr(addr): + if type(addr) in types.StringTypes: + return email.Utils.parseaddr(addr) + elif type(addr) == types.TupleType: + return addr + +def _unparse_addrs(addrs): + return string.join(map(_unparse_addr, addrs), ', ') + +def _unparse_addr(addr): + return email.Utils.dump_address_pair(addr) + +def _build_main_message(from_field, to_field, subject_field, content, cc_field=None): + msg = email.Message.Message() + msg['From'] = _unparse_addrs(from_field) + msg['To'] = _unparse_addrs(to_field) + if cc_field != None: + msg['Cc'] = _unparse_addrs(cc_field) + + msg['Subject'] = _encode_field(subject_field) + + msg['MIME-Version'] = '1.0' + #if content is a dictionnary (with content, type, encoding and disposition) + if type(content) == types.DictType: + msg['Content-Type'] = content['type'] + + #if we have subparts + if type(content['content']) == types.ListType: + for submsg in content['content']: + msg.attach(_build_message(submsg)) + #if we don't have any subpart + else: + #if the content is a string + if type(content['content']) in types.StringTypes: + msg.attach(content['content']) + #or if it's an open file + else: + msg.attach(content['content'].read()) + + #if we have an encoding, then we encode + if content.has_key('encoding'): + msg = _encode_message(msg, content['encoding']) + + #we allow to specify some additional fields + if content.has_key('additional_fields'): + for field in content['additional_fields']: + msg[field] = content['additional_fields'][field] + + #if the content is a list then we have subparts + elif type(content) == types.ListType: + msg['Content-Type'] = 'multipart/mixed' + for submsg in content: + msg.attach(_build_message(submsg)) + + #if content is a string, then we dump it as is + elif type(content) in types.StringTypes: + msg.attach(content) + + #in other cases, we consider it's an open file + else: + msg.attach(content.read()) + + return msg + +def _build_message(content): + msg = email.Message.Message() + + #if content is a dictionnary (with content, type, encoding and disposition) + if type(content) == types.DictType: + msg['Content-Type'] = content['type'] + + #if we have subparts + if type(content['content']) == types.ListType: + for submsg in content['content']: + msg.attach(_build_message(submsg)) + #if we don't have any subpart + else: + #if the content is a string + if type(content['content']) in types.StringTypes: + msg.attach(content['content']) + #or if it's an open file + else: + msg.attach(content['content'].read()) + + #if we have an encoding, then we encode + if content.has_key('encoding'): + msg = _encode_message(msg, content['encoding']) + + #we allow to specify some additional fields + if content.has_key('additional_fields'): + for field in content['additional_fields']: + msg[field] = content['additional_fields'][field] + + #if the content is a list then we have subparts + elif type(content) == types.ListType: + msg['Content-Type'] = 'multipart/mixed' + for submsg in content: + msg.attach(_build_message(submsg)) + + #if content is a string, then we dump it as is + elif type(content) in types.StringTypes: + msg.attach(content) + + #in other cases, we consider it's an open file + else: + msg.attach(content.read()) + + return msg + +def _encode_field(field): + if quopri.encodestring(field) == field: + return field + else: + return email.Utils.encode(field) + +def _encode_message(msg, encoding): + if encoding == 'quoted-printable': + email.Encoders.encode_quopri(msg) + elif encoding == 'base64': + email.Encoders.encode_base64(msg) + elif encoding == '7bit' or encoding == '8bit': + email.Encoders.encode_7or8bit(msg) + + return msg \ No newline at end of file diff --git a/spyce-2.1/contrib/modules/mail.txt b/spyce-2.1/contrib/modules/mail.txt new file mode 100755 index 0000000000000000000000000000000000000000..cf12bc806e70fa1b9d7a76ecbb0f35e8751acd75 --- /dev/null +++ b/spyce-2.1/contrib/modules/mail.txt @@ -0,0 +1,133 @@ +___________ +Spyce mail module + +1. Foreword + + Sorry for you people that do not use Python 2.2, but this module is not for + you ! + + The mail module make extensive use of the 'email' module, which appeared + only with the 2.2 release of Python. If you don't have it, then the mail + module won't work at all. + +2. Content + + 1. Foreword + 2. Content + 3. Introduction + 4. Module content + 4.1 setServer + 4.2 mail + 5. More informations + +3. Introduction + + The mail module is a spyce module which allows to send a mail from a spyce + script. It's usage is simple due to it's restricted number of functions, but + it's capabilities are very impressive: + + - Named recipients; + - Cc and Bcc; + - Multipart MIME message for attachment; + - Quoted-printable and base64 encoding; + - Sending of text or files. + + All this is performed with two function. + +4. Module content + + The mail module provides the following functions: + + 4.1 setServer + + Declaration: + setServer(host, [port=25], [user], [password]) + + Parameters: + host: The smtp server to which you will connect to send your mail. + + port: The tcp/ip port to use for this connection. + + user: For smtp server with authentication, those parameters allows you + password: to identify yourself to the server. + + 4.2 send + + Declaration: + send(from_field, to_field, subject_field, content, [cc_field=None], [bcc_field=None]) + + Parameters: + from_field: The people who this mail comes from (see syntax below). + to_field: The people who should receive this mail (see syntax below). + subject_field: The subject, a simple string. + content: The content (see syntax below). + cc_field: The people who should receive a copy of this mail. + bcc_field: The people who should receive a copy of this mail but without + the others knowing it (see syntax below). + + Syntax: + The from_field, to_field, cc_field and bcc_field all have the same syntax. + They can be: + - a simple string containing the address. + eg: 'rien@yeepa.org' + + - a string containing a RFC822 mail address, that is containing the name + and the mailbox. + eg: '"Adrien Plisson" ' + + - a tuple containing the name and the mailbox. + eg: ('Adrien Plisson', 'rien@yeepa.org') + + - a list where each element is of one of the three syntax given before. + eg: [('Adrien Plisson', 'rien@yeepa.org'), '"Rimon Barr" ', + 'grisha@modpython.org'] + + you don't have to worry about the encoding of the subject field, the mail + function will encode it automatically in quoted-printable if necessary. + + The content can be expressed in these ways: + - a string (what more simple ?). + eg: "this is the content of the mail" + + - an open file which is simply dumped onto the mail. + eg: data = open('/usr/bin/spyce/spyce.py', 'r') + mail.send('rien@yeepa.org', 'barr@cs.cornell.edu', 'spyce', data) + + - a dictionnary containing the folowing keys: + - content, + - type, + - encoding (optional), + - additional_fields (optional) + Content can be aany of the possible form for content. Type is the MIME + type, you can precise here some arguments, i.e. the name of a file. + Encoding is one of the encoding specified in the MIME definition, that + is 'quoted-printable', 'base64', '7bit' or '8bit'. Additional_fields + allows you to add some more fields in the header of the current + message part. + eg: { 'content': 'this is the content', + 'type': 'text/plain', + 'encoding': 'quoted-printable', + 'additional_fields': {'X-Mailer': 'Spyce mail module (the power of mail in one function)' } } + + - a list where each element is of one of the possible form for content (string, file, dict or list) + eg: ["this is the content of spyce", + {'content': data, + 'type': 'application/octet-stream', + 'encoding': 'base64', + 'additional_fields': {'Content-Disposition': 'attachment; filename="spyce.py"'} } + ] + + Please note that the content syntax definition is redundant: each content + can contain content. This way you can build multipart MIME mails the way + you want. You can also provide some extra headers. + + When you specify an encoding, the mail function will encode the content + according to the specified algorithm. Don't bother encoding it yourself... + +5. More informations + + RFC 2821: Simple Mail Transfert Protocol + RFC 2822: Internet Message Format + RFC 2045: MIME Part One: Format of Internet Message Bodies + RFC 2046: MIME Part Two: Media Types + RFC 2047: MIME Part Three: Message Header Extensions for Non-ASCII Text diff --git a/spyce-2.1/contrib/modules/session1.py b/spyce-2.1/contrib/modules/session1.py new file mode 100755 index 0000000000000000000000000000000000000000..d2dca9e569bd4865b10b11056e1f13cf1eb8ed9d --- /dev/null +++ b/spyce-2.1/contrib/modules/session1.py @@ -0,0 +1,371 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +################################################## + +# This is the old session module. It is provided for backwards compatibility, +# but not recommended for new development. + +from spyceModule import spyceModule +import re, time, string, random +import spyceLock + +try: + import cPickle + pickle = cPickle +except: + import pickle + +__doc__ = '''Session module provides support for session management - the +storage of variables on the server between requests under some short +identifier. + +A user must call setHandler() to determine how the sessions are stored, before +using the other session methods. The get(), set() and delete() methods provide +access to the session information. + +The autoSession() method will turn on the automatic session management +(loading and saving the session). When automatic session management is turned +on the session information, identifier, parameter name and browser method are +stored in the variables called auto, autoID, autoName and autoMethod, +respectively.''' + +class session1(spyceModule): + def start(self): + "Initialise the session module variables." + self._serverobject = self._api.getServerObject() + if 'session' not in dir(self._serverobject): + self._serverobject.session = sessionHandlerRegistry() + self._handler = None + self._clearAutoSession() + def finish(self, theError=None): + "Save the session, if automatic session management is turned on." + if self.autoID: + self.set(self.auto, self.autoExpire, self.autoID) + if self.autoMethod=='cookie': + self._api.getModule('cookie').set(self.autoName, self.autoID) + sessionCleanup(self._serverobject.session) + def init(self, handler=None, *args, **kwargs): + if handler: + session = apply(self.setHandler, (handler,)+args) + if kwargs.has_key('auto') and kwargs['auto']: + auto = kwargs['auto'] + if type(auto) != type(()): + auto = (auto,) + apply(session.autoSession, auto) + def setHandler(self, file_name, *params): + "Select a session handler." + file_name = string.split(file_name, ':') + if len(file_name)==1: file, name = None, file_name[0] + else: file, name = file_name[:2] + if file: handler = self._api.loadModule(name, file, self._api.getFilename()) + else: handler = eval(name) + self._handler = apply(handler, (self,)+params) + self._serverobject.session.add(self._handler) + return self + def get(self, id): # method deletes session, if stale + "Retrieve session information." + if not self._handler: raise 'call setHandler to initialise' + return self._handler.get(id) + def delete(self, id=None): + "Delete session information." + if not self._handler: raise 'call setHandler to initialise' + if not id: + id = self.autoID + self._clearAutoSession() + return self._handler.delete(id) + def set(self, state, expire, id=None): + "Set session information." + if not self._handler: raise 'call setHandler to initialise' + return self._handler.set(state, expire, id) + def clear(self): + "Clear all session information in current handler." + if not self._handler: raise 'call setHandler to initialise' + return self._handler.clear() + def autoSession(self, expire, method='cookie', name='spyceSession'): + "Turn on automatic session management." + if not self._handler: raise 'call setHandler to initialise' + method = string.lower(method) + if method=='cookie': self.autoID = self._api.getModule('cookie').get(name) + elif method=='post': self.autoID = self._api.getModule('request').post1(name) + elif method=='get': self.autoID = self._api.getModule('request').get1(name) + else: raise 'runtime error: invalid autosession method' + self.autoMethod = method + self.autoName = name + self.autoExpire = expire + self.auto = None + if self.autoID: + self.auto = self.get(self.autoID) + if not self.auto: self.autoID = None + if not self.autoID: # generate a sessionid + self.autoID = self.set(None, self.autoExpire) + def _clearAutoSession(self): + self.auto = None + self.autoID = None + self.autoMethod = None + self.autoName = None + self.autoExpire = None + +################################################## +# Cleanup +# + +# expire sessions every n requests in expectation +SESSION_EXPIRE_CHECK = 50 + +class sessionHandlerRegistry: + "Registry of all used session handlers." + def __init__(self): + self.handlers = {} + def add(self, handler): + self.handlers[handler.getHandlerID()] = handler + def list(self): + return self.handlers.values() + def remove(self, handler): + del self.handlers[handler.getHandlerID()] + +def sessionCleanup(registry): + """Iterates through all session handlers and sessions to perform session + cleanup""" + if random.randrange(SESSION_EXPIRE_CHECK): return + for handler in registry.list(): + try: + sessions = handler.keys() + for s in sessions: + handler.get(s) # will delete stale sessions + except: + registry.remove(handler) + + +################################################## +# Session handlers +# + +class sessionHandler: + '''All session handlers should subclass this, and implement the methods + marked: 'not implemented'.''' + def __init__(self, sessionModule): + self.childnum = sessionModule._api.getServerID() + def getHandlerID(self): + raise 'not implemented' + def get(self, id): # method should delete, if session is stale + raise 'not implemented' + def delete(self, id): + raise 'not implemented' + def clear(self): + raise 'not implemented' + def set(self, state, expire, id=None): + raise 'not implemented' + def keys(self): + raise 'not implemented' + def __getitem__(self, key): + return self.get(key) + def __delitem__(self, key): + return self.delete(key) + +################################################## +# File-based session handler +# + +class session_dir(sessionHandler): + def __init__(self, sessionModule, dir=None): + sessionHandler.__init__(self, sessionModule) + if not dir: + import spyce + dir = spyce.getServer().config.tmp + if not os.path.exists(dir): + raise "session directory '%s' does not exist" % dir + self.dir = dir + self.prefix = 'spy' + def getHandlerID(self): + return 'session_dir', self.childnum, self.dir + def get(self, id): + if not id: return None + filename = os.path.join(self.dir, self.prefix+id) + f=None + sessionInfo = None + try: + f=open(filename, 'rb') + sessionInfo = pickle.load(f) + f.close() + except: + try: + if f: f.close() + os.unlink(filename) + except: pass + if sessionInfo: + if time.time() > sessionInfo['expire']: + self.delete(id) + return None + else: return sessionInfo['state'] + else: return None + def delete(self, id): + try: + filename = os.path.join(self.dir, self.prefix+id) + os.remove(filename) + except: pass + def clear(self): + for id in self.keys(): + self.delete(id) + def set(self, state, expire, id=None): + f=None + try: + if id: + filename = os.path.join(self.dir, self.prefix+id) + f=open(filename, 'wb') + else: + filename, f, id = openUniqueFile(self.dir, self.prefix, ('%d_' % self.childnum)) + sessionInfo = {} + sessionInfo['expire'] = int(time.time())+expire + sessionInfo['state'] = state + pickle.dump(sessionInfo, f, -1) + f.close() + except: + try: + if f: f.close() + except: pass + raise + return id + def keys(self): + sessions = os.listdir(self.dir) + sessions = filter(lambda s, p=self.prefix: s[:len(p)]==p, sessions) + sessions = map(lambda s, self=self: s[len(self.prefix):], sessions) + return sessions + +# requires unique (dir, prefix) +def openUniqueFile(dir, prefix, unique, mode='w', max=1000000): + filelock = spyceLock.fileLock(os.path.join(dir, prefix)) + filelock.acquire() + try: + id = "%06d"%random.randrange(max) + filename = os.path.join(dir, prefix+unique+id) + while os.path.exists(filename): + id = str(random.randrange(max)) + filename = os.path.join(dir, prefix+unique+id) + f = None + f = open(filename, mode) + return filename, f, unique+id + finally: + filelock.release() + +################################################## +# Hash file session handlers +# + +class sessionHandlerDBM(sessionHandler): + def __init__(self, sessionModule, filename): + sessionHandler.__init__(self, sessionModule) + self.filename = filename + self.dbm = None + self.BINARY_MODE = 1 + self.dbm_type = None # redefine in subclass + def getHandlerID(self): + return 'session_'+self.dbm_type, self.childnum, self.filename + def _open(self): + raise 'need to implement' + def _close(self): + if self.dbm: + self.dbm.close() + self.dbm = None + def get(self, id): + if not id: return None + self._open() + try: + expire, state = None, None + if self.dbm.has_key(id): + expire, state = pickle.loads(self.dbm[id]) + if expire!=None and time.time() > expire: + self.delete(id) + state = None + return state + finally: + self._close() + def delete(self, id): + self._open() + try: + if self.dbm.has_key(id): + del self.dbm[id] + finally: + self._close() + def clear(self): + if os.path.exists(self.filename): + os.unlink(self.filename) + def set(self, state, expire, id=None): + self._open() + try: + if not id: + id = generateKey(self.dbm, self.childnum) + value = pickle.dumps( (int(time.time())+expire, state), self.BINARY_MODE) + self.dbm[id] = value + return id + finally: + self._close() + def keys(self): + self._open() + try: + return self.dbm.keys() + finally: + self._close() + +def opendb(dbm_session_handler, module, filename, flags): + mod = __import__(module) + if not dbm_session_handler.dbm: + dbm_session_handler.dbm = mod.open(filename, flags) + +class session_gdbm(sessionHandlerDBM): + def __init__(self, sessionModule, filename): + sessionHandlerDBM.__init__(self, sessionModule, filename) + self.dbm_type = 'gdbm' + def _open(self): + opendb(self, self.dbm_type, self.filename, 'cu') + +class session_bsddb(sessionHandlerDBM): + def __init__(self, sessionModule, filename): + sessionHandlerDBM.__init__(self, sessionModule, filename) + self.dbm_type = 'bsddb' + def _open(self): + opendb(self, 'dbhash', self.filename, 'c') + +def generateKey(hash, prefix, max = 1000000): + prefix = str(prefix)+'_' + key = random.randrange(max) + while hash.has_key(prefix+str(key)): + key = random.randrange(max) + key = prefix+str(key) + hash[key] = pickle.dumps(None, 1) + return key + + +################################################## +# User callback session handlers +# + +class session_user(sessionHandler): + '''User-callback session handler''' + def __init__(self, sessionModule, getf, setf, delf, idsf, info=None): + self.serverID = sessionModule._api.getServerID() + self.info = info + self.getf = getf + self.setf = setf + self.delf = delf + self.idsf = idsf + def getHandlerID(self): + return 'session_user', self.serverID, self.info + def get(self, id): # method should delete, if session is stale + return self.getf(self.info, id) + def set(self, state, expire, id): + return self.setf(self.info, state, expire, self.serverID, id) + def delete(self, id): + return self.delf(self.info, id) + def keys(self): + return self.idsf(self.info) + def clear(self): + for id in self.keys(): + self.delete(id) + +################################################## +# database-based session handlers +# + +# rimtodo: database-based session handler + diff --git a/spyce-2.1/contrib/modules/spydurus.py b/spyce-2.1/contrib/modules/spydurus.py new file mode 100755 index 0000000000000000000000000000000000000000..1058ef16ab421401077233941f5568e25e5fece6 --- /dev/null +++ b/spyce-2.1/contrib/modules/spydurus.py @@ -0,0 +1,110 @@ +# spydurus provides a threadsafe, pooled Spyce interface to a durus database. +# Author: Jonathan Ellis + +# If you're just trying to get a feel for Spyce, the main thing to +# understand is that this provides (via a subclass) the get method you +# see used (e.g., "data.get(id)") in .spy pages. More than that you don't +# really need to know unless you need to write your own connection +# pool module. + +CONNECTIONS = 3 # max connections to put in the pool + +import sys, os, os.path, threading, time +import spyce, spyceUtil + +# try to start a server: +# in a production environment, you'd keep it running with inittab or something, +# so this step wouldn't be necessary. + +# The best approach is to fork and then use StorageServer.serve to start durus; +# that way we don't have to worry about durus being on the path. However, +# since win32 doesn't support fork, AND that's where we're most likely to have +# PATH issues (durus on win32 doesn't modify the path on install), +# in the interest of making this run out of the box for windows people we'll +# take the StorageServer approach, but in a thread, not a new process. + +# Please use the inittab approach in production; Durus will be more performant +# in its own process. +from durus.storage_server import StorageServer +from durus.file_storage import FileStorage, TempFileStorage +_runningDurus = False +def maybeStartDurus(db_path): + if spyce.getServer().threaded(): + _runningDurus = True + def _maybeStartDurus(): + st = db_path and FileStorage(db_path) or TempFileStorage() + try: + StorageServer(st).serve() + except: + # already running + _runningDurus = False + _t = threading.Thread(target=_maybeStartDurus) + _t.start() + def _cleanup(): + if _runningDurus: + from durus.run_durus import stop_durus, DEFAULT_HOST, DEFAULT_PORT + stop_durus(DEFAULT_HOST, DEFAULT_PORT) + _t.join() + import atexit + atexit.register(_cleanup) + +import Queue +q = Queue.Queue() + +def initPool(connections=3, db_path=None): + "if another process takes care of durus, you never need to pass db_path" + from durus.connection import Connection + if spyce.getServer().threaded(): + from durus.client_storage import ClientStorage + for i in range(connections * 3): + if q.qsize() >= connections: + break + try: + q.put(Connection(ClientStorage())) + except: + time.sleep(0.5) # sometimes takes a while for server to start + else: + # not a long-running process -- spyce running under [Fast]CGI or mod_python + st = spyceUtil.tryForAwhile(lambda: FileStorage(db_path)) + if not st: + raise 'timeout while getting durus connection' + q.put(Connection(st)) + if not q.qsize(): + raise 'no durus connections created' + +# the actual spyceModule is refreshingly short now that starting Durus is out of the way +from spyceModule import spyceModule + +class spydurus(spyceModule): + def start(self): + self.conn = None + try: + self.conn = q.get(timeout=10) + except Queue.Empty: + raise 'timeout while getting durus connection' + else: + self.conn.abort() # syncs connection + + # spyce automatically calls finish methods at the end of each request. + # this is an excellent way to make sure that our connection always + # returns to the queue. + def finish(self, err): + q.put(self.conn) + + def root(self): + return self.conn.get_root() + + def get(self, oid): + """oid should be a formatted oid, not a raw _p_oid""" + return self.conn.get(int(oid)) + + def commit(self): + self.conn.commit() + +# you may wish to inherit from this instead of Persistent directly +from durus.persistent import Persistent +class PersistentWithId(Persistent): + # _p_format_oid is rather cumbersome to type + def id(self): + return self._p_format_oid() + diff --git a/spyce-2.1/contrib/modules/template.py b/spyce-2.1/contrib/modules/template.py new file mode 100755 index 0000000000000000000000000000000000000000..b47a2666ab703de7606d8d8c6e208338d2779f93 --- /dev/null +++ b/spyce-2.1/contrib/modules/template.py @@ -0,0 +1,83 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# CVS: $Id: template.py 859 2006-04-30 00:55:03Z ellisj $ +################################################## + +from spyceModule import spyceModule +import spyceException, spyceCache +import os + +__doc__ = """ +Template module provides templating functionality: the ability to separate +form from function, or HTML page design from programming code. This module +currently provides support for the Cheetah template engine. + +The Cheetah engine is invoked as follows: + cheetah ( file, [lookup] ) + +Calling this function will invoke the Cheetah engine to compile (and +cache) the template file> provided. The engine then "runs" the +template and fills in the appropriate data from the lookup dictionary, +or list of dictionaries. If the lookup is omitted, the convenient +default is to use the local and global variables from the current +context. The template is filled and the resulting string is returned. + +In general, that the Python path must simply include the Cheetah +installation directory and Spyce will find it. + +Support for other templating engines could be added in a similar fashion. +""" + +class template(spyceModule): + def cheetah(self, filename, lookup=None): + "Hook into the Cheetah template engine." + # check whether cheetah installed + from Cheetah.Compiler import Compiler + # define template cache + if not self._api.getModule('pool').has_key('cheetahCache'): + self._api.getModule('pool')['cheetahCache'] = spyceCache.semanticCache(spyceCache.memoryCache(), cheetahValid, cheetahGenerate) + cheetahCache = self._api.getModule('pool')['cheetahCache'] + # absolute filename, relative to script filename + filename = os.path.abspath(os.path.join( + os.path.dirname(self._api.getFilename()), filename)) + # set lookup variables + if lookup == None: + import inspect + lookup = [inspect.currentframe().f_back.f_locals, inspect.currentframe().f_back.f_globals] + elif type(lookup)!=type([]): + lookup = [lookup] + # compile (or get cached) and run template + return cheetahCache[filename](searchList=lookup) + +################################################## +# Cheetah semantic cache helper functions +# + +def cheetahValid(filename, validity): + try: + return os.path.getmtime(filename) == validity + except: return 0 + +def cheetahGenerate(filename): + # check permissions + if not os.path.exists(filename): + raise spyceException.spyceNotFound(filename) + if not os.access(filename, os.R_OK): + raise spyceException.spyceForbidden(filename) + # read the template + f = None + try: + f = open(filename, 'r') + buf = f.read() + finally: + if f: f.close() + # compile template, get timestamp + mtime = os.path.getmtime(filename) + from Cheetah.Compiler import Compiler + code = Compiler(source=buf).__str__() + dict = {} + exec code in dict + return mtime, dict['GenTemplate'] + diff --git a/spyce-2.1/contrib/modules/toc.py b/spyce-2.1/contrib/modules/toc.py new file mode 100755 index 0000000000000000000000000000000000000000..8cbb549e128e72d9cd2d4e7cffe51dffd7d34901 --- /dev/null +++ b/spyce-2.1/contrib/modules/toc.py @@ -0,0 +1,381 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: toc.py 944 2006-07-22 17:25:58Z ellisj $ +################################################## + +from spyceModule import spyceModule +import tree + +__doc__ = ''' +The TOC module provides support for constructing a table contents for a +lengthy document, such as this user documentation. The primary task of the TOC +module is to maintain a document tree, and initiate callbacks at the +appropriate points in the document. Note that this module may automatically +force a secondary processing of the Spyce file to resolve forward references. +

+ +The module provides the following methods to segment the document: + +

    + +
  • begin( data, [tag] ):
    Increase the nesting level and add a + new section. The data is stored in the document tree, and used for + callbacks (see later). An optional tag may be associated with the + node, otherwise one will automatically be generated. The function b() + is equivalent.
  • + +

  • next( data, [tag] ):
    Add a new section at the same nesting + level. The data is stored in the document tree, and used for + callbacks (see later). An optional tag may be associated with the + node, or one will be automatically generated. The function n() is + equivalent.
  • + +

  • end():
    Decrease the nesting level. The function e() + is equivalent.
  • + +

  • anchor( data, [tag] ):
    Set data and optionally the + tag associated with the root of the document tree. If the tag is + omitted, it defaults to the string 'root'.
  • + +

  • level( depth, data, [tag] ):
    Start a new section at given + depth with given data and optional tag. The necessary + begin(), next() and end() calls are automatically made, based on the current + document depth, so both types of calls can be inter-mixed.
  • + +

  • l1( data, [tag] ):
    Start a level 1 section. This + function merely calls level(1, datatag). + The functions, l2()...l9() are similarly defined.
  • + +

+ +The following methods provide access to document information: + +

    + +
  • getTag():
    Return the tag of the current document section. +
  • + +

  • getNumbering( [tag] )
    Return the numbering of some section + of the document identified by the given tag. If the tag is omitted, + the current document section is assumed. The numbering is an array of + numbers. This function may return 'None' on the first pass through a + document.
  • + +

  • getData( [tag] )
    Return the data associated with some + section of the document identified by the given tag. If the tag is + omitted, the current document section is assumed. This function may return + 'None' on the first pass through a document.
  • + +

  • getDepth( [tag] )
    Return the depth of some section of the + document identified by the given tag. If the tag is omitted, the + current document section is assumed. This function may return 'None' on the + first pass through a document.
  • + +

  • getNextTag( [tag] )
    Return the tag of the section following + some section of the document identified by the given tag. If the tag + is omitted, the current document section is assumed. If this is the last + section of the document, then this function will return 'None'. This + function may return 'None' on the first pass through a document.
  • + +

  • getPrevTag( [tag] )
    Return the tag of the section before + some section of the document identified by the given tag. If the tag + is omitted, the current document section is assumed. If this is the first + section of the document, then this function will return 'None'. This + function may return 'None' on the first pass through a document.
  • + +

  • getParentTag( [tag] )
    Return the tag of the section above + (or containing) some section of the document identified by the given + tag. If the tag is omitted, the current document section is assumed. + If this is the top-most section of the document, then this function will + return 'None'. This function may return 'None' on the first pass through a + document.
  • + +

  • getChildrenTags( [tag] )
    Return a list (possibly empty) of + tags of the sections directly contained within some section of the document + identified by the given tag. If the tag is omitted, the current + document section is assumed. This function may return a shorter list than + anticipated or 'None', on the first pass through a document. + +

+ +The TOC modules can make callbacks to handlers that format the document +correctly. The handlers should be defined and registered before the first +section break in the document. The following functions register handlers: + +

    + +
  • setDOC_PUSH( f ):
    Register a function f to be called + when the nesting depth of the document increases.
  • + +

  • setDOC_POP( f ):
    Register a function f to be called + when the nesting depth of the document decreases.
  • + +

  • setDOC_START( f ):
    Register a funtion f to be called + at the beginning of a section.
  • + +

  • setDOC_END( f ):
    Register a function f to be called + at the end of a section.
  • + +

  • setTOC_PUSH( f ):
    Register a function f to be called + when the nesting depth of the table of contents increases.
  • + +

  • setTOC_POP( f ):
    Register a function f to be called + when the nesting depth of the table of contents decreases.
  • + +

  • setTOC_ENTRY( f ):
    Register a function f to be called + for each table of contents entry.
  • + +

+ +Each callback function should be of the form:

f(depth, +tag, numbering, data),
where: depth is the nesting depth, +tag is the associated tag, numbering is the position array, and +data is the associated data of the section for which the callback was +made.

+ +The DOC callbacks are made as the sections are encountered. The +TOC callbacks are made while printing the table of contents. If the +modules detects that forward references exist in the document, the document +will be processed twice, and only the second output will be sent. Note that +buffering MUST be turned on for this to function correctly.

+ +To display a table of contents, define the appropriate TOC callback functions +and call: + +

    + +
  • showTOC(): Display the table of contents. + +
+''' + +ROOT_NAME = 'root' + +class toc(spyceModule): + + def start(self): + if not self._api.getModule('pool').has_key('toc'): + self._api.getModule('pool')['toc'] = {} + try: + self.oldtree, self.oldtags = self._api.getModule('pool')['toc'][self._api.getFilename()] + except (KeyError, TypeError): + self.oldtree = tree.tree( (ROOT_NAME, [], None) ) + self.oldtags = {ROOT_NAME: self.oldtree} + # tree data: (tag, numbering, data) + self.tree = tree.tree((ROOT_NAME, [], None)) + self.tags = {ROOT_NAME: self.tree} + self.node = self.tree + self.numbering = [] + self.autotag = 0 + self.tocShown = 0 + self.fDOC_PUSH = None + self.fDOC_POP = None + self.fDOC_START = None + self.fDOC_END = None + self.fTOC_PUSH = None + self.fTOC_POP = None + self.fTOC_ENTRY = None + + def finish(self, theError): + if self.oldtree is not None: + self.oldtree.delete() + self.oldtree = None + self.oldtags = None + + def generate(self): + self.tree.computePreChain() + regenerate = not (self.oldtree == self.tree) + file = self._api.getFilename() + self._api.getModule('pool')['toc'][file] = self.tree, self.tags + if self.tocShown and regenerate: + self._api.getModule('redirect').internal(filename=file) + + # set callbacks + def setDOC_PUSH(self, f): + self.fDOC_PUSH = f + def setDOC_POP(self, f): + self.fDOC_POP = f + def setDOC_START(self, f): + self.fDOC_START = f + def setDOC_END(self, f): + self.fDOC_END = f + def setTOC_PUSH(self, f): + self.fTOC_PUSH = f + def setTOC_POP(self, f): + self.fTOC_POP = f + def setTOC_ENTRY(self, f): + self.fTOC_ENTRY = f + + # sectioning + def begin(self, data, tag=None, number=1): + self._emit(self.node, self.fDOC_PUSH) + self.numbering = _in(self.numbering) + if number: + self.numbering = _inc(self.numbering) + self.node = self.node.append( (tag, self.numbering, data) ) + else: + self.node = self.node.append( (tag, None, data) ) + if not tag: tag = self._genTag() + self.tags[tag] = self.node + self._emit(self.node, self.fDOC_START) + def end(self): + self._emit(self.node, self.fDOC_END) + self.numbering = _out(self.numbering) + self.node = self.node.parent + self._emit(self.node, self.fDOC_POP) + def next(self, data, tag=None, number=1): + self._emit(self.node, self.fDOC_END) + self.node = self.node.parent + if number: + self.numbering = _inc(self.numbering) + self.node = self.node.append( (tag, self.numbering, data) ) + else: + self.node = self.node.append( (tag, None, data) ) + if not tag: tag = self._genTag() + self.tags[tag] = self.node + self._emit(self.node, self.fDOC_START) + def anchor(self, data, tag=ROOT_NAME): + self.tree.data = tag, [], data + self.tags[tag] = self.tree + + # shortcuts + b=begin + e=end + n=next + + # sectioning by depth + def level(self, depth, data, tag=None): + curdepth = self.getDepth() + if curdepth > depth: # indent + while curdepth > depth: + self.end() + curdepth = self.getDepth() + self.next(data, tag) + elif curdepth < depth: # outdent + while curdepth < depth - 1: + self.begin(None) + curdepth = self.getDepth() + self.begin(data, tag) + else: # next + self.next(data, tag) + def l1(self, data, tag=None): + self.level(1, data, tag) + def l2(self, data, tag=None): + self.level(2, data, tag) + def l3(self, data, tag=None): + self.level(3, data, tag) + def l4(self, data, tag=None): + self.level(4, data, tag) + def l5(self, data, tag=None): + self.level(5, data, tag) + def l6(self, data, tag=None): + self.level(6, data, tag) + def l7(self, data, tag=None): + self.level(7, data, tag) + def l8(self, data, tag=None): + self.level(8, data, tag) + def l9(self, data, tag=None): + self.level(9, data, tag) + + # show toc + def showTOC(self): + self.tocShown = 1 + self._tocHelper(self.oldtree) + def _tocHelper(self, node): + self._emit(node, self.fTOC_ENTRY) + if node.children: + self._emit(node, self.fTOC_PUSH) + for c in node.children: + self._tocHelper(c) + self._emit(node, self.fTOC_POP) + + # current state + def getTag(self, node=None): + self.tocShown = 1 + if not node: node = self.node + tag, numbering, data = node.data + return tag + def getNumbering(self, tag=None): + self.tocShown = 1 + try: + node = self.node + if tag: node = self.oldtags[tag] + tag, numbering, data = node.data + return numbering + except KeyError: + return None + def getData(self, tag=None): + self.tocShown = 1 + try: + node = self.node + if tag: node = self.oldtags[tag] + tag, numbering, data = node.data + return data + except KeyError: + return None + def getDepth(self, tag=None): + self.tocShown = 1 + try: + node = self.node + if tag: node = self.tags[tag] + return node.depth + except KeyError: + return None + def getNextTag(self, tag=None): + self.tocShown = 1 + try: + if not tag: tag = self.getTag() + tag = self.oldtags[tag].next + if tag==None: return None + return self.getTag(tag) + except KeyError: + return None + def getPrevTag(self, tag=None): + self.tocShown = 1 + try: + if not tag: tag = self.getTag() + node = self.oldtags[tag].prev + if node==None: return None + return self.getTag(node) + except KeyError: + return None + def getParentTag(self, tag=None): + self.tocShown = 1 + try: + if not tag: tag = self.getTag() + node = self.oldtags[tag].parent + if node==None: return None + return self.getTag(node) + except KeyError: + return None + def getChildrenTags(self, tag=None): + self.tocShown = 1 + try: + if not tag: tag = self.getTag() + nodes = self.oldtags[tag].children + return map(self.getTag, nodes) + except KeyError: + return None + + # internal helpers + def _genTag(self): + tag = 'auto_'+str(self.autotag) + self.autotag = self.autotag + 1 + return tag + def _emit(self, node, f): + tag, numbering, data = node.data + if f: s = f(node.depth, tag, numbering, data) + +# hierarchical counting +def _inc(numbering, inc=1): + return numbering[:-1]+[numbering[-1]+inc] +def _in(numbering, start=0): + return numbering+[start] +def _out(numbering): + return numbering[:-1] + +def defaultOutput(tag, numbering, data): + return reduce(lambda s, i: '%s%d.' % (s, i), numbering, '') + ' ' + str(data) diff --git a/spyce-2.1/contrib/modules/toc.pyc b/spyce-2.1/contrib/modules/toc.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3449815f0a208a222c43fb437498e9c132d35b19 Binary files /dev/null and b/spyce-2.1/contrib/modules/toc.pyc differ diff --git a/spyce-2.1/contrib/pyweboff/config.py b/spyce-2.1/contrib/pyweboff/config.py new file mode 100755 index 0000000000000000000000000000000000000000..290584a2b7086ee08ae9e10077c83d558a844dae --- /dev/null +++ b/spyce-2.1/contrib/pyweboff/config.py @@ -0,0 +1,31 @@ +from spyceconf import * + +# The root option defines the path from which Spyce requests are processed. +# I.e., when a request for http://yourserver/path/foo.spy arrives, +# Spyce looks for foo.spy in /path/. +PYWEBOFF_HOME = os.path.abspath(os.path.split(__file__)[0]) +root = os.path.join(PYWEBOFF_HOME, 'www') +# This allows you to import .py modules from the lib directory. +sys.path.append(os.path.join(PYWEBOFF_HOME, 'lib')) + +# Some commonly overridden options -- see spyceconf.py from the Spyce +# distribution for details and the full set of options. +port = 8001 # webserver port +indexExtensions = ['spy'] # list of extensions to check if directory requested + +#### pyweboff config #### +db = SqlSoup('sqlite:///%s' % os.path.join(PYWEBOFF_HOME, 'pyweboff.db')) + +# admin login is "Bhargan Basepair", "basepair" +def validator(login, password): + L = db.users.select_by(name=login, password=password) + if L: + return login + return None +def admin_validator(login, password): + L = db.users.select_by(name=login, password=password, admin=True) + if L: + return login + return None +login_defaultvalidator = validator +login_storage = FileStorage(os.path.join(PYWEBOFF_HOME, 'login-tokens')) diff --git a/spyce-2.1/contrib/pyweboff/lib/actions.py b/spyce-2.1/contrib/pyweboff/lib/actions.py new file mode 100755 index 0000000000000000000000000000000000000000..9a949a394d5441fcd6f6c6a9dc672a2afa55e960 --- /dev/null +++ b/spyce-2.1/contrib/pyweboff/lib/actions.py @@ -0,0 +1,19 @@ +from spyceConfig import db + +def book_loan(api, user_name, books): + for book_id in books: + db.loans.insert(user_name=user_name, book_id=book_id) + db.flush() + +def book_return(api, books): + for book_id in books: + loan = db.loans.selectone_by(book_id=book_id) + db.delete(loan) + db.flush() + +def user_new(api, name, email, password, classname, admin): + u = db.users.insert(name=name, email=email, password=password, classname=classname, admin=admin) + u.admin = admin + db.flush() + api.redirect.external('/') + api.response.end() diff --git a/spyce-2.1/contrib/pyweboff/lib/pyweboff.sql b/spyce-2.1/contrib/pyweboff/lib/pyweboff.sql new file mode 100755 index 0000000000000000000000000000000000000000..dde7381a06c25df198fdd3afa8de1efaf04e8f14 --- /dev/null +++ b/spyce-2.1/contrib/pyweboff/lib/pyweboff.sql @@ -0,0 +1,38 @@ +CREATE TABLE books ( + id integer PRIMARY KEY, -- auto-SERIAL in sqlite + title text NOT NULL, + published_year char(4) NOT NULL, + authors text NOT NULL +); + +CREATE TABLE users ( + name varchar(32) PRIMARY KEY, + email varchar(128) NOT NULL, + password varchar(128) NOT NULL, + classname text, + admin int NOT NULL -- 0 = false +); + +CREATE TABLE loans ( + book_id int PRIMARY KEY REFERENCES books(id), + user_name varchar(32) references users(name) + ON DELETE SET NULL ON UPDATE CASCADE, + loan_date date DEFAULT current_timestamp +); + +insert into users(name, email, password, admin) +values('Bhargan Basepair', 'basepair@example.edu', 'basepair', 1); +insert into users(name, email, password, admin) +values('Joe Student', 'student@example.edu', 'student', 0); + +insert into books(title, published_year, authors) +values('Mustards I Have Known', '1989', 'Jones'); +insert into books(title, published_year, authors) +values('Regional Variation in Moss', '1971', 'Flim and Flam'); + +insert into loans(book_id, user_name, loan_date) +values ( + (select min(id) from books), + (select name from users where name like 'Joe%'), + '2006-07-12 0:0:0') +; diff --git a/spyce-2.1/contrib/pyweboff/www/index.spy b/spyce-2.1/contrib/pyweboff/www/index.spy new file mode 100755 index 0000000000000000000000000000000000000000..ff3a1a68a4d2adc62a0a3e424d7cac44e0f0a3e3 --- /dev/null +++ b/spyce-2.1/contrib/pyweboff/www/index.spy @@ -0,0 +1,73 @@ +[[\ +from spyceConfig import db + +books = db.books.select() +users = db.users.select_by(admin=False) +]] + + + +[[! +def new_user(self, api): + api.redirect.external('user-new.spy') + api.response.end() +]] + + + +[[ current_user = db.users.selectone_by(name=request.login_id()) ]] +Welcome, [[= current_user.name ]]
+
+ +
+ + + + + + + + + + + +[[\ +# sqlite3 has a bug that prevents using the simpler join +# of (books, (users, loans)) +book_loans = db.join(db.join(db.books, db.loans, isouter=True), + db.users, isouter=True) +]] +[[ for book_loan in book_loans.select():{ ]] + + + + + + [[ if book_loan.user_name:{ # loaned out ]] + + [[ if current_user.admin or current_user.classname == book_loan.classname:{ ]] + + [[ } ]] + + [[ }else:{ ]] + + + + [[ } ]] + +[[ } ]] +
TitlePublishedAuthor(s)On loan toBorrower's emailLoaned
[[= book_loan.title ]][[= book_loan.published_year ]][[= book_loan.authors ]][[= book_loan.user_name ]][[= book_loan.email ]][[= book_loan.loan_date ]]
+ +
+[[ if current_user.admin:{ ]] +
    +
  • + +
  • +
  • +
+[[ } ]] + + + +
diff --git a/spyce-2.1/contrib/pyweboff/www/parent.spi b/spyce-2.1/contrib/pyweboff/www/parent.spi new file mode 100755 index 0000000000000000000000000000000000000000..d4efb6ae7ed6a3d4692e00e01c296dbf14549b71 --- /dev/null +++ b/spyce-2.1/contrib/pyweboff/www/parent.spi @@ -0,0 +1,46 @@ + + + + [[= child.title ]] + + + + +

[[= child.title ]]

+
+ +
[[= child._body ]]
+ + + + diff --git a/spyce-2.1/contrib/pyweboff/www/user-new.spy b/spyce-2.1/contrib/pyweboff/www/user-new.spy new file mode 100755 index 0000000000000000000000000000000000000000..727f0090692ed23ad6b367e553adac755984dd14 --- /dev/null +++ b/spyce-2.1/contrib/pyweboff/www/user-new.spy @@ -0,0 +1,12 @@ + + + + + + + + + + +

+
diff --git a/spyce-2.1/contrib/pyweboff/www/util/form_calendar.gif b/spyce-2.1/contrib/pyweboff/www/util/form_calendar.gif new file mode 100755 index 0000000000000000000000000000000000000000..2ae947fb21f30fd9a571f46d727b51ab2c572258 Binary files /dev/null and b/spyce-2.1/contrib/pyweboff/www/util/form_calendar.gif differ diff --git a/spyce-2.1/contrib/pyweboff/www/util/form_calendar.js b/spyce-2.1/contrib/pyweboff/www/util/form_calendar.js new file mode 100755 index 0000000000000000000000000000000000000000..394556aa14e016e42a723081829c23fde412227f --- /dev/null +++ b/spyce-2.1/contrib/pyweboff/www/util/form_calendar.js @@ -0,0 +1,1464 @@ +// =================================================================== +// Author: Matt Kruse +// WWW: http://www.mattkruse.com/ +// +// NOTICE: You may use this code for any purpose, commercial or +// private, without any further permission from the author. You may +// remove this notice from your final code if you wish, however it is +// appreciated by the author if at least my web site address is kept. +// +// You may *NOT* re-distribute this code in any way except through its +// use. That means, you can include it in your product, or your web +// site, or any other form where the code is actually being used. You +// may not put the plain javascript up on your site for download or +// include it in your javascript libraries for download. +// If you wish to share this code with others, please just point them +// to the URL instead. +// Please DO NOT link directly to my .js files from your site. Copy +// the files to your server and use them there. Thank you. +// +// (Redistribution in Spyce is with permission.) +// =================================================================== + + +/* SOURCE FILE: AnchorPosition.js */ + +/* +AnchorPosition.js +Author: Matt Kruse +Last modified: 5/24/04 + +DESCRIPTION: These functions find the position of an tag in a document, +so other elements can be positioned relative to it. + +COMPATABILITY: Netscape 4.x,6.x,Mozilla, IE 5.x,6.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. + +FUNCTIONS: +getAnchorPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor. Position is relative to the PAGE. + +getAnchorWindowPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor, relative to the WHOLE SCREEN. + +NOTES: + +1) For popping up separate browser windows, use getAnchorWindowPosition. + Otherwise, use getAnchorPosition + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. +*/ + +// getAnchorPosition(anchorname) +// This function returns an object having .x and .y properties which are the coordinates +// of the named anchor, relative to the page. +function getAnchorPosition(anchorname) { + // This function will return an Object with x and y properties + var useWindow=false; + var coordinates=new Object(); + var x=0,y=0; + // Browser capability sniffing + var use_gebi=false, use_css=false, use_layers=false; + if (document.getElementById) { use_gebi=true; } + else if (document.all) { use_css=true; } + else if (document.layers) { use_layers=true; } + // Logic to find position + if (use_gebi && document.all) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_gebi) { + var o=document.getElementById(anchorname); + x=AnchorPosition_getPageOffsetLeft(o); + y=AnchorPosition_getPageOffsetTop(o); + } + else if (use_css) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_layers) { + var found=0; + for (var i=0; i9?"":"0")+x} + +// ------------------------------------------------------------------ +// isDate ( date_string, format_string ) +// Returns true if date string matches format of format string and +// is a valid date. Else returns false. +// It is recommended that you trim whitespace around the value before +// passing it to this function, as whitespace is NOT ignored! +// ------------------------------------------------------------------ +function isDate(val,format) { + var date=getDateFromFormat(val,format); + if (date==0) { return false; } + return true; + } + +// ------------------------------------------------------------------- +// compareDates(date1,date1format,date2,date2format) +// Compare two date strings to see which is greater. +// Returns: +// 1 if date1 is greater than date2 +// 0 if date2 is greater than date1 of if they are the same +// -1 if either of the dates is in an invalid format +// ------------------------------------------------------------------- +function compareDates(date1,dateformat1,date2,dateformat2) { + var d1=getDateFromFormat(date1,dateformat1); + var d2=getDateFromFormat(date2,dateformat2); + if (d1==0 || d2==0) { + return -1; + } + else if (d1 > d2) { + return 1; + } + return 0; + } + +// ------------------------------------------------------------------ +// formatDate (date_object, format) +// Returns a date in the output format specified. +// The format string uses the same abbreviations as in getDateFromFormat() +// ------------------------------------------------------------------ +function formatDate(date,format) { + format=format+""; + var result=""; + var i_format=0; + var c=""; + var token=""; + var y=date.getYear()+""; + var M=date.getMonth()+1; + var d=date.getDate(); + var E=date.getDay(); + var H=date.getHours(); + var m=date.getMinutes(); + var s=date.getSeconds(); + var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k; + // Convert real date parts into formatted versions + var value=new Object(); + if (y.length < 4) {y=""+(y-0+1900);} + value["y"]=""+y; + value["yyyy"]=y; + value["yy"]=y.substring(2,4); + value["M"]=M; + value["MM"]=LZ(M); + value["MMM"]=MONTH_NAMES[M-1]; + value["NNN"]=MONTH_NAMES[M+11]; + value["d"]=d; + value["dd"]=LZ(d); + value["E"]=DAY_NAMES[E+7]; + value["EE"]=DAY_NAMES[E]; + value["H"]=H; + value["HH"]=LZ(H); + if (H==0){value["h"]=12;} + else if (H>12){value["h"]=H-12;} + else {value["h"]=H;} + value["hh"]=LZ(value["h"]); + if (H>11){value["K"]=H-12;} else {value["K"]=H;} + value["k"]=H+1; + value["KK"]=LZ(value["K"]); + value["kk"]=LZ(value["k"]); + if (H > 11) { value["a"]="PM"; } + else { value["a"]="AM"; } + value["m"]=m; + value["mm"]=LZ(m); + value["s"]=s; + value["ss"]=LZ(s); + while (i_format < format.length) { + c=format.charAt(i_format); + token=""; + while ((format.charAt(i_format)==c) && (i_format < format.length)) { + token += format.charAt(i_format++); + } + if (value[token] != null) { result=result + value[token]; } + else { result=result + token; } + } + return result; + } + +// ------------------------------------------------------------------ +// Utility functions for parsing in getDateFromFormat() +// ------------------------------------------------------------------ +function _isInteger(val) { + var digits="1234567890"; + for (var i=0; i < val.length; i++) { + if (digits.indexOf(val.charAt(i))==-1) { return false; } + } + return true; + } +function _getInt(str,i,minlength,maxlength) { + for (var x=maxlength; x>=minlength; x--) { + var token=str.substring(i,i+x); + if (token.length < minlength) { return null; } + if (_isInteger(token)) { return token; } + } + return null; + } + +// ------------------------------------------------------------------ +// getDateFromFormat( date_string , format_string ) +// +// This function takes a date string and a format string. It matches +// If the date string matches the format string, it returns the +// getTime() of the date. If it does not match, it returns 0. +// ------------------------------------------------------------------ +function getDateFromFormat(val,format) { + val=val+""; + format=format+""; + var i_val=0; + var i_format=0; + var c=""; + var token=""; + var token2=""; + var x,y; + var now=new Date(); + var year=now.getYear(); + var month=now.getMonth()+1; + var date=1; + var hh=now.getHours(); + var mm=now.getMinutes(); + var ss=now.getSeconds(); + var ampm=""; + + while (i_format < format.length) { + // Get next token from format string + c=format.charAt(i_format); + token=""; + while ((format.charAt(i_format)==c) && (i_format < format.length)) { + token += format.charAt(i_format++); + } + // Extract contents of value based on format token + if (token=="yyyy" || token=="yy" || token=="y") { + if (token=="yyyy") { x=4;y=4; } + if (token=="yy") { x=2;y=2; } + if (token=="y") { x=2;y=4; } + year=_getInt(val,i_val,x,y); + if (year==null) { return 0; } + i_val += year.length; + if (year.length==2) { + if (year > 70) { year=1900+(year-0); } + else { year=2000+(year-0); } + } + } + else if (token=="MMM"||token=="NNN"){ + month=0; + for (var i=0; i11)) { + month=i+1; + if (month>12) { month -= 12; } + i_val += month_name.length; + break; + } + } + } + if ((month < 1)||(month>12)){return 0;} + } + else if (token=="EE"||token=="E"){ + for (var i=0; i12)){return 0;} + i_val+=month.length;} + else if (token=="dd"||token=="d") { + date=_getInt(val,i_val,token.length,2); + if(date==null||(date<1)||(date>31)){return 0;} + i_val+=date.length;} + else if (token=="hh"||token=="h") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<1)||(hh>12)){return 0;} + i_val+=hh.length;} + else if (token=="HH"||token=="H") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<0)||(hh>23)){return 0;} + i_val+=hh.length;} + else if (token=="KK"||token=="K") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<0)||(hh>11)){return 0;} + i_val+=hh.length;} + else if (token=="kk"||token=="k") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<1)||(hh>24)){return 0;} + i_val+=hh.length;hh--;} + else if (token=="mm"||token=="m") { + mm=_getInt(val,i_val,token.length,2); + if(mm==null||(mm<0)||(mm>59)){return 0;} + i_val+=mm.length;} + else if (token=="ss"||token=="s") { + ss=_getInt(val,i_val,token.length,2); + if(ss==null||(ss<0)||(ss>59)){return 0;} + i_val+=ss.length;} + else if (token=="a") { + if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";} + else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";} + else {return 0;} + i_val+=2;} + else { + if (val.substring(i_val,i_val+token.length)!=token) {return 0;} + else {i_val+=token.length;} + } + } + // If there are any trailing characters left in the value, it doesn't match + if (i_val != val.length) { return 0; } + // Is date valid for month? + if (month==2) { + // Check for leap year + if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year + if (date > 29){ return 0; } + } + else { if (date > 28) { return 0; } } + } + if ((month==4)||(month==6)||(month==9)||(month==11)) { + if (date > 30) { return 0; } + } + // Correct hours value + if (hh<12 && ampm=="PM") { hh=hh-0+12; } + else if (hh>11 && ampm=="AM") { hh-=12; } + var newdate=new Date(year,month-1,date,hh,mm,ss); + return newdate.getTime(); + } + +// ------------------------------------------------------------------ +// parseDate( date_string [, prefer_euro_format] ) +// +// This function takes a date string and tries to match it to a +// number of possible date formats to get the value. It will try to +// match against the following international formats, in this order: +// y-M-d MMM d, y MMM d,y y-MMM-d d-MMM-y MMM d +// M/d/y M-d-y M.d.y MMM-d M/d M-d +// d/M/y d-M-y d.M.y d-MMM d/M d-M +// A second argument may be passed to instruct the method to search +// for formats like d/M/y (european format) before M/d/y (American). +// Returns a Date object or null if no patterns match. +// ------------------------------------------------------------------ +function parseDate(val) { + var preferEuro=(arguments.length==2)?arguments[1]:false; + generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d'); + monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d'); + dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M'); + var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst'); + var d=null; + for (var i=0; i tags may cause errors. + +USAGE: +// Create an object for a WINDOW popup +var win = new PopupWindow(); + +// Create an object for a DIV window using the DIV named 'mydiv' +var win = new PopupWindow('mydiv'); + +// Set the window to automatically hide itself when the user clicks +// anywhere else on the page except the popup +win.autoHide(); + +// Show the window relative to the anchor name passed in +win.showPopup(anchorname); + +// Hide the popup +win.hidePopup(); + +// Set the size of the popup window (only applies to WINDOW popups +win.setSize(width,height); + +// Populate the contents of the popup window that will be shown. If you +// change the contents while it is displayed, you will need to refresh() +win.populate(string); + +// set the URL of the window, rather than populating its contents +// manually +win.setUrl("http://www.site.com/"); + +// Refresh the contents of the popup +win.refresh(); + +// Specify how many pixels to the right of the anchor the popup will appear +win.offsetX = 50; + +// Specify how many pixels below the anchor the popup will appear +win.offsetY = 100; + +NOTES: +1) Requires the functions in AnchorPosition.js + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. + +4) When a PopupWindow object is created, a handler for 'onmouseup' is + attached to any event handler you may have already defined. Do NOT define + an event handler for 'onmouseup' after you define a PopupWindow object or + the autoHide() will not work correctly. +*/ + +// Set the position of the popup window based on the anchor +function PopupWindow_getXYPosition(anchorname) { + var coordinates; + if (this.type == "WINDOW") { + coordinates = getAnchorWindowPosition(anchorname); + } + else { + coordinates = getAnchorPosition(anchorname); + } + this.x = coordinates.x; + this.y = coordinates.y; + } +// Set width/height of DIV/popup window +function PopupWindow_setSize(width,height) { + this.width = width; + this.height = height; + } +// Fill the window with contents +function PopupWindow_populate(contents) { + this.contents = contents; + this.populated = false; + } +// Set the URL to go to +function PopupWindow_setUrl(url) { + this.url = url; + } +// Set the window popup properties +function PopupWindow_setWindowProperties(props) { + this.windowProperties = props; + } +// Refresh the displayed contents of the popup +function PopupWindow_refresh() { + if (this.divName != null) { + // refresh the DIV object + if (this.use_gebi) { + document.getElementById(this.divName).innerHTML = this.contents; + } + else if (this.use_css) { + document.all[this.divName].innerHTML = this.contents; + } + else if (this.use_layers) { + var d = document.layers[this.divName]; + d.document.open(); + d.document.writeln(this.contents); + d.document.close(); + } + } + else { + if (this.popupWindow != null && !this.popupWindow.closed) { + if (this.url!="") { + this.popupWindow.location.href=this.url; + } + else { + this.popupWindow.document.open(); + this.popupWindow.document.writeln(this.contents); + this.popupWindow.document.close(); + } + this.popupWindow.focus(); + } + } + } +// Position and show the popup, relative to an anchor object +function PopupWindow_showPopup(anchorname) { + this.getXYPosition(anchorname); + this.x += this.offsetX; + this.y += this.offsetY; + if (!this.populated && (this.contents != "")) { + this.populated = true; + this.refresh(); + } + if (this.divName != null) { + // Show the DIV object + if (this.use_gebi) { + document.getElementById(this.divName).style.left = this.x + "px"; + document.getElementById(this.divName).style.top = this.y + "px"; + document.getElementById(this.divName).style.visibility = "visible"; + } + else if (this.use_css) { + document.all[this.divName].style.left = this.x; + document.all[this.divName].style.top = this.y; + document.all[this.divName].style.visibility = "visible"; + } + else if (this.use_layers) { + document.layers[this.divName].left = this.x; + document.layers[this.divName].top = this.y; + document.layers[this.divName].visibility = "visible"; + } + } + else { + if (this.popupWindow == null || this.popupWindow.closed) { + // If the popup window will go off-screen, move it so it doesn't + if (this.x<0) { this.x=0; } + if (this.y<0) { this.y=0; } + if (screen && screen.availHeight) { + if ((this.y + this.height) > screen.availHeight) { + this.y = screen.availHeight - this.height; + } + } + if (screen && screen.availWidth) { + if ((this.x + this.width) > screen.availWidth) { + this.x = screen.availWidth - this.width; + } + } + var avoidAboutBlank = window.opera || ( document.layers && !navigator.mimeTypes['*'] ) || navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ); + this.popupWindow = window.open(avoidAboutBlank?"":"about:blank","window_"+anchorname,this.windowProperties+",width="+this.width+",height="+this.height+",screenX="+this.x+",left="+this.x+",screenY="+this.y+",top="+this.y+""); + } + this.refresh(); + } + } +// Hide the popup +function PopupWindow_hidePopup() { + if (this.divName != null) { + if (this.use_gebi) { + document.getElementById(this.divName).style.visibility = "hidden"; + } + else if (this.use_css) { + document.all[this.divName].style.visibility = "hidden"; + } + else if (this.use_layers) { + document.layers[this.divName].visibility = "hidden"; + } + } + else { + if (this.popupWindow && !this.popupWindow.closed) { + this.popupWindow.close(); + this.popupWindow = null; + } + } + } +// Pass an event and return whether or not it was the popup DIV that was clicked +function PopupWindow_isClicked(e) { + if (this.divName != null) { + if (this.use_layers) { + var clickX = e.pageX; + var clickY = e.pageY; + var t = document.layers[this.divName]; + if ((clickX > t.left) && (clickX < t.left+t.clip.width) && (clickY > t.top) && (clickY < t.top+t.clip.height)) { + return true; + } + else { return false; } + } + else if (document.all) { // Need to hard-code this to trap IE for error-handling + var t = window.event.srcElement; + while (t.parentElement != null) { + if (t.id==this.divName) { + return true; + } + t = t.parentElement; + } + return false; + } + else if (this.use_gebi && e) { + var t = e.originalTarget; + while (t.parentNode != null) { + if (t.id==this.divName) { + return true; + } + t = t.parentNode; + } + return false; + } + return false; + } + return false; + } + +// Check an onMouseDown event to see if we should hide +function PopupWindow_hideIfNotClicked(e) { + if (this.autoHideEnabled && !this.isClicked(e)) { + this.hidePopup(); + } + } +// Call this to make the DIV disable automatically when mouse is clicked outside it +function PopupWindow_autoHide() { + this.autoHideEnabled = true; + } +// This global function checks all PopupWindow objects onmouseup to see if they should be hidden +function PopupWindow_hidePopupWindows(e) { + for (var i=0; i0) { + this.type="DIV"; + this.divName = arguments[0]; + } + else { + this.type="WINDOW"; + } + this.use_gebi = false; + this.use_css = false; + this.use_layers = false; + if (document.getElementById) { this.use_gebi = true; } + else if (document.all) { this.use_css = true; } + else if (document.layers) { this.use_layers = true; } + else { this.type = "WINDOW"; } + this.offsetX = 0; + this.offsetY = 0; + // Method mappings + this.getXYPosition = PopupWindow_getXYPosition; + this.populate = PopupWindow_populate; + this.setUrl = PopupWindow_setUrl; + this.setWindowProperties = PopupWindow_setWindowProperties; + this.refresh = PopupWindow_refresh; + this.showPopup = PopupWindow_showPopup; + this.hidePopup = PopupWindow_hidePopup; + this.setSize = PopupWindow_setSize; + this.isClicked = PopupWindow_isClicked; + this.autoHide = PopupWindow_autoHide; + this.hideIfNotClicked = PopupWindow_hideIfNotClicked; + } + +/* SOURCE FILE: CalendarPopup.js */ + +// HISTORY +// ------------------------------------------------------------------ +// Feb 7, 2005: Fixed a CSS styles to use px unit +// March 29, 2004: Added check in select() method for the form field +// being disabled. If it is, just return and don't do anything. +// March 24, 2004: Fixed bug - when month name and abbreviations were +// changed, date format still used original values. +// January 26, 2004: Added support for drop-down month and year +// navigation (Thanks to Chris Reid for the idea) +// September 22, 2003: Fixed a minor problem in YEAR calendar with +// CSS prefix. +// August 19, 2003: Renamed the function to get styles, and made it +// work correctly without an object reference +// August 18, 2003: Changed showYearNavigation and +// showYearNavigationInput to optionally take an argument of +// true or false +// July 31, 2003: Added text input option for year navigation. +// Added a per-calendar CSS prefix option to optionally use +// different styles for different calendars. +// July 29, 2003: Fixed bug causing the Today link to be clickable +// even though today falls in a disabled date range. +// Changed formatting to use pure CSS, allowing greater control +// over look-and-feel options. +// June 11, 2003: Fixed bug causing the Today link to be unselectable +// under certain cases when some days of week are disabled +// March 14, 2003: Added ability to disable individual dates or date +// ranges, display as light gray and strike-through +// March 14, 2003: Removed dependency on graypixel.gif and instead +/// use table border coloring +// March 12, 2003: Modified showCalendar() function to allow optional +// start-date parameter +// March 11, 2003: Modified select() function to allow optional +// start-date parameter +/* +DESCRIPTION: This object implements a popup calendar to allow the user to +select a date, month, quarter, or year. + +COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. +The calendar can be modified to work for any location in the world by +changing which weekday is displayed as the first column, changing the month +names, and changing the column headers for each day. + +USAGE: +// Create a new CalendarPopup object of type WINDOW +var cal = new CalendarPopup(); + +// Create a new CalendarPopup object of type DIV using the DIV named 'mydiv' +var cal = new CalendarPopup('mydiv'); + +// Easy method to link the popup calendar with an input box. +cal.select(inputObject, anchorname, dateFormat); +// Same method, but passing a default date other than the field's current value +cal.select(inputObject, anchorname, dateFormat, '01/02/2000'); +// This is an example call to the popup calendar from a link to populate an +// input box. Note that to use this, date.js must also be included!! +Select + +// Set the type of date select to be used. By default it is 'date'. +cal.setDisplayType(type); + +// When a date, month, quarter, or year is clicked, a function is called and +// passed the details. You must write this function, and tell the calendar +// popup what the function name is. +// Function to be called for 'date' select receives y, m, d +cal.setReturnFunction(functionname); +// Function to be called for 'month' select receives y, m +cal.setReturnMonthFunction(functionname); +// Function to be called for 'quarter' select receives y, q +cal.setReturnQuarterFunction(functionname); +// Function to be called for 'year' select receives y +cal.setReturnYearFunction(functionname); + +// Show the calendar relative to a given anchor +cal.showCalendar(anchorname); + +// Hide the calendar. The calendar is set to autoHide automatically +cal.hideCalendar(); + +// Set the month names to be used. Default are English month names +cal.setMonthNames("January","February","March",...); + +// Set the month abbreviations to be used. Default are English month abbreviations +cal.setMonthAbbreviations("Jan","Feb","Mar",...); + +// Show navigation for changing by the year, not just one month at a time +cal.showYearNavigation(); + +// Show month and year dropdowns, for quicker selection of month of dates +cal.showNavigationDropdowns(); + +// Set the text to be used above each day column. The days start with +// sunday regardless of the value of WeekStartDay +cal.setDayHeaders("S","M","T",...); + +// Set the day for the first column in the calendar grid. By default this +// is Sunday (0) but it may be changed to fit the conventions of other +// countries. +cal.setWeekStartDay(1); // week is Monday - Sunday + +// Set the weekdays which should be disabled in the 'date' select popup. You can +// then allow someone to only select week end dates, or Tuedays, for example +cal.setDisabledWeekDays(0,1); // To disable selecting the 1st or 2nd days of the week + +// Selectively disable individual days or date ranges. Disabled days will not +// be clickable, and show as strike-through text on current browsers. +// Date format is any format recognized by parseDate() in date.js +// Pass a single date to disable: +cal.addDisabledDates("2003-01-01"); +// Pass null as the first parameter to mean "anything up to and including" the +// passed date: +cal.addDisabledDates(null, "01/02/03"); +// Pass null as the second parameter to mean "including the passed date and +// anything after it: +cal.addDisabledDates("Jan 01, 2003", null); +// Pass two dates to disable all dates inbetween and including the two +cal.addDisabledDates("January 01, 2003", "Dec 31, 2003"); + +// When the 'year' select is displayed, set the number of years back from the +// current year to start listing years. Default is 2. +// This is also used for year drop-down, to decide how many years +/- to display +cal.setYearSelectStartOffset(2); + +// Text for the word "Today" appearing on the calendar +cal.setTodayText("Today"); + +// The calendar uses CSS classes for formatting. If you want your calendar to +// have unique styles, you can set the prefix that will be added to all the +// classes in the output. +// For example, normal output may have this: +// Today +// But if you set the prefix like this: +cal.setCssPrefix("Test"); +// The output will then look like: +// Today +// And you can define that style somewhere in your page. + +// When using Year navigation, you can make the year be an input box, so +// the user can manually change it and jump to any year +cal.showYearNavigationInput(); + +// Set the calendar offset to be different than the default. By default it +// will appear just below and to the right of the anchorname. So if you have +// a text box where the date will go and and anchor immediately after the +// text box, the calendar will display immediately under the text box. +cal.offsetX = 20; +cal.offsetY = 20; + +NOTES: +1) Requires the functions in AnchorPosition.js and PopupWindow.js + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. + +4) When a CalendarPopup object is created, a handler for 'onmouseup' is + attached to any event handler you may have already defined. Do NOT define + an event handler for 'onmouseup' after you define a CalendarPopup object + or the autoHide() will not work correctly. + +5) The calendar popup display uses style sheets to make it look nice. + +*/ + +// CONSTRUCTOR for the CalendarPopup Object +function CalendarPopup() { + var c; + if (arguments.length>0) { + c = new PopupWindow(arguments[0]); + } + else { + c = new PopupWindow(); + c.setSize(150,175); + } + c.offsetX = -152; + c.offsetY = 25; + c.autoHide(); + // Calendar-specific properties + c.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December"); + c.monthAbbreviations = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"); + c.dayHeaders = new Array("S","M","T","W","T","F","S"); + c.returnFunction = "CP_tmpReturnFunction"; + c.returnMonthFunction = "CP_tmpReturnMonthFunction"; + c.returnQuarterFunction = "CP_tmpReturnQuarterFunction"; + c.returnYearFunction = "CP_tmpReturnYearFunction"; + c.weekStartDay = 0; + c.isShowYearNavigation = false; + c.displayType = "date"; + c.disabledWeekDays = new Object(); + c.disabledDatesExpression = ""; + c.yearSelectStartOffset = 2; + c.currentDate = null; + c.todayText="Today"; + c.cssPrefix=""; + c.isShowNavigationDropdowns=false; + c.isShowYearNavigationInput=false; + window.CP_calendarObject = null; + window.CP_targetInput = null; + window.CP_dateFormat = "MM/dd/yyyy"; + // Method mappings + c.copyMonthNamesToWindow = CP_copyMonthNamesToWindow; + c.setReturnFunction = CP_setReturnFunction; + c.setReturnMonthFunction = CP_setReturnMonthFunction; + c.setReturnQuarterFunction = CP_setReturnQuarterFunction; + c.setReturnYearFunction = CP_setReturnYearFunction; + c.setMonthNames = CP_setMonthNames; + c.setMonthAbbreviations = CP_setMonthAbbreviations; + c.setDayHeaders = CP_setDayHeaders; + c.setWeekStartDay = CP_setWeekStartDay; + c.setDisplayType = CP_setDisplayType; + c.setDisabledWeekDays = CP_setDisabledWeekDays; + c.addDisabledDates = CP_addDisabledDates; + c.setYearSelectStartOffset = CP_setYearSelectStartOffset; + c.setTodayText = CP_setTodayText; + c.showYearNavigation = CP_showYearNavigation; + c.showCalendar = CP_showCalendar; + c.hideCalendar = CP_hideCalendar; + c.getStyles = getCalendarStyles; + c.refreshCalendar = CP_refreshCalendar; + c.getCalendar = CP_getCalendar; + c.select = CP_select; + c.setCssPrefix = CP_setCssPrefix; + c.showNavigationDropdowns = CP_showNavigationDropdowns; + c.showYearNavigationInput = CP_showYearNavigationInput; + c.copyMonthNamesToWindow(); + // Return the object + return c; + } +function CP_copyMonthNamesToWindow() { + // Copy these values over to the date.js + if (typeof(window.MONTH_NAMES)!="undefined" && window.MONTH_NAMES!=null) { + window.MONTH_NAMES = new Array(); + for (var i=0; i\n"; + result += '
\n'; + } + else { + result += '
\n'; + result += '
\n'; + result += '
\n'; + } + // Code for DATE display (default) + // ------------------------------- + if (this.displayType=="date" || this.displayType=="week-end") { + if (this.currentDate==null) { this.currentDate = now; } + if (arguments.length > 0) { var month = arguments[0]; } + else { var month = this.currentDate.getMonth()+1; } + if (arguments.length > 1 && arguments[1]>0 && arguments[1]-0==arguments[1]) { var year = arguments[1]; } + else { var year = this.currentDate.getFullYear(); } + var daysinmonth= new Array(0,31,28,31,30,31,30,31,31,30,31,30,31); + if ( ( (year%4 == 0)&&(year%100 != 0) ) || (year%400 == 0) ) { + daysinmonth[2] = 29; + } + var current_month = new Date(year,month-1,1); + var display_year = year; + var display_month = month; + var display_date = 1; + var weekday= current_month.getDay(); + var offset = 0; + + offset = (weekday >= this.weekStartDay) ? weekday-this.weekStartDay : 7-this.weekStartDay+weekday ; + if (offset > 0) { + display_month--; + if (display_month < 1) { display_month = 12; display_year--; } + display_date = daysinmonth[display_month]-offset+1; + } + var next_month = month+1; + var next_month_year = year; + if (next_month > 12) { next_month=1; next_month_year++; } + var last_month = month-1; + var last_month_year = year; + if (last_month < 1) { last_month=12; last_month_year--; } + var date_class; + if (this.type!="WINDOW") { + result += ""; + } + result += '\n'; + var refresh = windowref+'CP_refreshCalendar'; + var refreshLink = 'javascript:' + refresh; + if (this.isShowNavigationDropdowns) { + result += ''; + result += ''; + + result += ''; + } + else { + if (this.isShowYearNavigation) { + result += ''; + result += ''; + result += ''; + result += ''; + + result += ''; + if (this.isShowYearNavigationInput) { + result += ''; + } + else { + result += ''; + } + result += ''; + } + else { + result += '\n'; + result += '\n'; + result += '\n'; + } + } + result += '
 <'+this.monthNames[month-1]+'> <'+year+'><<'+this.monthNames[month-1]+' '+year+'>>
\n'; + result += '\n'; + result += '\n'; + for (var j=0; j<7; j++) { + + result += '\n'; + } + result += '\n'; + for (var row=1; row<=6; row++) { + result += '\n'; + for (var col=1; col<=7; col++) { + var disabled=false; + if (this.disabledDatesExpression!="") { + var ds=""+display_year+LZ(display_month)+LZ(display_date); + eval("disabled=("+this.disabledDatesExpression+")"); + } + var dateClass = ""; + if ((display_month == this.currentDate.getMonth()+1) && (display_date==this.currentDate.getDate()) && (display_year==this.currentDate.getFullYear())) { + dateClass = "cpCurrentDate"; + } + else if (display_month == month) { + dateClass = "cpCurrentMonthDate"; + } + else { + dateClass = "cpOtherMonthDate"; + } + if (disabled || this.disabledWeekDays[col-1]) { + result += ' \n'; + } + else { + var selected_date = display_date; + var selected_month = display_month; + var selected_year = display_year; + if (this.displayType=="week-end") { + var d = new Date(selected_year,selected_month-1,selected_date,0,0,0,0); + d.setDate(d.getDate() + (7-col)); + selected_year = d.getYear(); + if (selected_year < 1000) { selected_year += 1900; } + selected_month = d.getMonth()+1; + selected_date = d.getDate(); + } + result += ' \n'; + } + display_date++; + if (display_date > daysinmonth[display_month]) { + display_date=1; + display_month++; + } + if (display_month > 12) { + display_month=1; + display_year++; + } + } + result += ''; + } + var current_weekday = now.getDay() - this.weekStartDay; + if (current_weekday < 0) { + current_weekday += 7; + } + result += '\n'; + result += '
'+this.dayHeaders[(this.weekStartDay+j)%7]+'
'+display_date+''+display_date+'
\n'; + if (this.disabledDatesExpression!="") { + var ds=""+now.getFullYear()+LZ(now.getMonth()+1)+LZ(now.getDate()); + eval("disabled=("+this.disabledDatesExpression+")"); + } + if (disabled || this.disabledWeekDays[current_weekday+1]) { + result += ' '+this.todayText+'\n'; + } + else { + result += ' '+this.todayText+'\n'; + } + result += '
\n'; + result += '
\n'; + } + + // Code common for MONTH, QUARTER, YEAR + // ------------------------------------ + if (this.displayType=="month" || this.displayType=="quarter" || this.displayType=="year") { + if (arguments.length > 0) { var year = arguments[0]; } + else { + if (this.displayType=="year") { var year = now.getFullYear()-this.yearSelectStartOffset; } + else { var year = now.getFullYear(); } + } + if (this.displayType!="year" && this.isShowYearNavigation) { + result += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += ' \n'; + result += '
<<'+year+'>>
\n'; + } + } + + // Code for MONTH display + // ---------------------- + if (this.displayType=="month") { + // If POPUP, write entire HTML document + result += '\n'; + for (var i=0; i<4; i++) { + result += ''; + for (var j=0; j<3; j++) { + var monthindex = ((i*3)+j); + result += ''; + } + result += ''; + } + result += '
'+this.monthAbbreviations[monthindex]+'
\n'; + } + + // Code for QUARTER display + // ------------------------ + if (this.displayType=="quarter") { + result += '
\n'; + for (var i=0; i<2; i++) { + result += ''; + for (var j=0; j<2; j++) { + var quarter = ((i*2)+j+1); + result += ''; + } + result += ''; + } + result += '

Q'+quarter+'

\n'; + } + + // Code for YEAR display + // --------------------- + if (this.displayType=="year") { + var yearColumnSize = 4; + result += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += '
<<>>
\n'; + result += '\n'; + for (var i=0; i'+currentyear+''; + } + result += ''; + } + result += '
\n'; + } + // Common + if (this.type == "WINDOW") { + result += "\n"; + } + return result; + } diff --git a/spyce-2.1/contrib/pyweboff/www/util/spyce2.png b/spyce-2.1/contrib/pyweboff/www/util/spyce2.png new file mode 100755 index 0000000000000000000000000000000000000000..c3bd014e2bd24f9d6107b7c902834dc107b9ad48 Binary files /dev/null and b/spyce-2.1/contrib/pyweboff/www/util/spyce2.png differ diff --git a/spyce-2.1/contrib/readme.txt b/spyce-2.1/contrib/readme.txt new file mode 100755 index 0000000000000000000000000000000000000000..2821a3a04be65b5417d79a6a1bd4d6bf92b1ff30 --- /dev/null +++ b/spyce-2.1/contrib/readme.txt @@ -0,0 +1,32 @@ +Contents of contrib/: + +File What Author + +jedit-spyce.xml jEdit spyce syntax Marius Scurtescu +kate-spyce.xml Kate spyce syntax Jeffery Reavis +spyce.el Emacs spyce mode Jonathan Ellis +spyce.vim VIM spyce syntax Rimon Barr + + +xitami/ LWRP connector for Xitami Jim Madsen + + +modules/ spyce modules + + automaton.* state machine module Rimon Barr + etag.py cgi_buffer functionality Francisco Cabello + mail.* mail functionality Adrien Plisson + spydurus.py Durus connection pooling Jonathan Ellis + template.py Cheetah interface Rimon Barr + toc.py table-of-contents module Rimon Barr + + +tags/ spyce active tags + + flow.py flow-control tags Rimon Barr + + +pyweboff/ demo app; see Jonathan ellis + http://www.third-bit.com/pyweb/index.html + for description of "The Challenge" + run with "spyceCmd -l --conf contrib/pyweboff/config.py" diff --git a/spyce-2.1/contrib/spyce.el b/spyce-2.1/contrib/spyce.el new file mode 100755 index 0000000000000000000000000000000000000000..5128f0e9ef40c9050c0e3a86e7095379751dc984 --- /dev/null +++ b/spyce-2.1/contrib/spyce.el @@ -0,0 +1,26 @@ +; place in .emacs to enable syntax coloring for spyce files +; this is about as simple as you get with mmm-mode; improvements are welcome +; (mmm-mode is installed by default with recent versions of GNU|X Emacs) +(require 'mmm-auto) +(require 'mmm-sample) +(setq mmm-global-mode 'maybe) +(setq auto-mode-alist (cons '("\\.spy$" . html-mode) auto-mode-alist)) +(setq auto-mode-alist (cons '("\\.spi$" . html-mode) auto-mode-alist)) +(mmm-add-classes + '((spyce1 + :submode python-mode + :front "<%" + :back "%>") + (spyce2 + :submode python-mode + :front "\\[\\[" + :back "\\]\\]") + ) +) +(mmm-add-mode-ext-class nil "\\.spi$" 'spyce1) +(mmm-add-mode-ext-class nil "\\.spy$" 'spyce1) +(mmm-add-mode-ext-class nil "\\.spi$" 'spyce2) +(mmm-add-mode-ext-class nil "\\.spy$" 'spyce2) + +(mmm-add-mode-ext-class nil "\\.spi$" 'html-js) +(mmm-add-mode-ext-class nil "\\.spy$" 'html-js) diff --git a/spyce-2.1/contrib/spyce.vim b/spyce-2.1/contrib/spyce.vim new file mode 100755 index 0000000000000000000000000000000000000000..b14a749a1ce65c74121f7ad04ee71df9e322887a --- /dev/null +++ b/spyce-2.1/contrib/spyce.vim @@ -0,0 +1,110 @@ +" Vim syntax file +" Language: SPYCE +" Maintainer: Rimon Barr +" URL: http://spyce.sourceforge.net +" Last Change: 2003 Jul 13 + +" For version 5.x: Clear all syntax items +" For version 6.x: Quit when a syntax file was already loaded +if version < 600 + syntax clear +elseif exists("b:current_syntax") + finish +endif + +" we define it here so that included files can test for it +if !exists("main_syntax") + let main_syntax='spyce' +endif + +" Read the HTML syntax to start with +let b:did_indent = 1 " don't perform HTML indentation! +let html_no_rendering = 1 " do not render ,, etc... +if version < 600 + so :p:h/html.vim +else + runtime! syntax/html.vim + unlet b:current_syntax +endif + +" include python +syn include @Python :p:h/python.vim +syn include @Html :p:h/html.vim + +" spyce definitions +syn keyword spyceDirectiveKeyword include compact module import contained +syn keyword spyceDirectiveArg name names file as contained +syn region spyceDirectiveString start=+"+ end=+"+ contained +syn match spyceDirectiveValue "=[\t ]*[^'", \t>][^, \t>]*"hs=s+1 contained + +syn match spyceBeginErrorS ,\[\[, +syn match spyceBeginErrorA ,<%, +syn cluster spyceBeginError contains=spyceBeginErrorS,spyceBeginErrorA +syn match spyceEndErrorS ,\]\], +syn match spyceEndErrorA ,%>, +syn cluster spyceEndError contains=spyceEndErrorS,spyceEndErrorA + +syn match spyceEscBeginS ,\\\[\[, +syn match spyceEscBeginA ,\\<%, +syn cluster spyceEscBegin contains=spyceEscBeginS,spyceEscBeginA +syn match spyceEscEndS ,\\\]\], +syn match spyceEscEndA ,\\%>, +syn cluster spyceEscEnd contains=spyceEscEndS,spyceEscEndA +syn match spyceEscEndCommentS ,--\\\]\], +syn match spyceEscEndCommentA ,--\\%>, +syn cluster spyceEscEndComment contains=spyceEscEndCommentS,spyceEscEndCommentA + +syn region spyceStmtS matchgroup=spyceStmtDelim start=,\[\[, end=,\]\], contains=@Python,spyceLambdaS,spyceLambdaA,spyceBeginError keepend +syn region spyceStmtA matchgroup=spyceStmtDelim start=,<%, end=,%>, contains=@Python,spyceLambdaS,spyceLambdaA,spyceBeginError keepend +syn region spyceChunkS matchgroup=spyceChunkDelim start=,\[\[\\, end=,\]\], contains=@Python,spyceLambdaS,spyceLambdaA,spyceBeginError keepend +syn region spyceChunkA matchgroup=spyceChunkDelim start=,<%\\, end=,%>, contains=@Python,spyceLambdaS,spyceLambdaA,spyceBeginError keepend +syn region spyceEvalS matchgroup=spyceEvalDelim start=,\[\[=, end=,\]\], contains=@Python,spyceLambdaS,spyceLambdaA,spyceBeginError keepend +syn region spyceEvalA matchgroup=spyceEvalDelim start=,<%=, end=,%>, contains=@Python,spyceLambdaS,spyceLambdaA,spyceBeginError keepend +syn region spyceDirectiveS matchgroup=spyceDelim start=,\[\[\., end=,\]\], contains=spyceBeginError,spyceDirectiveKeyword,spyceDirectiveArg,spyceDirectiveValue,spyceDirectiveString keepend +syn region spyceDirectiveA matchgroup=spyceDelim start=,<%@, end=,%>, contains=spyceBeginError,spyceDirectiveKeyword,spyceDirectiveArg,spyceDirectiveValue,spyceDirectiveString keepend +syn region spyceCommentS matchgroup=spyceCommentDelim start=,\[\[--, end=,--\]\], +syn region spyceCommentA matchgroup=spyceCommentDelim start=,<%--, end=,--%>, +syn region spyceLambdaS matchgroup=spyceLambdaDelim start=,\[\[spy!\?, end=,\]\], contains=@Html,@spyce extend +syn region spyceLambdaA matchgroup=spyceLambdaDelim start=,<%spy!\?, end=,%>, contains=@Html,@spyce extend + +syn cluster spyce contains=spyceStmtS,spyceStmtA,spyceChunkS,spyceChunkA,spyceEvalS,spyceEvalA,spyceCommentS,spyceCommentA,spyceDirectiveS,spyceDirectiveA + +syn cluster htmlPreproc contains=@spyce + +hi link spyceDirectiveKeyword Special +hi link spyceDirectiveArg Type +hi link spyceDirectiveString String +hi link spyceDirectiveValue String + +hi link spyceDelim Special +hi link spyceStmtDelim spyceDelim +hi link spyceChunkDelim spyceDelim +hi link spyceEvalDelim spyceDelim +hi link spyceLambdaDelim spyceDelim +hi link spyceCommentDelim Comment + +hi link spyceBeginErrorS Error +hi link spyceBeginErrorA Error +hi link spyceEndErrorS Error +hi link spyceEndErrorA Error + +hi link spyceStmtS spyce +hi link spyceStmtA spyce +hi link spyceChunkS spyce +hi link spyceChunkA spyce +hi link spyceEvalS spyce +hi link spyceEvalA spyce +hi link spyceDirectiveS spyce +hi link spyceDirectiveA spyce +hi link spyceCommentS Comment +hi link spyceCommentA Comment +hi link spyceLambdaS Normal +hi link spyceLambdaA Normal + +hi link spyce Statement + +let b:current_syntax = "spyce" +if main_syntax == 'spyce' + unlet main_syntax +endif + diff --git a/spyce-2.1/contrib/tags/flow.py b/spyce-2.1/contrib/tags/flow.py new file mode 100755 index 0000000000000000000000000000000000000000..05f5d88ad1bd3dfb0ee4cb4b6ccb1268f1d2c8a4 --- /dev/null +++ b/spyce-2.1/contrib/tags/flow.py @@ -0,0 +1,117 @@ +from spyceTag import spyceTagLibrary, spyceTagPlus + +# use of these tags is deprecated; they will be removed in a future version of Spyce + +# These tags are modeled after some of the functionality that exists in Java's +# JSTL. However, with Spyce, +# it's simpler and cleaner to just embed python statements. The cumbersome +# nature of using Java code directly inside JSP makes the JSTL make sense for +# J2EE users; there's no real need for the same in Spyce, but these are +# available for those who avoid Python in "view" code for religions reasons. + +# < print val=expression [encode=url|html] [default=expression] /> +# Outputs the value of the "val" expression. If there is an error +# and a "default" is provided, the default will be evaluated instead. The +# output may be encoded to be HTML- or URL-safe, depending on the +# "encode" attribute + +# < let var=expression val=expression /> [exports *var] +# Sets the variable "var" to the value "val". + +# < if test=expression>... +# Evaluate "test" and conditionally process body of tag. + +# < for items=expression [var=expr (item)] [counter=expr]>... +# [exports *var, *counter] +# Iterate through "items" and process the body each time. The current +# item can optionally be stored in variable named by "var", and the +# current iteration number (starting at zero) can optionally be stored +# in a variable named by "counter". + +class tag_print(spyceTagPlus): + name = 'print' + def syntax(self): + self.syntaxSingleOnly() + self.syntaxNonEmpty('val') + self.syntaxValidSet('encode', ['false', 'url', 'html']) + def begin(self, val, encode='html', default=None): + try: out = str(val) + except: + if default: + out = str(default) + else: + raise + if encode == 'html': + out = self.getModule('transform').html_encode(out) + elif encode == 'url': + out = self.getModule('transform').url_encode(out) + self.getOut().write(out) + +class tag_let(spyceTagPlus): + name = 'let' + exports = 1 + def syntax(self): + self.syntaxSingleOnly() + self.syntaxNonEmpty('var') + def begin(self, var, val): + self.var = var + self.val = val + def export(self): + return {self.var: self.val} + +class tag_if(spyceTagPlus): + name = 'if' + conditional = 1 + def syntax(self): + self.syntaxPairOnly() + def begin(self, test, var=None): + return test + +# rimtodo: add first, last boolean flag variables +class tag_for(spyceTagPlus): + name = 'for' + conditional = 1 + loops = 1 + exports = 1 + def syntax(self): + self.syntaxPairOnly() + self.syntaxNonEmpty('items', 'var', 'counter') + def body(self, _contents): + return self._iter() + def begin(self, items, var='item', counter=None): + self.remaining = items + try: self.remaining = list(self.remaining) + except TypeError: + raise 'items expression should result in sequence, %s is not iterable'%repr(self.remaining) + self.var = var + self.counter = counter + self.i = 0 + return self._iter() + def export(self): + d = {} + if self.var: + try: + d[self.var] = self.element + except: pass + if self.counter: + d[self.counter] = self.i + return d + + def _iter(self): + # get next + if not self.remaining: return 0 + self.element, self.remaining = self.remaining[0], self.remaining[1:] + self.i = self.i + 1 + return 1 + + +# rimtodo: catch +# rimtodo: choose, when, otherwise + +class flow(spyceTagLibrary): + tags = [ + tag_print, + tag_let, + tag_if, + tag_for, + ] diff --git a/spyce-2.1/contrib/xitami/lrwplib.py b/spyce-2.1/contrib/xitami/lrwplib.py new file mode 100755 index 0000000000000000000000000000000000000000..0c36b1214640b94df0034eb14483d1b10296a388 --- /dev/null +++ b/spyce-2.1/contrib/xitami/lrwplib.py @@ -0,0 +1,273 @@ +#!python +#------------------------------------------------------------------------ +# Copyright (c) 1997 by Total Control Software +# All Rights Reserved +#------------------------------------------------------------------------ +# +# Module Name: lrwplib.py +# +# Description: Class LRWP handles the connection to the LRWP agent in +# Xitami. This class can be used standalone or derived +# from to override behavior. +# +# Creation Date: 11/11/97 8:36:21PM +# +# License: This is free software. You may use this software for any +# purpose including modification/redistribution, so long as +# this header remains intact and that you do not claim any +# rights of ownership or authorship of this software. This +# software has been tested, but no warranty is expressed or +# implied. +# +#------------------------------------------------------------------------ + +import sys, socket, string +import os, cgi +from cStringIO import StringIO + + +__version__ = '1.0' + +LENGTHSIZE = 9 +LENGTHFMT = '%09d' + +#--------------------------------------------------------------------------- +# Exception objects + +ConnectError = 'lrwp.ConnectError' +ConnectionClosed = 'lrwp.ConnectionClosed' +SocketError = 'lrwp.SocketError' + +#--------------------------------------------------------------------------- + +class Request: + ''' + Encapsulates the request/response IO objects and CGI-Environment. + An instance of this class is returned + ''' + def __init__(self, lrwp): + self.inp = lrwp.inp + self.out = lrwp.out + self.err = lrwp.out + self.env = lrwp.env + self.lrwp = lrwp + + def finish(self): + self.lrwp.finish() + + def getFieldStorage(self): + method = 'POST' + if self.env.has_key('REQUEST_METHOD'): + method = string.upper(self.env['REQUEST_METHOD']) + if method == 'GET': + return cgi.FieldStorage(environ=self.env, keep_blank_values=1) + else: + return cgi.FieldStorage(fp=self.inp, environ=self.env, keep_blank_values=1) + + +#--------------------------------------------------------------------------- + +class LRWP: + def __init__(self, name, host, port, vhost='', filter='', useStdio=0): + ''' + Construct an LRWP object. + name The name or alias of this request handler. Requests + matching http://host/name will be directed to this + LRWP object. + host Hostname or IP address to connect to. + port Port number to connect on. + vhost If this handler is to only be available to a specific + virtual host, name it here. + filter A space separated list of file extenstions that should + be directed to this handler in filter mode. (Not yet + supported.) + ''' + self.name = name + self.host = host + self.port = port + self.vhost = vhost + self.filter = filter + self.useStdio = useStdio + self.sock = None + self.env = None + self.inp = None + self.out = None + + #---------------------------------------- + def connect(self): + ''' + Establishes the connection to the web server, using the parameters given + at construction. + ''' + try: + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((self.host, self.port)) # Changed to two item tuple with Python 2.0 + self.sock.send("%s\xFF%s\xFF%s" % (self.name, self.vhost, self.filter) ) + buf = self.sock.recv(1024) + if buf != 'OK': + raise ConnectError, buf + except socket.error, val: + raise SocketError, val + + #---------------------------------------- + def acceptRequest(self): + ''' + Wait for, and accept a new request from the Web Server. Reads the + name=value pairs that comprise the CGI environment, followed by the + post data, if any. Constructs and returns a Request object. + ''' + if self.out: + self.finish() + try: + # get the length of the environment data + data = self.recvBlock(LENGTHSIZE) + if not data: # server closed down + raise ConnectionClosed + length = string.atoi(data) + + # and then the environment data + data = self.recvBlock(length) + if not data: # server closed down + raise ConnectionClosed + data = string.split(data, '\000') + self.env = {} + for x in data: + x = string.split(x, '=') + if len(x) > 1: + self.env[x[0]] = string.join(x[1:], '=') + + # now get the size of the POST data + data = self.recvBlock(LENGTHSIZE) + if not data: # server closed down + raise ConnectionClosed + length = string.atoi(data) + + # and the POST data... + if length: + data = self.recvBlock(length) + if not data: # server closed down + raise ConnectionClosed + self.inp = StringIO(data) + else: + self.inp = StringIO() + + self.out = StringIO() + + # do the switcheroo on the sys IO objects, etc. + if self.useStdio: + self.saveStdio = sys.stdin, sys.stdout, sys.stderr, os.environ + sys.stdin, sys.stdout, sys.stderr, os.environ = \ + self.inp, self.out, self.out, self.env + + return Request(self) + + except socket.error, val: + raise SocketError, val + + + #---------------------------------------- + def recvBlock(self, size): + ''' + Pull an exact number of bytes from the socket, taking into + account the possibility of multiple packets... + ''' + numRead = 0 + data = [] + while numRead < size: + buf = self.sock.recv(size - numRead); + if not buf: + return '' + data.append(buf) + numRead = numRead + len(buf) + + return string.join(data, '') + + #---------------------------------------- + def finish(self): + ''' + Complete the request and send the output back to the webserver. + ''' + doc = self.out.getvalue() + size = LENGTHFMT % (len(doc), ) + try: + self.sock.send(size) + self.sock.send(doc) + except socket.error, val: + raise SocketError, val + + if self.useStdio: + sys.stdin, sys.stdout, sys.stderr, os.environ = self.saveStdio + + self.env = None + self.inp = None + self.out = None + + #---------------------------------------- + def close(self): + ''' + Close the LRWP connection to the web server. + ''' + self.sock.close() + self.sock = None + self.env = None + self.inp = None + self.out = None + +#--------------------------------------------------------------------------- + + +def _test(): + import os, time + + eol = '\r\n' + appname = 'testapp1' + vhost = '' + host = 'localhost' + port = 5081 + if len(sys.argv) > 1: + appname = sys.argv[1] + if len(sys.argv) > 2: + host = sys.argv[2] + if len(sys.argv) > 3: + port = string.atoi(sys.argv[3]) + if len(sys.argv) > 4: + vhost = sys.argv[4] + + lrwp = LRWP(appname, host, port, vhost) + lrwp.connect() + + count = 0 + while count < 5: # exit after servicing 5 requests + req = lrwp.acceptRequest() + + doc = ['LRWP TestApp ('+appname+')\n\n'] + count = count + 1 + doc.append('

LRWP test app ('+appname+')

') + doc.append('request count = %d
' % (count, )) + if hasattr(os, 'getpid'): + doc.append('pid = %s
' % (os.getpid(), )) + doc.append('
post data: ' + req.inp.read() + '
') + + doc.append('


')
+        keys = req.env.keys()
+        keys.sort()
+        for k in keys:
+            doc.append('%-20s :  %s\n' % (k, req.env[k]))
+        doc.append('\n


\n') + doc.append('\n') + + + req.out.write('Content-type: text/html' + eol) + req.out.write(eol) + req.out.write(string.join(doc, '')) + + req.finish() + + lrwp.close() + + +if __name__ == '__main__': + #import pdb + #pdb.run('_test()') + _test() + diff --git a/spyce-2.1/contrib/xitami/spyceLRWP.py b/spyce-2.1/contrib/xitami/spyceLRWP.py new file mode 100755 index 0000000000000000000000000000000000000000..0369fa32e6b2f700d86251c707692987769ef780 --- /dev/null +++ b/spyce-2.1/contrib/xitami/spyceLRWP.py @@ -0,0 +1,213 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: +################################################## + +# spyceLRWP.py by Jim Madsen +# Adapted from the work of Rimon Barr & Robin Dunn +# Some further modifications by: +# Rimon Barr +################################################## +''' +Instructions: + +Edit the 'Startup Parameters' section below for your configuration. +Make sure Xitami is already running. +Run spyceLRWP.py - status will display in console window. +(You can rename spyceLRWP.py to the name of your app) + +The URL for your app will be: http://server/appname/filename +Example: http://localhost/spyceLRWP/form.spy +Base directory is the default you set for Xitami pages. + +To shutdown the connection cleanly, enter Ctrl-C...shutdown will +occur on next request handled. (Send one yourself if necessary) + +If you are unfamiliar with LRWP, the Xitami Docs have a good section on it. + +Multiple instances of spyceLRWP can be run. Just start spyceLRWP.py multiple +times. Xitami will pool the instances and use them if server load warrants it. +''' +################################################## + +# Startup Parameters + +LRWPappName = 'spyceLRWP' +LRWPhost = 'localhost' +LRWPport = 5081 + +################################################## +import os, sys, string +import spyce, spyceUtil +from lrwplib import LRWP + +__doc__ = '''Xitami LRWP-based Spyce entry point.''' + +################################################## + +# Request / Response handlers + +class spyceLRWPRequest(spyce.spyceRequest): + + def __init__(self, input, env, filename): + spyce.spyceRequest.__init__(self) + self._in = input + self._env = env + if not self._env.has_key('QUERY_STRING'): + self._env['QUERY_STRING']='' + self._headers = { + 'Content-Length': self.env('CONTENT_LENGTH'), + 'Content-Type': self.env('CONTENT_TYPE'), + 'User-Agent': self.env('HTTP_USER_AGENT'), + 'Accept': self.env('HTTP_ACCEPT'), + 'Accept-Encoding': self.env('HTTP_ACCEPT_ENCODING'), + 'Accept-Language': self.env('HTTP_ACCEPT_LANGUAGE'), + 'Accept-Charset': self.env('HTTP_ACCEPT_CHARSET'), + 'Cookie': self.env('HTTP_COOKIE'), + 'Referer': self.env('HTTP_REFERER'), + 'Host': self.env('HTTP_HOST'), + 'Connection': self.env('HTTP_CONNECTION'), + 'Keep-Alive': self.env('HTTP_KEEP_ALIVE'), + } + def env(self, name=None): + return spyceUtil.extractValue(self._env, name) + def getHeader(self, type=None): + return spyceUtil.extractValue(self._headers, type) + def getServerID(self): + return os.getpid() + +class spyceLRWPResponse(spyce.spyceResponse): + + def __init__(self, out, err): + spyce.spyceResponse.__init__(self) + self.origout = out + self.out = spyceUtil.BufferedOutput(out) + self.err = err + self.headers = [] + self.headersSent = 0 + self.CT = None + self.returncode = self.RETURN_OK + def write(self, s): + self.out.write(s) + def writeErr(self, s): + self.err.write(s) + def close(self): + self.flush() + self.out.close() + def clear(self): + self.out.clear() + def sendHeaders(self): + if not self.headersSent: + resultText = spyceUtil.extractValue(self.RETURN_CODE, self.returncode) + self.origout.write('Status: %3d "%s"\n' % (self.returncode, resultText)) + if not self.CT: + self.setContentType('text/html') + self.origout.write('Content-Type: %s\n' % self.CT) + for h in self.headers: + self.origout.write('%s: %s\n'%h) + self.origout.write('\n') + self.headersSent = 1 + def clearHeaders(self): + if self.headersSent: + raise 'headers already sent' + self.headers = [] + def setContentType(self, content_type): + if self.headersSent: + raise 'headers already sent' + self.CT = content_type + def setReturnCode(self, code): + if self.headersSent: + raise 'headers already sent' + self.returncode = code + def addHeader(self, type, data, replace=0): + if self.headersSent: + raise 'headers already sent' + if type=='Content-Type': + self.setContentType(data) + else: + if replace: + self.headers = filter(lambda (type, _), t2=type: type!=t2, self.headers) + self.headers.append((type, data)) + def flush(self, stopFlag=0): + if stopFlag: return + self.sendHeaders() + self.out.flush() + def unbuffer(self): + self.sendHeaders() + self.out.unbuffer() + +def spyceMain(cgiscript, inp, out, err, env): + + dir = os.path.dirname(cgiscript) + script = os.path.basename(cgiscript) + os.chdir(dir) + + output = out + request = spyceLRWPRequest(inp, env, script) + response = spyceLRWPResponse(output, err) + result = spyce.spyceFileHandler(request, response, script) + response.close() + + if output: + output.close() + +def doSpyce(inp, out, err, env): + path = env['PATH_TRANSLATED'] + if os.path.isfile(path): + print 'Processing - ' + path + result = spyceMain(path, inp, out, err, env) + else: + result = 'pageError' + print 'Invalid Page Requested' + return result + +def main(): + try: + lrwp = LRWP(LRWPappName, LRWPhost, LRWPport) + lrwp.connect() + print ('\r\n Connected to Xitami -- Listening for ' + LRWPappName + '\r\n') + + except: + sys.exit('\r\n Could not make proper connection to Xitami') + + while 1: + try: + req = lrwp.acceptRequest() + + # Fix environment variables due to the way Xitami reports them under LRWP + req.env['SCRIPT_NAME'] = ('/' + LRWPappName) + req.env['REQUEST_URI'] = ('/' + LRWPappName + req.env['PATH_INFO']) + + result = doSpyce(req.inp, spyceUtil.NoCloseOut(req.out), req.out, req.env) + + # Error page to keep LRWP from hanging on bad urls + if result == 'pageError': + req.out.write('Content-type: text/html\r\n\r\n') + req.out.write('Page Error') + req.out.write('\n') + req.out.write('') + req.out.write('

404 Error

') + req.out.write('

') + req.out.write('

Page Not Available On This Server

') + req.out.write('\n\n') + + req.finish() + + # Capture Ctrl-C...shutdown will occur on next request handled + except KeyboardInterrupt: + print '\r\n Closing connection to Xitami \r\n' + lrwp.close() + sys.exit(' Clean Exit') + + except: + print 'Unknown Error handling requests' + + +if __name__=='__main__': + if sys.platform == "win32": + import os, msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + main() diff --git a/spyce-2.1/debugspyce.py b/spyce-2.1/debugspyce.py new file mode 100755 index 0000000000000000000000000000000000000000..a318a826c11966fe01c057163f522b193b9d6a2e --- /dev/null +++ b/spyce-2.1/debugspyce.py @@ -0,0 +1,35 @@ +# quick-and-dirty spyce debugging harness + +import spyceconf +import sys +import spyce + +import taglib as t + +class response_stub: + def writeStatic(self, s): + print s + def writeExpr(self, s): + print s + +response = response_stub() + +class wrapper_stub: + def spyceTaglib(self, name, file=None, rel_file=None): + "Return Spyce taglib class" + return spyce.getServer(spyceconf).loadModule(name, file, rel_file) + def registerModuleCallback(self, callback): + pass + def getFilename(self): + return 'debugstub' + def getModules(self): + return {'response': response, + 'stdout': sys.stdout} + +taglib = t.taglib(wrapper_stub()) +taglib.start() + +# paste compiled Spyce here + +test = spyceImpl() +test.spyceProcess() diff --git a/spyce-2.1/fcgi.py b/spyce-2.1/fcgi.py new file mode 100755 index 0000000000000000000000000000000000000000..7aae8954c120b69c3f45ebe177ee576d7b12c4e2 --- /dev/null +++ b/spyce-2.1/fcgi.py @@ -0,0 +1,268 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: fcgi.py 915 2006-07-20 00:00:51Z ellisj $ +################################################## + +# Taken originally from: http://alldunn.com/python/fcgi.py +# Edited a fair bit. -- RB + +__doc__ = 'Python Fast CGI implementation' + +import os, sys, string, socket, errno, cgi +from cStringIO import StringIO +import spyceUtil + +################################################## +# Constants +# + +# Protol constants: record types +FCGI_BEGIN_REQUEST = 1 +FCGI_ABORT_REQUEST = 2 +FCGI_END_REQUEST = 3 +FCGI_PARAMS = 4 +FCGI_STDIN = 5 +FCGI_STDOUT = 6 +FCGI_STDERR = 7 +FCGI_DATA = 8 +FCGI_GET_VALUES = 9 +FCGI_GET_VALUES_RESULT = 10 +FCGI_UNKNOWN_TYPE = 11 +FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE +# Protocol constants: FCGI_BEGIN_REQUEST flag mask +FCGI_KEEP_CONN = 1 +# Protocol constants: FCGI_BEGIN_REQUEST role +FCGI_RESPONDER = 1 +FCGI_AUTHORIZER = 2 +FCGI_FILTER = 3 +# Protocol constants: FCGI_END_REQUEST protocolStatus +FCGI_REQUEST_COMPLETE = 0 # ok +FCGI_CANT_MPX_CONN = 1 # can not multiplex +FCGI_OVERLOADED = 2 # too busy +FCGI_UNKNOWN_ROLE = 3 # role unknown +# Protocol constants: management record types +FCGI_NULL_REQUEST_ID = 0 + +# Protocol setting: maximum number of requests +FCGI_MAX_REQS = 1 +FCGI_MAX_CONNS = 1 +# Protocol setting: can multiplex? +FCGI_MPXS_CONNS = 0 +# Protocol setting: FastCGI protocol version +FCGI_VERSION_1 = 1 + +################################################## +# Protocol +# + +class record: + def __init__(self): + self.version = FCGI_VERSION_1 + self.recType = FCGI_UNKNOWN_TYPE + self.reqId = FCGI_NULL_REQUEST_ID + self.content = "" + def readRecord(self, sock): + # read content + hdr = map(ord, self.readExact(sock, 8)) + self.version = hdr[0] + self.recType = hdr[1] + self.reqId = (hdr[2]<<8)+hdr[3] + contentLength = (hdr[4]<<8)+hdr[5] + paddingLength = hdr[6] + self.content = self.readExact(sock, contentLength) + self.readExact(sock, paddingLength) + # parse + c = self.content + if self.recType == FCGI_BEGIN_REQUEST: + self.role = (ord(c[0])<<8) + ord(c[1]) + self.flags = ord(c[2]) + elif self.recType == FCGI_UNKNOWN_TYPE: + self.unknownType = ord(c[0]) + elif self.recType == FCGI_GET_VALUES or self.recType == FCGI_PARAMS: + self.values={} + pos=0 + while pos < len(c): + name, value, pos = self.decodePair(c, pos) + self.values[name] = value + elif self.recType == FCGI_END_REQUEST: + b = map(ord, c[0:5]) + self.appStatus = (b[0]<<24) + (b[1]<<16) + (b[2]<<8) + b[3] + self.protocolStatus = b[4] + def writeRecord(self, sock): + content = self.content + if self.recType == FCGI_BEGIN_REQUEST: + content = chr(self.role>>8) + chr(self.role & 255) + chr(self.flags) + 5*'\000' + elif self.recType == FCGI_UNKNOWN_TYPE: + content = chr(self.unknownType) + 7*'\000' + elif self.recType==FCGI_GET_VALUES or self.recType==FCGI_PARAMS: + content = "" + for i in self.values.keys(): + content = content + self.encodePair(i, self.values[i]) + elif self.recType==FCGI_END_REQUEST: + v = self.appStatus + content = chr((v>>24)&255) + chr((v>>16)&255) + chr((v>>8)&255) + chr(v&255) + content = content + chr(self.protocolStatus) + 3*'\000' + cLen = len(content) + eLen = (cLen + 7) & (0xFFFF - 7) # align to an 8-byte boundary + padLen = eLen - cLen + hdr = [ self.version, self.recType, self.reqId >> 8, + self.reqId & 255, cLen >> 8, cLen & 255, padLen, 0] + hdr = string.joinfields(map(chr, hdr), '') + sock.send(hdr + content + padLen*'\000') + def readExact(self, sock, amount): + data = '' + while amount and len(data) < amount: + data = data + sock.recv(amount-len(data)) + return data + def decodePair(self, s, pos): + nameLen=ord(s[pos]) ; pos=pos+1 + if nameLen & 128: + b=map(ord, s[pos:pos+3]) ; pos=pos+3 + nameLen=((nameLen&127)<<24) + (b[0]<<16) + (b[1]<<8) + b[2] + valueLen=ord(s[pos]) ; pos=pos+1 + if valueLen & 128: + b=map(ord, s[pos:pos+3]) ; pos=pos+3 + valueLen=((valueLen&127)<<24) + (b[0]<<16) + (b[1]<<8) + b[2] + name = s[pos:pos+nameLen] ; pos = pos + nameLen + value = s[pos:pos+valueLen] ; pos = pos + valueLen + return name, value, pos + def encodePair(self, name, value): + l=len(name) + if l<128: s=chr(l) + else: s=chr(128|(l>>24)&255)+chr((l>>16)&255)+chr((l>>8)&255)+chr(l&255) + l=len(value) + if l<128: s=s+chr(l) + else: s=s+chr(128|(l>>24)&255)+chr((l>>16)&255)+chr((l>>8)&255)+chr(l&255) + return s + name + value + +class FCGI: + def __init__(self, port=None): + # environment variables + try: + self.FCGI_PORT = int(os.environ['FCGI_PORT']) + except: + self.FCGI_PORT = None + if port: self.FCGI_PORT = port + self.FCGI_PORT = None + try: + self.FCGI_ALLOWED_ADDR = os.environ['FCGI_WEB_SERVER_ADDRS'] + self.FCGI_ALLOWED_ADDR = map(string.strip, string.split(self.FCGI_ALLOWED_ADDR, ',')) + except: + self.FCGI_ALLOWED_ADDR = None + self.firstCall = 1 + self.clearState() + self.socket = None + self.createServerSocket() + def createServerSocket(self): + if self.FCGI_PORT: + s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.set_reuse_addr() + s.bind(('127.0.0.1', self.FCGI_PORT)) + else: + try: + s=socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM) + s.getpeername() + except socket.error, (err, errmsg): + if err!=errno.ENOTCONN: + return + except: + return + self.socket = s + def accept(self): + if not self.socket: # plain CGI + if self.firstCall: + result = sys.stdin, spyceUtil.NoCloseOut(sys.stdout), sys.stderr, os.environ + else: + return 0 + else: # FCGI + result = self.recv() + self.firstCall = 0 + return result + def finish(self): + if self.firstCall or not self.socket: return + self.send() + def clearState(self): + self.reqID = 0 + self.connection = None + self.environ = {} + self.stdin = StringIO() + self.stderr = StringIO() + self.stdout = StringIO() + self.data = StringIO() + def send(self): + self.stderr.seek(0,0) + self.stdout.seek(0,0) + self.sendStream(FCGI_STDERR, self.stderr.read()) + self.sendStream(FCGI_STDOUT, self.stdout.read()) + r=record() + r.recType=FCGI_END_REQUEST + r.reqId=self.reqID + r.appStatus=0 + r.protocolStatus=FCGI_REQUEST_COMPLETE + r.writeRecord(self.connection) + self.connection.close() + self.clearState() + def sendStream(self, streamType, streamData): + if not streamData: + return + r=record() + r.recType = streamType + r.reqId = self.reqID + data = streamData + while data: + r.content, data = data[:8192], data[8192:] + r.writeRecord(self.connection) + r.content='' ; r.writeRecord(self.connection) + def recv(self): + self.connection, address = self.socket.accept() + # rimtodo: filter to serve only allowed addresses + # if good_addrs!=None and addr not in good_addrs: + # raise 'Connection from invalid server!' + remaining=1 + while remaining: + r=record(); r.readRecord(self.connection) + if r.recType in [FCGI_GET_VALUES]: # management records + if r.recType == FCGI_GET_VALUES: + r.recType = FCGI_GET_VALUES_RESULT + v={} + vars={'FCGI_MAX_CONNS' : FCGI_MAX_CONNS, + 'FCGI_MAX_REQS' : FCGI_MAX_REQS, + 'FCGI_MPXS_CONNS': FCGI_MPXS_CONNS} + for i in r.values.keys(): + if vars.has_key(i): v[i]=vars[i] + r.values=vars + r.writeRecord(self.connection) + elif r.reqId == 0: # management record of unknown type + r2 = record() + r2.recType = FCGI_UNKNOWN_TYPE ; r2.unknownType = r.recType + r2.writeRecord(self.connection) + continue + elif r.reqId != self.reqID and r.recType != FCGI_BEGIN_REQUEST: + continue # ignore inactive requests + elif r.recType == FCGI_BEGIN_REQUEST and self.reqID != 0: + continue # ignore BEGIN_REQUESTs in the middle of request + if r.recType == FCGI_BEGIN_REQUEST: # begin request + self.reqID = r.reqId + if r.role == FCGI_AUTHORIZER: remaining=1 + elif r.role == FCGI_RESPONDER: remaining=2 + elif r.role == FCGI_FILTER: remaining=3 + elif r.recType == FCGI_PARAMS: # environment + if r.content == '': remaining=remaining-1 + else: + for i in r.values.keys(): + self.environ[i] = r.values[i] + elif r.recType == FCGI_STDIN: # stdin + if r.content == '': remaining=remaining-1 + else: self.stdin.write(r.content) + elif r.recType == FCGI_DATA: # data + if r.content == '': remaining=remaining-1 + else: self.data.write(r.content) + # end while + self.stdin.seek(0,0) + self.data.seek(0,0) + # return CGI environment + return self.stdin, spyceUtil.NoCloseOut(self.stdout), self.stderr, self.environ + diff --git a/spyce-2.1/installHelper.py b/spyce-2.1/installHelper.py new file mode 100755 index 0000000000000000000000000000000000000000..adaac5c499b434a18807d0d9b760730065c69357 --- /dev/null +++ b/spyce-2.1/installHelper.py @@ -0,0 +1,181 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: installHelper.py 312 2002-12-02 00:56:49Z batripler $ +################################################## + +__doc__ = 'Spyce install helper script' + +import os, imp, sys, getopt, string, re, time + +CONF_BEGIN_MARK = '### BEGIN SPYCE CONFIG MARKER' +CONF_END_MARK = '### END SPYCE CONFIG MARKER' +HTTPD_LOCATIONS = [ + '/etc/httpd/conf', + r'C:\Program Files\Apache Group\Apache2\conf', + r'C:\Program Files\Apache Group\Apache\conf', + '/etc', + '/'] +APACHE_EXE_LOCATIONS = [ + r'C:\Program Files\Apache Group\Apache2\bin', + r'C:\Program Files\Apache Group\Apache2', + r'C:\Program Files\Apache Group\Apache\bin', + r'C:\Program Files\Apache Group\Apache', +] +SYS_LOCATIONS = [ + r'C:\winnt\system32', +] + +def endsWith(s, suffix): + suffixLen = len(suffix) + return s[-suffixLen:] == suffix + +def listDirFilter(dir, extension): + files = os.listdir(dir) + files = filter(lambda file: endsWith(file, extension), files) + return files + +def compilePythonDir(dir): + print '** Compiling Python files in: %s' % dir + for file in listDirFilter(dir, '.py'): + print 'Compiling: %s' % file + try: + p = os.path.join(dir, file) + f = None + try: + f = open(p, 'r') + imp.load_source(os.path.split(file)[1][:-3], p, f) + finally: + if f: f.close() + except: pass + +def compileSpyceDir(dir): + import spyceCmd + print '** Processing Spyce files in: %s' % dir + for file in listDirFilter(dir, '.spy'): + print 'Processing: %s' % file + sys.argv = ['', '-o', os.path.join(dir, file[:-4]+'.html'), os.path.join(dir, file)] + spyceCmd.spyceMain() + +def findLine(array, line): + line = string.strip(line) + for i in range(len(array)): + if re.search(line, string.strip(array[i])): + return i + return None + +def unconfig(s): + lines = string.split(s, '\n') + begin = findLine(lines, CONF_BEGIN_MARK) + end = findLine(lines, CONF_END_MARK) + if begin!=None and end!=None and end>begin: + del lines[begin:end+1] + s = string.join(lines, '\n') + return s + +def config(s, root): + append = readFile('spyceApache.conf') + root = re.sub(r'\\', '/', root) + append = string.replace(append, 'XXX', root) + append = string.split(append, '\n') + if os.name=='nt': + row = findLine(append, 'ScriptInterpreterSource') + append[row] = string.strip(re.sub('#', '', append[row])) + lines = [s] + [CONF_BEGIN_MARK] + append + [CONF_END_MARK] + s = string.join(lines, '\n') + return s + +def readFile(filename): + f = None + try: + f = open(filename, 'r') + return f.read() + finally: + if f: f.close() + +def writeFileBackup(filename, new): + old = readFile(filename) + backupname = filename + '.save' + f = None + try: + f = open(backupname, 'w') + f.write(old) + finally: + if f: f.close() + f = None + try: + f = open(filename, 'w') + f.write(new) + finally: + if f: f.close() + +def locateFile(file, locations): + def visit(arg, dirname, names, file=file): + path = os.path.join(dirname, file) + if os.path.exists(path): + arg.append(path) + if arg: + del names[:] + found = [] + for path in locations: + os.path.walk(path, visit, found) + if found: + return found[0] + +def configHTTPD(spyceroot): + print '** Searching for httpd.conf...' + file = locateFile('httpd.conf', HTTPD_LOCATIONS) + if file: + print '** Modifying httpd.conf' + s = readFile(file) + s = unconfig(s) + s = config(s, spyceroot) + writeFileBackup(file, s) + +def unconfigHTTPD(): + print '** Searching for httpd.conf...' + file = locateFile('httpd.conf', HTTPD_LOCATIONS) + if file: + print '** Modifying httpd.conf' + s = readFile(file) + s = unconfig(s) + writeFileBackup(file, s) + +def restartApache(): + print '** Searching for apache.exe...' + file = locateFile('apache.exe', APACHE_EXE_LOCATIONS) + cmd = locateFile('cmd.exe', SYS_LOCATIONS) + if file and cmd: + print '** Restarting Apache' + os.spawnl(os.P_WAIT, cmd, '/c "%s" -k restart'%file) + return + print 'Could not find apache.exe' + + + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], '', + ['py=', 'spy=', 'apache=', 'apacheUN', + 'apacheRestart']); + except getopt.error: + print "Syntax error" + return -1 + for o, a in opts: + if o == "--py": + compilePythonDir(a); return 0 + if o == "--spy": + compileSpyceDir(a); return 0 + if o == "--apache": + configHTTPD(a); return 0 + if o == "--apacheUN": + unconfigHTTPD(); return 0 + if o == "--apacheRestart": + restartApache(); return 0 + print "Syntax error" + return -1 + +if __name__=='__main__': + sys.exit(main()) diff --git a/spyce-2.1/misc/addfirstline.sh b/spyce-2.1/misc/addfirstline.sh new file mode 100755 index 0000000000000000000000000000000000000000..6300b7f88371898fac0b6efb58abea35583cd29a --- /dev/null +++ b/spyce-2.1/misc/addfirstline.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +file=$1 +line=$2 + +cat >> addfirstline.tmp << EOF +$line +`cat $file` +EOF + +mv addfirstline.tmp $file diff --git a/spyce-2.1/misc/benchmark/hello.c b/spyce-2.1/misc/benchmark/hello.c new file mode 100755 index 0000000000000000000000000000000000000000..ad6e080609417f66249a2c694e306fe189943875 --- /dev/null +++ b/spyce-2.1/misc/benchmark/hello.c @@ -0,0 +1,24 @@ +#include + +int main() +{ + int i; + printf("Date: Sun, 26 May 2002 18:22:24 GMT\n"); + printf("Server: Apache/1.3.22 (Unix) (Red-Hat/Linux) mod_python/2.7.6 Python/2.2\n"); + printf("Last-Modified: Sun, 26 May 2002 18:19:49 GMT\n"); + printf("ETag: \"84244-47-3cf12745\"\n"); + printf("Accept-Ranges: bytes\n"); + printf("Content-Length: 71\n"); + printf("Connection: close\n"); + printf("Content-Type: text/html\n"); + printf("\n"); + printf("\n"); + printf("Hello world!\n"); + for(i=0; i<10; i++) + { + printf(" %d ", i); + } + printf("\n"); + return 0; +} + diff --git a/spyce-2.1/misc/benchmark/hello.html b/spyce-2.1/misc/benchmark/hello.html new file mode 100755 index 0000000000000000000000000000000000000000..bc1d51e082656abccbf6e4afb47432331ab2d17e --- /dev/null +++ b/spyce-2.1/misc/benchmark/hello.html @@ -0,0 +1,4 @@ + +Hello world! +0 1 2 3 4 5 6 7 8 9 + diff --git a/spyce-2.1/misc/benchmark/hello.jsp b/spyce-2.1/misc/benchmark/hello.jsp new file mode 100755 index 0000000000000000000000000000000000000000..6dec3bb360e84309d03ce171097d0225a7d50f8a --- /dev/null +++ b/spyce-2.1/misc/benchmark/hello.jsp @@ -0,0 +1,6 @@ + +Hello world! +<% for(int i=0; i<10; i++) { %> + <%=i%> +<% } %> + diff --git a/spyce-2.1/misc/benchmark/hello.php b/spyce-2.1/misc/benchmark/hello.php new file mode 100755 index 0000000000000000000000000000000000000000..9fade22bc91bacae9d8384ac7a7042f2dd4a0b19 --- /dev/null +++ b/spyce-2.1/misc/benchmark/hello.php @@ -0,0 +1,6 @@ + + Hello world! + + + + diff --git a/spyce-2.1/misc/benchmark/hello.py b/spyce-2.1/misc/benchmark/hello.py new file mode 100755 index 0000000000000000000000000000000000000000..f5a926e6569945875d704c6d73e6fe007807b1e7 --- /dev/null +++ b/spyce-2.1/misc/benchmark/hello.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +print "Content-Type: text/html" +print +print "" +print "Hello world! " +for i in range(10): + print i, +print "" diff --git a/spyce-2.1/misc/benchmark/hellopy.cgi b/spyce-2.1/misc/benchmark/hellopy.cgi new file mode 100755 index 0000000000000000000000000000000000000000..9ec01089b5b2fec7adac1be7a5f7060c6a72bc3c --- /dev/null +++ b/spyce-2.1/misc/benchmark/hellopy.cgi @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +print "Date: Sun, 26 May 2002 18:22:24 GMT" +print "Server: Apache/1.3.22 (Unix) (Red-Hat/Linux) mod_python/2.7.6 Python/2.2" +print "Last-Modified: Sun, 26 May 2002 18:19:49 GMT" +print "ETag: \"84244-47-3cf12745\"" +print "Accept-Ranges: bytes" +print "Content-Length: 71" +print "Connection: close" +print "Content-Type: text/html" +print "" +print "" +print "Hello world! " +for i in range(10): + print i, +print "" + diff --git a/spyce-2.1/misc/one-check.bmp b/spyce-2.1/misc/one-check.bmp new file mode 100755 index 0000000000000000000000000000000000000000..02e09b2739dd667ae4293ef78adaeff85f9ed39a Binary files /dev/null and b/spyce-2.1/misc/one-check.bmp differ diff --git a/spyce-2.1/misc/one-nocheck.bmp b/spyce-2.1/misc/one-nocheck.bmp new file mode 100755 index 0000000000000000000000000000000000000000..bf8bed12efc5ecbaaf3b1ef926012f849be7b56c Binary files /dev/null and b/spyce-2.1/misc/one-nocheck.bmp differ diff --git a/spyce-2.1/misc/pics/pilpel1-edit.jpg b/spyce-2.1/misc/pics/pilpel1-edit.jpg new file mode 100755 index 0000000000000000000000000000000000000000..9c124425d893296e3f98491d05f35bb971ad2ab3 Binary files /dev/null and b/spyce-2.1/misc/pics/pilpel1-edit.jpg differ diff --git a/spyce-2.1/misc/pics/pilpel2.jpg b/spyce-2.1/misc/pics/pilpel2.jpg new file mode 100755 index 0000000000000000000000000000000000000000..dc5b3b7583285e90947b2f68f6c4e259e8516909 Binary files /dev/null and b/spyce-2.1/misc/pics/pilpel2.jpg differ diff --git a/spyce-2.1/misc/pics/pypow.gif b/spyce-2.1/misc/pics/pypow.gif new file mode 100755 index 0000000000000000000000000000000000000000..eb3ccf53b15d4e93e35b9c4f2a09dff563b31d35 Binary files /dev/null and b/spyce-2.1/misc/pics/pypow.gif differ diff --git a/spyce-2.1/misc/pics/spyce-border.ico b/spyce-2.1/misc/pics/spyce-border.ico new file mode 100755 index 0000000000000000000000000000000000000000..4cca56510dd26072254e5c2690f3fd47457f770a Binary files /dev/null and b/spyce-2.1/misc/pics/spyce-border.ico differ diff --git a/spyce-2.1/misc/pics/spyce.ico b/spyce-2.1/misc/pics/spyce.ico new file mode 100755 index 0000000000000000000000000000000000000000..d5b76b1036a0e2c815b5bbe8c34967a4a7d4b6a3 Binary files /dev/null and b/spyce-2.1/misc/pics/spyce.ico differ diff --git a/spyce-2.1/misc/pics/spyce1.bmp b/spyce-2.1/misc/pics/spyce1.bmp new file mode 100755 index 0000000000000000000000000000000000000000..97d582a9f02b07b114011fae4998cb69eeac575c Binary files /dev/null and b/spyce-2.1/misc/pics/spyce1.bmp differ diff --git a/spyce-2.1/misc/pics/spyce2.bmp b/spyce-2.1/misc/pics/spyce2.bmp new file mode 100755 index 0000000000000000000000000000000000000000..8477865dfc28c8e9c1be1c9e0505d5dc1ba5af68 Binary files /dev/null and b/spyce-2.1/misc/pics/spyce2.bmp differ diff --git a/spyce-2.1/misc/pics/spyce3.bmp b/spyce-2.1/misc/pics/spyce3.bmp new file mode 100755 index 0000000000000000000000000000000000000000..3a8385994e1b9133e6453632224552feb6cc5795 Binary files /dev/null and b/spyce-2.1/misc/pics/spyce3.bmp differ diff --git a/spyce-2.1/misc/pics/spyce3.gif b/spyce-2.1/misc/pics/spyce3.gif new file mode 100755 index 0000000000000000000000000000000000000000..926617fd800398dd2eecedc53ed904220ff6d9a2 Binary files /dev/null and b/spyce-2.1/misc/pics/spyce3.gif differ diff --git a/spyce-2.1/misc/pics/spyce3.jpg b/spyce-2.1/misc/pics/spyce3.jpg new file mode 100755 index 0000000000000000000000000000000000000000..a48e5fe777f1bafca6551e315f81401c1eb6f878 Binary files /dev/null and b/spyce-2.1/misc/pics/spyce3.jpg differ diff --git a/spyce-2.1/misc/runsfcgi.py b/spyce-2.1/misc/runsfcgi.py new file mode 100755 index 0000000000000000000000000000000000000000..1c33477b732436950e2669e70ce101b042b6b71a --- /dev/null +++ b/spyce-2.1/misc/runsfcgi.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +# must be placed in the main spyce group directory, i.e. +# /home/groups/s/sp/spyce +# from there it sets up the environment for CGI scripts; this is +# so that we don't have +# to reconfigure the vanilla spyce distro every time we "make sf" + +import sys, os.path + +script = '/home/groups/s/sp/spyce/spyce/spyceCGI.py' +confpath = '/home/groups/s/sp/spyce/spyceconf-sf.py' + +if __name__ == '__main__': + sys.path.append(os.path.dirname(script)) + for arg in ['--conf', confpath]: + sys.argv.insert(-1, arg) + execfile(script) diff --git a/spyce-2.1/misc/spyceconf-sf.py b/spyce-2.1/misc/spyceconf-sf.py new file mode 100755 index 0000000000000000000000000000000000000000..024d6edc442b4d1e0565a3f31dbd857bb3fbfda8 --- /dev/null +++ b/spyce-2.1/misc/spyceconf-sf.py @@ -0,0 +1,11 @@ +from spyceconf import * + +sys.path.append('/home/groups/s/sp/spyce/modules') + +tmp = '/tmp/persistent/spyce' + +session_store = session.DbmStore(tmp) + +login_storage = FileStorage(tmp) + +check_modules_and_restart = False diff --git a/spyce-2.1/modules/compress.py b/spyce-2.1/modules/compress.py new file mode 100755 index 0000000000000000000000000000000000000000..c0e2d43c7cf879ae963d408af4283f1382500158 --- /dev/null +++ b/spyce-2.1/modules/compress.py @@ -0,0 +1,84 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: compress.py 1042 2006-08-05 16:16:20Z ellisj $ +################################################## + +from spyceModule import spyceModule +from cStringIO import StringIO +import gzip, string, spyceUtil + +__doc__ = '''Compress module provides dynamic compression.''' + +OUTPUT_POSITION = 95 + +class compress(spyceModule): + def start(self): + # install compress filter into response module + self._filter = FilterCompress(self) + self._api.getModule('response').addFilter(OUTPUT_POSITION, self._filter) + def finish(self, theError=None): + if not theError: + self._filter.close() + def init(self, gzip=None, spaces=None): + if gzip: self.gzip() + if spaces: self.spaces() + def spaces(self, on=1): + self._filter.setSpaceMode(on) + def gzip(self, level=None): + self._filter.setGZIP(level) + +class FilterCompress(object): + def __init__(self, module): + self._module = module + self._buf = StringIO() + self._flushed = 0 + self._space = 0 + self._gzip = None + self._gzipfile = None + def writeStatic(self, s): + self.write(s) + def writeExpr(self, s, **kwargs): + self.write(str(s)) + def setSpaceMode(self, on): + self._space = on + def setGZIP(self, level): + if self._flushed: + raise 'output already flushed' + encodings = self._module._api.getModule('request').getHeader('Accept-Encoding') + if not encodings or string.find(encodings, 'gzip')<0: + return # ensure the browser can cope + if level==0: + self._gzip = None + self._gzipfile = None + else: + self._gzipfile = StringIO() + if level: + self._gzip = gzip.GzipFile(mode='w', fileobj=self._gzipfile, compresslevel=level) + else: + self._gzip = gzip.GzipFile(mode='w', fileobj=self._gzipfile) + def write(self, s, *args, **kwargs): + self._buf.write(s) + def flushImpl(self, final=0): + self._flushed = 1 + s = self._buf.getvalue() + self._buf = StringIO() + if self._space: + s = spyceUtil.spaceCompact(s) + if self._gzip: + self._gzip.write(s) + self._gzip.flush() + if final: + self._module._api.getModule('response').addHeader('Content-Encoding', 'gzip') + self._gzip.close() + self._gzip = None + s = self._gzipfile.getvalue() + self._gzipfile.truncate(0) + self.next.write(s) + def clearImpl(self): + self._buf = StringIO() + def close(self): + self.flushImpl(1) + diff --git a/spyce-2.1/modules/cookie.py b/spyce-2.1/modules/cookie.py new file mode 100755 index 0000000000000000000000000000000000000000..50500476784bd62ec048133836a9fca4312d92e9 --- /dev/null +++ b/spyce-2.1/modules/cookie.py @@ -0,0 +1,54 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: cookie.py 959 2006-07-28 21:01:10Z ellisj $ +################################################## + +from spyceModule import spyceModule +import Cookie, time, calendar, urllib +from UserDict import DictMixin + +__doc__ = """Cookie module gives users full control over browser cookie +functionality. """ + +class cookie(spyceModule, DictMixin): + def start(self): + self._cookie = {} + cookie = Cookie.SimpleCookie(self._api.getModule('request').env('HTTP_COOKIE')) + for c in cookie.keys(): + self._cookie[c] = urllib.unquote(cookie[c].value) + def get(self, key=None): + "Get brower cookie(s)" + if key: + if key in self._cookie: + return self._cookie[key] + return None + else: return self._cookie + def set(self, key, value, expire=None, domain=None, path=None, secure=0): + "Set browser cookie" + if value==None: # delete (set to expire one week ago) + return self.set(key, '', -60*60*24*7, domain, path, secure) + text = '%s=%s' % (key, urllib.quote(value)) + if expire != None: text = text + ';EXPIRES=%s' % time.strftime( + '%a, %d-%b-%y %H:%M:%S GMT', + time.gmtime(time.time()+expire)) + if domain: text = text + ';DOMAIN=%s' % domain + if path: text = text + ';PATH=%s' % path + if secure: text = text + ';SECURE' + self._api.getModule('response').addHeader('Set-Cookie', text) + def delete(self, key, domain=None, path=None, secure=0): + "Delete browser cookie" + self.set(key, None, domain=domain, path=path, secure=secure) + + # for DictMixin + def keys(self): + return self._cookie.keys() + def __getitem__(self, key): + return self.get(key) + def __contains__(self, key): + return key in self._cookie + def __delitem__(self, key): + return self.delete(self, key) + diff --git a/spyce-2.1/modules/cookie.pyc b/spyce-2.1/modules/cookie.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa9e792812bf6a495c18a74b52ff75763962ecd6 Binary files /dev/null and b/spyce-2.1/modules/cookie.pyc differ diff --git a/spyce-2.1/modules/error.py b/spyce-2.1/modules/error.py new file mode 100755 index 0000000000000000000000000000000000000000..577bda6a5ac3ce9db9f678026229c288a119b6db --- /dev/null +++ b/spyce-2.1/modules/error.py @@ -0,0 +1,175 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: error.py 578 2005-04-19 04:09:33Z jbe $ +################################################## + +from spyceModule import spyceModule +import spyceException, spyceUtil +import os + +__doc__ = '''Error module provides error-handling functionality.''' + +class error(spyceModule): + def start(self): + self._error = None + pageerrorType, pageerrorData = self._api.getPageError() + self.handler = lambda self, pageerrorType=pageerrorType, pageerrorData=pageerrorData: spyceHandler(self, pageerrorData, pageerrorType) + def finish(self, theError=None): + self._error = theError + self._fromFile = self._api.getFilename() + if theError: + self.handler(self) + def setHandler(self, fn): + if not type(fn)==type(spyceHandler): + raise 'parameter should be a function' + self.handler = fn + def setStringHandler(self, s): + if not type(s)==type(''): + raise 'parameter should be a string of spyce code' + self.handler = lambda self, s=s: spyceHandler(self, s, 'string') + def setFileHandler(self, f): + if not type(f)==type(''): + raise 'parameter should be a filename' + self.handler = lambda self, f=f: spyceHandler(self, f) + def getHandler(self): + return self.handler + def isError(self): + return not not self._error + def getMessage(self): + if self._error: return self._error.msg + def getType(self): + if self._error: return self._error.type + def getFile(self): + if self._error: return self._fromFile + def getTraceback(self): + if self._error: return self._error.traceback + def getString(self): + if self._error: return self._error.str + def __repr__(self): + if not self._error: return 'None' + return 'type: %s, string: %s, msg: %s, file: %s' % ( + self.getType(), self.getString(), self.getMessage(), self.getFile()) + +def spyceHandler(errorModule, spyceCode, type='file'): + try: + responseModule = errorModule._api.getModule('response') + responseModule.clear() + responseModule.clearHeaders() + responseModule.clearFilters() + responseModule.setContentType('text/html') + responseModule.setReturnCode(errorModule._api.getResponse().RETURN_OK) + except: pass + try: + s, file = None, None + if type=='file': + file = spyceUtil.url2file(spyceCode, errorModule._api.getFilename()) + code = errorModule._api.spyceFile(file) + elif type=='string': + file = '' + code = errorModule._api.spyceString(spyceCode) + else: + raise 'unrecognized handler type' + try: + s = code.newWrapper() + modules = errorModule._api.getModules() + for name in modules.keys(): + s.setModule(name, modules[name]) # include modules as well! + s.spyceInit(errorModule._api.getRequest(), errorModule._api.getResponse()) + errmod = s._startModule('error', None, None, 1) + errmod._error = errorModule._error + errmod._fromFile = errorModule._fromFile + s.spyceProcess() + finally: + if s: + s.spyceDestroy() + code.returnWrapper(s) + except spyceException.spyceRuntimeException, e: + errorModule._error = e + errorModule._fromFile = file + if (type, spyceCode) == ('string', defaultErrorTemplate): + raise # avoid infinite loop + else: + spyceHandler(errorModule, defaultErrorTemplate, 'string') + +defaultErrorTemplate = r''' +[[.module name=transform]] +[[transform.expr('html_encode')]] + +Spyce exception: [[=error.getMessage()]] + + + + + + + +

Spyce exception

File:[[=error.getFile()]]
Message:
[[=error.getMessage()]]
Stack: + [[\ + L = list(error.getTraceback()) + L.reverse() + ]] + [[ for frame in L: { ]] + [[=frame[0] ]]:[[=frame[1] ]], in [[=frame[2] ]]:
+
+
[[=frame[3] ]]
+
+ [[ } ]] +
+ +''' + +def serverHandler(theServer, theRequest, theResponse, theError): + try: + theResponse.clear() + theResponse.clearHeaders() + theResponse.setContentType('text/html') + theResponse.setReturnCode(theResponse.RETURN_OK) + except: pass + s = None + try: + spycecode = theServer.spyce_cache[('string', serverErrorTemplate)] + s = spycecode.newWrapper() + s.spyceInit(theRequest, theResponse) + s.getModule('error')._error = theError + s.spyceProcess() + finally: + if s: + s.spyceDestroy() + spycecode.returnWrapper(s) + + +serverErrorTemplate = ''' +[[.module name=transform]] +[[import string, spyceException + if isinstance(error._error, spyceException.spyceNotFound): { ]] + + [[=error._error.file]] not found + [[response.setReturnCode(response._api.getResponse().RETURN_NOT_FOUND)]] + +[[ } elif isinstance(error._error, spyceException.spyceForbidden): { ]] + + [[=error._error.file]] forbidden + [[response.setReturnCode(response._api.getResponse().RETURN_FORBIDDEN)]] + +[[ } elif isinstance(error._error, spyceException.spyceSyntaxError): { ]] +
+  [[=transform.html_encode(`error._error`)]]
+  
+[[ } elif isinstance(error._error, spyceException.pythonSyntaxError): { ]] +
+  [[=transform.html_encode(`error._error`)]]
+  
+[[ } elif isinstance(error._error, SyntaxError): { ]] +
+  Syntax error at [[=error._error.filename]]:[[=error._error.lineno]] - 
+    [[=transform.html_encode(error._error.text)]]    [[
+      if not error._error.offset==None: {
+        print ' '*error._error.offset+'^'
+      }
+    ]]
+  
+[[ } else: { raise error._error } ]] +''' diff --git a/spyce-2.1/modules/error.pyc b/spyce-2.1/modules/error.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a051434179c73bb657aa8ce3f11c34e5a005f959 Binary files /dev/null and b/spyce-2.1/modules/error.pyc differ diff --git a/spyce-2.1/modules/include.py b/spyce-2.1/modules/include.py new file mode 100755 index 0000000000000000000000000000000000000000..a644b29ff339db949fe2d2f44d60b76dfea6598b --- /dev/null +++ b/spyce-2.1/modules/include.py @@ -0,0 +1,136 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +################################################## + +from spyceModule import spyceModule +import re +import spyce, spyceUtil + +__doc__ = """Include module is used to assist the inclusion of abitrary +elements/files into a Spyce file. It can also support the notion of an +'inclusion context'.""" + +class include(spyceModule): + def start(self): + self.context = None + self.vars = None + def spyce(self, url, context=None): + "Include a Spyce file" + spyce.DEBUG('including ' + url) + if len(self._api.getStack()) >= spyce.MAX_STACK: + raise 'Maximum stack depth exceeded! (infinite include loop?)' + filename = spyceUtil.url2file(url, self._api.getFilename()) + result = s = code = None + try: + code = self._api.spyceFile(filename) + s = code.newWrapper() + modules = self._api.getModules() + for name in modules.keys(): + s.setModule(name, modules[name]) # include module as well! + s.spyceInit(self._api.getRequest(), self._api.getResponse()) + incmod = s._startModule('include', None, None, 1) + incmod.context = context + if type(context)==type({}): + incmod.vars = spyceVars(context) + self._api.getStack().append(filename) + result = s.spyceProcess() + self._api.getStack().pop() + finally: + if s: + s.spyceDestroy() + code.returnWrapper(s) + return result + def spyceStr(self, url, context=None): + stdout = self._api.getModule('stdout') + stdout.push() + try: + result = self.spyce(url, context) + finally: + output = stdout.pop() + return output + def dump(self, file, binary=0): + "Include a plain text file, verbatim" + file = os.path.join(os.path.dirname(self._api.getFilename()), file) + f = None + try: + if binary: mode='rb' + else: mode='r' + f = open(file, mode) + buf = f.read() + finally: + if f: f.close() + return buf + def spycecode(self, url=None, string=None, html=None, code=None, eval=None, directive=None, comment=None, tag=None): + "Emit formatted Spyce code" + if not html: html = ('', '') + if not code: code = ('', '') + if not eval: eval = ('', '') + if not directive: directive = ('', '') + if not comment: comment = ('', '') + if not tag: tag = ('', '') + import spyceCompile + from StringIO import StringIO + if (url and string) or (not url and not string): + raise 'must specify either url or string, and not both' + if url: + f = None + try: + filename = spyceUtil.url2file(url, self._api.getFilename()) + f = open(filename, 'r') + string = f.read() + finally: + if f: f.close() + html_encode = self._api.getModule('transform').html_encode + try: + tokens = spyceCompile.spyceTokenize(string) + buf = StringIO() + markupstack = [] + buf.write(html[0]); markupstack.append(html[1]) + for type, text, _, _ in tokens: + if type == spyceCompile.T_TEXT: + while True: + m = spyceCompile.RE_LIB_TAG.search(text) + if not m: + buf.write(html_encode(text)) + break + pre = text[:m.start()] + post = text[m.end():] + buf.write(html_encode(pre)) + buf.write(tag[0]) + buf.write(html_encode(m.group(0))) + buf.write(tag[1]) + text = post + continue + + if type in (spyceCompile.T_STMT, spyceCompile.T_CHUNK, spyceCompile.T_CHUNKC,): + buf.write(code[0]); markupstack.append(code[1]) + elif type in (spyceCompile.T_LAMBDA,): + buf.write(html[0]); markupstack.append(html[1]) + elif type in (spyceCompile.T_EVAL,): + buf.write(eval[0]); markupstack.append(eval[1]) + elif type in (spyceCompile.T_DIRECT,): + buf.write(directive[0]); markupstack.append(directive[1]) + elif type in (spyceCompile.T_CMNT,): + buf.write(comment[0]); markupstack.append(comment[1]) + buf.write(html_encode(text)) + if type in (spyceCompile.T_END_CMNT, spyceCompile.T_END,): + buf.write(markupstack.pop()) + while markupstack: + buf.write(markupstack.pop()) + return buf.getvalue() + except: + raise + raise 'error tokenizing!' + +class spyceVars: + def __init__(self, vars): + self.__dict__['vars'] = vars + def __getattr__(self, name): + try: return self.__dict__['vars'][name] + except KeyError: raise AttributeError + def __setattr__(self, name, value): + self.__dict__['vars'][name] = value + diff --git a/spyce-2.1/modules/include.pyc b/spyce-2.1/modules/include.pyc new file mode 100644 index 0000000000000000000000000000000000000000..112ae977e589ad14243cbd46584663e3cff2d86d Binary files /dev/null and b/spyce-2.1/modules/include.pyc differ diff --git a/spyce-2.1/modules/pool.py b/spyce-2.1/modules/pool.py new file mode 100755 index 0000000000000000000000000000000000000000..b129f3161de44bea36b9e78eda8276da04878578 --- /dev/null +++ b/spyce-2.1/modules/pool.py @@ -0,0 +1,92 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: pool.py 799 2005-07-22 02:19:19Z jbe $ +################################################## + +import sys, time, os.path +import cPickle as pickle +import spyce, spyceLock, spyceUtil +from spyceModule import spyceModule + +__doc__ = """Pool module supports the creation of server-pooled objects. The +single pool is shared among all Spyce execution context residing on a given +server, and remains until the server dies. It is often useful to store +database connections, and other variables that are expensive to compute on a +per-request basis. """ + +# todo: should pickle/unpickle go somewhere else, such as request? +# this would make it more intuitive for people using SPYCE_GLOBALS +# but not pool module +def picklefile(): + return os.path.join(spyce.getServer().config.tmp, 'spypool.pickle') + +class pool(spyceModule): + def start(self): + "Define or retrieve the pool." + if spyce.getServer().threaded(): + self._pool = self._api.getServerGlobals() + return + try: + f = open(picklefile(), 'rb') + except IOError: + self._pool = {} + return + def foo(): + spyceLock.file_lock(f, spyceLock.LOCK_SH | spyceLock.LOCK_NB) + return True + if not spyceUtil.tryForAwhile(foo): + raise "timeout opening pool pickle file for read" + try: + try: + self._pool = pickle.load(f) + except EOFError, pickle.UnpicklingError: + spyce.DEBUG("Warning: unable to complete loading pool from pickle file. A common cause is adding instances of custom classes to the pool. Consider using Spyce in standalone mode to avoid pickle-related limitations.") + self._pool = {} + finally: + spyceLock.file_unlock(f) + f.close() + def finish(self, err): + if spyce.getServer().threaded(): + return + f = open(picklefile(), 'wb') + def foo(): + spyceLock.file_lock(f, spyceLock.LOCK_EX | spyceLock.LOCK_NB) + return True + if not spyceUtil.tryForAwhile(foo): + raise "timeout opening pool pickle file for write" + try: + pickle.dump(self._pool, f, -1) + finally: + spyceLock.file_unlock(f) + f.close() + def __getitem__(self, key): + "Get an item from the pool." + return self._pool[key] + def __setitem__(self, key, value): + "Set an item in the pool." + self._pool[key] = value + def __delitem__(self, key): + "Delete an item in the pool." + del self._pool[key] + def keys(self): + "Return the pool hash keys." + return self._pool.keys() + def values(self): + "Return the pool hash values." + return self._pool.values() + def has_key(self, key): + "Test of existence of key in pool." + return self._pool.has_key(key) + def __contains__(self, key): + return key in self._pool + def setdefault(self, *args, **kwargs): + return self._pool.setdefault(*args, **kwargs) + def items(self): + return self._pool.items() + def clear(self): + "Purge the pool of all items." + return self._pool.clear() + diff --git a/spyce-2.1/modules/pool.pyc b/spyce-2.1/modules/pool.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f18b9446446e63fe72713da6c314d65c1f2fb221 Binary files /dev/null and b/spyce-2.1/modules/pool.pyc differ diff --git a/spyce-2.1/modules/redirect.py b/spyce-2.1/modules/redirect.py new file mode 100755 index 0000000000000000000000000000000000000000..6f6443f9449ab351d693489f990bab62170771ed --- /dev/null +++ b/spyce-2.1/modules/redirect.py @@ -0,0 +1,58 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: redirect.py 640 2005-04-25 04:30:37Z jbe $ +################################################## + +from spyceModule import spyceModule +import spyceException, spyceUtil, spyce + +__doc__ = '''Redirect module provides support for different kinds of request +redirection, currently: internal, external and externalRefresh. +- internal: flush the current output bufffer (assuming it has not been sent) + and raise an appropriate exception that will start the processing of the + new file, if left to percolate all the way to the Spyce engine. The + browser url does not change. +- external: send an HTTP return code that signals a permanent or temporary + page move, depending on the boolean parameter. Spyce file execution will + continue to termination, but the output buffer is flushed at the end and a + special redirect document is generated. The browser is expected, as per the + standard, to immediately redirect and perform a new request, thus the url + will change. +- externalRefresh: send an HTTP Refresh header that requests a page refresh to + a (possibly) new location within some number of seconds. The current Spyce + page will be displayed until that time. This is often used to display a page + before redirecting the browser to a file download. +''' + +class redirect(spyceModule): + def start(self): + self.clear = 0 + def finish(self, theError=None): + if not theError: + if self.clear: + self._api.getModule('response').clear() + def internal(self, url=None, filename=None): + "Perform an internal redirect." + if (url and filename) or not (url or filename): + raise 'Specify exactly one of {url, filename}' + spyce.DEBUG('internal redirect to %s at the behest of %s' % (url, self._api.getModule('request').stack()[-1])) + self._api.getModule('response').clearHeaders() + self._api.getModule('response').clear() + if url: + filename = spyceUtil.url2file(url, self._api.getFilename()) + raise spyceException.spyceRedirect(filename) + def external(self, url, permanent=0): + "Perform an external redirect." + spyce.DEBUG('external redirect to %s at the behest of %s' % (url, self._api.getModule('request').stack()[-1])) + self._api.getModule('response').addHeader('Location', url) + if permanent: + self._api.getModule('response').setReturnCode(self._api.getResponse().RETURN_MOVED_PERMANENTLY) + else: + self._api.getModule('response').setReturnCode(self._api.getResponse().RETURN_MOVED_TEMPORARILY) + self.clear = 1 + def externalRefresh(self, url, sec=0): + "Perform an external redirect, via refresh." + self._api.getModule('response').addHeader('Refresh', '%d; URL=%s' % (sec, url)) diff --git a/spyce-2.1/modules/redirect.pyc b/spyce-2.1/modules/redirect.pyc new file mode 100755 index 0000000000000000000000000000000000000000..84de6bf8d6e6da3471b2f498b15decf7300bca04 Binary files /dev/null and b/spyce-2.1/modules/redirect.pyc differ diff --git a/spyce-2.1/modules/request.py b/spyce-2.1/modules/request.py new file mode 100755 index 0000000000000000000000000000000000000000..f4b83c1382817e1352f3678bdaf4812bec55274a --- /dev/null +++ b/spyce-2.1/modules/request.py @@ -0,0 +1,239 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: request.py 1021 2006-08-04 01:10:53Z ellisj $ +################################################## + +from spyceModule import spyceModule +import spyce, cgi, string, urlparse, spyceUtil, sys + +__doc__ = """Request module provides access to the browser request +information. """ + +def lowerize(d): + """ + returns a dict containing the list values of d, mapped to lower-case keys. + d may contain keys that differ only in case. + """ + lowered = {} + for key in d: + keyL = string.lower(key) + lowered[keyL] = lowered.get(keyL, []) + d[key] + return lowered + +def extractValue(hash, key, default, single): + """ + Extract key's value from dictionary [of lists], if it exists. + If single, return value[0]. + If key is none, return entire dictionary, or dictionary of first values if single. + Else if key is not found, return default. + """ + if key is None: + if single: + d = {} + for key in hash: + d[key] = hash[key][0] + return d + return hash + if key in hash: + if single: + return hash[key][0] + return hash[key] + if default == []: default = [] # workaround default-only-evaluated-once "feature" + return default + +def filterDict(request, d, filters): + for f in filters: + f(request, d) + +class request(spyceModule): + def start(self): + "Initialise module variables" + self._initted = False + if spyce.DEBUG_ERROR: + sys.stderr.write('GET variables: %s\n' % self.get()) + sys.stderr.write('POST variables: %s\n' % self.post()) + def uri(self, component=None): + "Return request URI, or URI component" + theuri = self._api.getRequest().env()['REQUEST_URI'] + if not component: + return theuri + else: + component = string.lower(component) + if component == 'scheme': component = 0 + elif component == 'location': component = 1 + elif component == 'path': component = 2 + elif component == 'parameters': component = 3 + elif component == 'query': component = 4 + elif component == 'fragment': component = 5 + else: raise 'unknown uri component' + return urlparse.urlparse(theuri)[component] + def uri_scheme(self): + "Return request URI scheme, ie. http (usually)" + return urlparse.urlparse(self.uri())[0] + def uri_location(self): + "Return request URI scheme, ie. http (usually)" + return urlparse.urlparse(self.uri())[1] + def uri_path(self): + "Return request URI path component" + return urlparse.urlparse(self.uri())[2] + def method(self): + "Return request method: get/post/..." + return string.upper(self._api.getRequest().env()['REQUEST_METHOD']) + def query(self): + "Return request query string" + return self._api.getRequest().env()['QUERY_STRING'] + def stack(self, i=None): + s = self._api.getStack() + if i is not None: s = s[i] + return s + def filename(self, relative=None): + "Return original Spyce filename" + myfile = self._api.getFilename() + if relative is None: + return myfile + else: + return os.path.realpath(os.path.join(os.path.dirname(myfile), relative)) + def default(self, value, value2): + "Return value, or value2 if value is None" + if value is None: return value2 + return value + def _getInit(self): + self._get = cgi.parse_qs(self.query(), 1) + def get(self, name=None, default=[], ignoreCase=0, single=False): + "Return GET parameter(s) list(s)" + self._init() + if ignoreCase: + if name: name = string.lower(name) + return extractValue(self._getL, name, default, single) + return extractValue(self._get, name, default, single) + def get1(self, name=None, default=None, ignoreCase=0): + "Return single GET parameter(s)" + return self.get(name, default, ignoreCase, True) + def _postInit(self): + if hasattr(self._api.getRequest(), 'spycepostinfo'): + # stream was already parsed (possibly this is an internal redirect) + (self._post, self._file) = self._api.getRequest().spycepostinfo + return + self._post = {} + self._file = {} + try: len = int(str(self.env('CONTENT_LENGTH'))) + except: len=0 + if self.method()=='POST' and len: + postfields = cgi.FieldStorage(fp=self._api.getRequest(), environ=self.env(), keep_blank_values=1) + for key in postfields.keys(): + if type(postfields[key]) == type( [] ): + self._post[key] = map(lambda attr: attr.value, postfields[key]) + elif not postfields[key].filename: + self._post[key] = [postfields[key].value] + else: + self._file[key] = postfields[key] + # save parsed information in request object to prevent reparsing (on redirection) + self._api.getRequest().spycepostinfo = (self._post, self._file) + def _init(self): + if self._initted: + return + # get raw parameters before filtering + # this allows sophisticated filters to take action based on the "big picture" + # not just individual key/values + self._getInit() + self._postInit() + filterDict(self, self._get, spyce.getServer().config.param_filters) + filterDict(self, self._post, spyce.getServer().config.param_filters) + filterDict(self, self._file, spyce.getServer().config.file_filters) + # generate lowercase dicts after filtering + self._getL = lowerize(self._get) + self._postL = lowerize(self._post) + self._fileL = {} + for key in self._file: + self._fileL[string.lower(key)] = self._file[key] + self._initted = True + + def post(self, name=None, default=[], ignoreCase=False, single=False): + "Return POST parameter(s) list(s)" + self._postInit() + if ignoreCase: + if name: name = string.lower(name) + return extractValue(self._postL, name, default, single) + return extractValue(self._post, name, default, single) + def post1(self, name=None, default=None, ignoreCase=0): + "Return single POST parameter(s)" + return self.post(name, default, ignoreCase, True) + def file(self, name=None, ignoreCase=0): + "Return POSTed file(s)" + self._init() + if ignoreCase: + if name: name = string.lower(name) + return spyceUtil.extractValue(self._fileL, name) + else: + return spyceUtil.extractValue(self._file, name) + def env(self, name=None, default=None): + "Return other request (CGI) environment variables" + return self.default(self._api.getRequest().env(name), default) + def getHeader(self, type=None): + "Return browser HTTP header(s)" + return self._api.getRequest().getHeader(type) + # __setitem__ deliberately left out -- + # if you really truly want to present a modified view of request parameters + # to yourself, use add[File|Param]Filter. + def __getitem__(self, key): + if type(key) == type(0): + spyce.DEBUG('returning raw for %s' % key) + return self.getpost().keys()[key] + else: + v = self.get1(key) + if v is not None: return v + v = self.post1(key) + if v is not None: return v + v = self.file(key) + spyce.DEBUG('getitem for %s is %s' % (key, v)) + if v is not None: return v + def __repr__(self): + return '' + def _multidict(self, *args): + args = list(args) + args.reverse() + dict = {} + for d in args: + for k in d.keys(): + dict[k] = d[k] + return dict + def getpost(self, name=None, default=None, ignoreCase=0): + "Return get() if not None, otherwise post() if not None, otherwise default" + if name is None: + self._init() + return self._multidict(self._get, self._post) + else: + value = self.get(name, None, ignoreCase) + if value is None: value = self.post(name, default, ignoreCase) + return value + def getpost1(self, name=None, default=None, ignoreCase=0): + "Return get1() if not None, otherwise post1() if not None, otherwise default" + if name is None: + self._init() + return self._multidict(self.get1(), self.post1()) + else: + value = self.get1(name, None, ignoreCase) + if value is None: value = self.post1(name, default, ignoreCase) + return value + def postget(self, name=None, default=None, ignoreCase=0): + "Return post() if not None, otherwise get() if not None, otherwise default" + if name is None: + self._init() + return self._multidict(self._post, self._get) + else: + value = self.post(name, None, ignoreCase) + if value is None: value = self.get(name, default, ignoreCase) + return value + def postget1(self, name=None, default=None, ignoreCase=0): + "Return post1() if not None, otherwise get1() if not None, otherwise default" + if name is None: + self._init() + return self._multidict(self.post1(), self.get1()) + else: + value = self.post1(name, None, ignoreCase) + if value is None: value = self.get1(name, default, ignoreCase) + return value + diff --git a/spyce-2.1/modules/request.pyc b/spyce-2.1/modules/request.pyc new file mode 100644 index 0000000000000000000000000000000000000000..169dffbbd1cd86d1868839d27a4331a6c4d531c0 Binary files /dev/null and b/spyce-2.1/modules/request.pyc differ diff --git a/spyce-2.1/modules/response.py b/spyce-2.1/modules/response.py new file mode 100755 index 0000000000000000000000000000000000000000..54d5f504fed74a824d2a3a3ffd7a1a3a25e3cccd --- /dev/null +++ b/spyce-2.1/modules/response.py @@ -0,0 +1,205 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: response.py 826 2006-03-10 00:49:04Z jbe $ +################################################## + +from spyceModule import spyceModule +import string, time + +__doc__ = '''Response module provides user control over the browser +response.''' + +class response(spyceModule): + def start(self): + self.clearFilters() + self._unbuffer = 0 + self._ioerror = 0 + self._api.registerResponseCallback(self.syncResponse) + self.syncResponse() + def syncResponse(self): + self._response = self._api.getResponse() + def finish(self, theError=None): + self._api.unregisterResponseCallback(self.syncResponse) + if not theError: + self._filter.flush(1) + def clearFilters(self): + self._filter = FilterUnify(self) + self._filterList = [(99, self._filter)] + def addFilter(self, level, filter): + 'Inject filter functions into output stream at given level of precedence' + filterExists = None + for i in range(len(self._filterList)): + l, _ = self._filterList[i] + if l==level: + _, filterExists = self._filterList[i] + del self._filterList[i] + break + if filter: + self._filterList.append((level, filter)) + self._filterList.sort() + for i in range(len(self._filterList)-1): + l1, f1 = self._filterList[i] + l2, f2 = self._filterList[i+1] + f1.setNext(f2) + _, self._filter = self._filterList[0] + return filterExists + + # user functions + def end(self): + from spyceException import spyceDone + raise spyceDone() + def write(self, s): + "Write out a dynamic (code) string." + try: + self._filter.write(s) + if self._unbuffer: self.flush() + except IOError: + self._ioerror = 1 + def writeln(self, s): + "Writeln out a dynamic (code) string." + self.write(s+'\n') + def writeStatic(self, s): + "Write out a static string." + try: + self._filter.writeStatic(s) + if self._unbuffer: self.flush() + except IOError: + self._ioerror = 1 + def writeExpr(self, s, **kwargs): + "Write out an expression result." + try: + apply(self._filter.writeExpr, (s,), kwargs) + if self._unbuffer: self.flush() + except IOError: + self._ioerror = 1 + def clear(self): + "Clear the output buffer. (must not be unbuffered)" + self._filter.clear() + def flush(self, stopFlag=0): + "Flush resident buffer." + try: + self._filter.flush(stopFlag) + except IOError: + self._ioerror = 1 + def setContentType(self, ct): + "Set document content type. (must not be unbuffered)" + self._response.setContentType(ct) + def setReturnCode(self, code): + "Set HTTP return (status) code" + self._response.setReturnCode(int(code)) + def isCancelled(self): + return self._ioerror + def addHeader(self, type, data, replace=0): + "Add an HTTP header. (must not be unbuffered)" + if string.find(type, ':') != -1: + raise 'HTTP header type should not contain ":" (colon).' + self._response.addHeader(type, data, replace) + def clearHeaders(self): + "Clear all HTTP headers (must not be unbuffered)" + self._response.clearHeaders() + def unbuffer(self): + "Turn off output stream buffering; flush immediately to browser." + if self._api._hasParent(): + raise 'spyce code with parent template may not specify unbuffered output' + self._unbuffer = 1 + self.flush() + def timestamp(self, thetime=None): + "Timestamp response with a HTTP Date header" + self.addHeader('Date', _genTimestampString(thetime), 1) + def expires(self, thetime=None): + "Add HTTP expiration headers" + self.addHeader('Expires', _genTimestampString(thetime), 1) + def expiresRel(self, secs=0): + "Set response expiration (relative to now) with a HTTP Expires header" + self.expires(int(time.time())+secs) + def lastModified(self, thetime=-1): + "Set last modification time" + if thetime==-1: + filename = self._api.getFilename() + if not filename or not os.path.exists(filename): + raise 'request filename not found; can not determine last modification time' + thetime = os.stat(filename)[9] # file ctime + self.addHeader('Last-Modified', _genTimestampString(thetime), 1) + # ensure last modified before timestamp, at least when we're generating it + if thetime==None: self.timestamp() + def uncacheable(self): + "Ensure that compliant clients and proxies don't cache this response" + self.addHeader('Cache-Control', 'no-store, no-cache, must-revalidate') + self.addHeader('Pragma', 'no-cache') + def __repr__(self): + s = [] + s.append('filters: %s' % len(self._filterList)) + s.append('unbuffered: %s' % self._unbuffer) + return string.join(s, ', ') + +# from a chat with Rimon: +# Yeah, the filters are a pain in the ass. I tried to make them very +# general. I think I succeeded. :) you could practically fry eggs with them. +# +# It's actually not so complicated -- the complexity really only arises +# as a consequence of optimizations that are sorely needed for +# performance. Remember that this is the IO path, so you need to make +# have a short a stack as possible. That's why it has this strange +# structure of binding using a control channel, which basically copies +# function pointers around that form the data channel. Remember that +# the binding happens once; the write() happens hundreds of times. + +class Filter: + def setNext(self, filter): + self.next = filter + def write(self, s): + s = self.dynamicImpl(s) + self.next.write(s) + def writeStatic(self, s): + s = self.staticImpl(s) + self.next.writeStatic(s) + def writeExpr(self, s, **kwargs): + s = apply(self.exprImpl, (s,), kwargs) + apply(self.next.writeExpr, (s,), kwargs) + def flush(self, stopFlag=0): + self.flushImpl() + self.next.flush(stopFlag) + def clear(self): + self.clearImpl() + self.next.clear() + def dynamicImpl(self, s, *args, **kwargs): + raise 'not implemented' + def staticImpl(self, s, *args, **kwargs): + raise 'not implemented' + def exprImpl(self, s, *args, **kwargs): + raise 'not implemented' + def flushImpl(self): + raise 'not implemented' + def clearImpl(self): + raise 'not implemented' + +class FilterUnify(Filter): + def __init__(self, mod): + self.mod = mod + self.mod._api.registerResponseCallback(self.syncResponse) + self.syncResponse() + def syncResponse(self): + response = self.mod._api.getResponse() + self.write = response.write + self.writeStatic = response.write + self.flush = response.flush + self.clear = response.clear + def writeExpr(self, s, **kwargs): + if s is None: + s = '' + self.write(str(s)) + def setNext(self, filter): + pass # we are always at the end + +def _genTimestampString(thetime=None): + "Generate timestamp string" + if thetime==None: + thetime = int(time.time()) + if type(thetime)==type(0): + thetime = time.strftime('%a, %d %b %Y %H:%M:%S %Z', time.localtime(thetime)) + if type(thetime)==type(''): + return thetime + raise 'thetime value should be None or string or integer (seconds)' diff --git a/spyce-2.1/modules/response.pyc b/spyce-2.1/modules/response.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfd8a207f3a3bbf92581e9e77570eb69959692bc Binary files /dev/null and b/spyce-2.1/modules/response.pyc differ diff --git a/spyce-2.1/modules/session.py b/spyce-2.1/modules/session.py new file mode 100755 index 0000000000000000000000000000000000000000..36e1ed773f2cf4b3537cf20cf852c293c0483a88 --- /dev/null +++ b/spyce-2.1/modules/session.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# +# Copyright (c) 2006 Conan C. Albrecht, Jonathan Ellis +# +# 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. + +''' +Module Use +========== + +Use the session by calling the get(), put(), and delete() methods. The +following example stores, uses, and then deletes the username: + +In other words, it is very similar to a dictionary. You can pretend the session +module is just a dictionary specific to the user accessing the page. The session +"dictionary" contents will change automatically to match the user's values +for the browser accessing your page. + +IMPORTANT NOTE 1: +You should only put small, temporary objects into the session. While +the session will take anything that can be pickled, remember that +it must be stored in a dbm hash. Large objects will slow things down +considerably; avoid them. Store strings, ints, and simple objects. + +For example, if you want to store a User object, store the primary +key (user id) to the User object. On each request, reload the User +object from your database using the user id stored in the session. + +IMPORTANT NOTE 2: + +This module adds a _lastaccess key to each session. It uses this +value to know when the session needs to be cleaned out. Don't +use this key name. In other words, don't call +session['_lastaccess'] = anything. + +IMPORTANT NOTE 3: + +If you are running Spyce in mod_python, cgi, or fastcgi installation +modes, you *should not* use the memory option. Use the dbm method +for these installation modes. While dbm is slower and takes disk +space, an in-memory cache will cause update errors since you'll +get multiple caches! This is because mod_python, cgi, and fastcgi +create multiple instances of your application -- you'll have an +cache in *each* instance. + +In addition, dbm-method sessions survive a server reboot, memory-method +sessions do not. + + +The Internals +============= +Sessions are regular Python dictionaries. The standard python +"shelve" module is used to save these dictionaries to disk. +Since sessions are temporary and shouldn't be moved from installation +to installation, the module uses the highest possible pickle +protocol for speed. + +Whenever a session is saved to a file, the last checked +timestamp is used to see if the file should be cleaned. +If enough time has passed, all old sessions are deleted +from the shelf. + +The module cleans every CLEAN_INTERVAL seconds (set to every +24 hours right now). If you want a different CLEAN_INTERVAL, +just change this constant at the top of the file. I didn't make +this accessible publicly because I don't think most users +care how often it cleans sessions out. Note that files are +not actually ever cleaned until something is set within them. + +Sessions are never actually created until your program puts +data in them. In other words, they are created just in time. +Since many site visitors may never get session data set, this +provides a great efficiency since no disk access is required +for them. Your program will think they have a session, but +as long as you don't call put(), they won't get a session +internally. + +The module is thread-safe. A series of file locks is used +to lock shelves when they are being used or cleaned. However, +if multiple instances of Spyce are created, it is remotely +possible that sessions might blast each other. I'm not +sure how to solve this without additional disk access. If anyone +wants to help here please do. (note that the regular session +module has this problem, too.) +''' + +import types, sys, shelve, pickle, os, anydbm +from UserDict import DictMixin +from spyceModule import spyceModule, moduleFinder +import spyce, spyceLock + +######################################################## +### globals + +# how often to check for and clean old sessions +CLEAN_INTERVAL = 60 * 60 * 2 # clean every 2 hours + +######################################################## +### Session stores + +class SessionNotFoundError(Exception): pass + + +class SessionStore: + def __init__(self): + self._last_cleaned = time.time() + def load(self, sessionid, expires): + # when creating a new session object, its expiration should be set to "expires" + raise NotImplementedError() + def save(self, sessionid, session, expires): + raise NotImplementedError() + def clear(self, sessionid): + raise NotImplementedError() + def is_expired(self, sessionid): + raise NotImplementedError() + def list(self): + raise NotImplementedError() + + +memory_cache = {} +class MemoryStore(SessionStore): + def load(self, sessionid, expires): + # (python interpreter lock takes care of locking) + return memory_cache.get(sessionid, {'_expires': expires}) + def save(self, sessionid, session): + session['_lastaccess'] = time.time() + memory_cache[sessionid] = session + def is_expired(self, sessionid): + try: + s = memory_cache[sessionid] + except KeyError: + raise SessionNotFoundError() + return s['_lastaccess'] < time.time() - s['_expires'] + def clear(self, sessionid): + try: + del memory_cache[sessionid] + except KeyError: + raise SessionNotFoundError() + def list(self): + return memory_cache.keys() + + +class DbmStore(SessionStore): + def __init__(self, dir): + SessionStore.__init__(self) + self.dir = dir + def _init(self): + # can't perform these in __init__ b/c spyce's server object + # doesn't exist yet when __init__ is called from spyceconf + server = spyce.getServer() + gen = lambda i: server.createLock('sessionlock%d' % i) + self.lock = spyceLock.MultiLock(100, gen) + def _filename(self, sessionid): + return os.path.join(self.dir, 'spysession' + sessionid) + def load(self, sessionid, expires): + """ + (Conan's original session2 used a fixed, configurable, number of + shelve files for all session objects. This works poorly for highly + concurrent access, though: shelve files serialize write access. + So as aesthetically messy as it may be, one-file-per-session seems + to be the way to go.) + """ + if not hasattr(self, 'lock'): self._init() + fname = self._filename(sessionid) + self.lock.acquire(fname) + try: + session = shelve.open(fname, writeback=True, protocol=pickle.HIGHEST_PROTOCOL) + finally: + self.lock.release(fname) + if '_expires' not in session: + session['_expires'] = expires + return session + def save(self, sessionid, session): + if not hasattr(self, 'lock'): self._init() + fname = self._filename(sessionid) + self.lock.acquire(fname) + try: + session.close() + finally: + self.lock.release(fname) + def is_expired(self, sessionid): + self.lock.acquire(fname) + try: + try: + s = shelve.open(fname, flag='r') + except anydbm.error, e: + # "need 'c' or 'n' flag to open new db" + if 'new db' in e.args[0]: + raise SessionNotFoundError() + raise + finally: + self.lock.release(fname) + # (shelve updates mtime even when nothing changes) + return os.path.getmtime(self._filename(sessionid)) < time.time() - s['_expires'] + def clear(self, sessionid): + try: + os.unlink(self._filename(sessionid)) + except OSError, e: + if e.args[0] == 2: + # OSError: [Errno 2] No such file or directory: 'foo.bar' + raise SessionNotFoundError() + elif e.args[0] == 13: + # OSError: [Errno 13] Permission denied: 'foo.sh' + # usually means in-use by another thread/process + pass + else: + raise + def list(self): + import glob + n = len('spysession') + return [fname[n:] for fname in + glob.glob(os.path.join(self.dir, 'spysession*'))] + + +def clean_store(self): + store = server.config.session_store + for sessionid in store.list(): + try: + if store.is_expired(sessionid): + store.clear(sessionid) + except SessionNotFoundError: + pass + +######################################################## +### The session module + +class session(spyceModule, DictMixin): + '''Manages the sessions of the web site in memory or a dbm (shelve) file.''' + def start(self, *args, **kargs): + self.mf = moduleFinder(self._api) + server = spyce.getServer() + self.store = server.config.session_store + def get_option(name): + if kargs.has_key(name): + return kargs[name] + return getattr(server.config, 'session_' + name) + self.path = get_option('path') + self.expire = get_option('expire') + self.sessionid = self._find_sessionid() + self.session = self.store.load(self.sessionid, self.expire) + + def finish(self, err): + # refresh the cookie in the browser and restart expiration timer + self._api.getModule('cookie').set('sessionid', self.sessionid, expire=self.expire, path=self.path) + self.store.save(self.sessionid, self.session) + + ################################################### + ### Dictionary-like methods + + def __setitem__(self, key, value): + self.session[key] = value + def __delitem__(self, key): + del self.session[key] + def __getitem__(self, key): + return self.session[key] + def keys(self): + return self.session.keys() + def __contains__(self, key): + return key in self.session + def __iter__(self): + return iter(self.session) + def iteritems(self): + return iter(self.session.iteritems()) + + ##################################################### + ### Private methods + + def _find_sessionid(self): + return self._api.getModule('cookie').get('sessionid') or newtoken(self.mf) + + +############################################################################ + +seed = 'spycesessionseed' +import sha, time, random + +def newtoken(mf): + global seed + s = sha.sha(seed) + s.update(str(time.time())) + s.update(str(random.random())) + s.update(mf.request.getHeader('User-Agent') or '') + s.update(mf.request.filename()) + h = s.hexdigest() + seed = h[:10] + return h[10:] diff --git a/spyce-2.1/modules/session.pyc b/spyce-2.1/modules/session.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca9c2da5cfa7c47a9ae38bd49564571bce98b16e Binary files /dev/null and b/spyce-2.1/modules/session.pyc differ diff --git a/spyce-2.1/modules/spylambda.py b/spyce-2.1/modules/spylambda.py new file mode 100755 index 0000000000000000000000000000000000000000..f95fb1bb8df08c10943cf704063e280f560fe0d2 --- /dev/null +++ b/spyce-2.1/modules/spylambda.py @@ -0,0 +1,56 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: spylambda.py 1095 2006-08-09 22:28:10Z ellisj $ +################################################## + +from spyceModule import spyceModule +import string +import spyce + +__doc__ = """spylambda module produces functions from spyce strings.""" + +class spylambda(spyceModule): + def define(self, sig, code, memoize=0): + # compile spyce to catch errors early + spyce.DEBUG('generating spylambda for code:\n%s' % code) + spycecode = self._api.spyceString((code, sig)) + def processSpyce(args, kwargs, self=self, spycecode=spycecode): + s = None + try: + s = spycecode.newWrapper() + modules = self._api.getModules() + for name in modules.keys(): + s.setModule(name, modules[name]) + s.spyceInit(self._api.getRequest(), self._api.getResponse()) + result = s.spyceProcess(*args, **kwargs) + finally: + if s: + s.spyceDestroy() + spycecode.returnWrapper(s) + return result + if memoize: + def memoizer(f, id, stdout=self._api.getModule('stdout')): + def memoized(args, kwargs, f=f, id=id, stdout=stdout): + key = id, `args, kwargs` + try: r, s = stdout.memoizeCache[key] + except: + r, s = stdout.memoizeCache[key] = apply(stdout.capture, (f, args, kwargs)) + print s + return r + return memoized + Processspyce = memoizer(processSpyce, code) + def makeArgProcessor(f): + dict = { 'f': f } + exec ''' +def processArg(*args, **kwargs): + return f(args, kwargs) +''' in dict + return dict['processArg'] + return makeArgProcessor(processSpyce) + def __call__(self, sig, code, memoize=0): + return self.define(sig, code) + def __repr__(self): + return '' diff --git a/spyce-2.1/modules/spylambda.pyc b/spyce-2.1/modules/spylambda.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5871ca9849deb839e8dd1abfce4ae83dfa5d9f2 Binary files /dev/null and b/spyce-2.1/modules/spylambda.pyc differ diff --git a/spyce-2.1/modules/stdout.py b/spyce-2.1/modules/stdout.py new file mode 100755 index 0000000000000000000000000000000000000000..c506a9159904ad07aeac72fdc256a073d2beedef --- /dev/null +++ b/spyce-2.1/modules/stdout.py @@ -0,0 +1,107 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: stdout.py 540 2005-04-08 20:58:32Z jbe $ +################################################## + +from spyceModule import spyceModule +from spyceUtil import NoCloseOut +from cStringIO import StringIO + +__doc__ = '''Sets the thread-safe server stdout to the response object for +convenience of using print statements, and supports output redirection.''' + +class stdout(spyceModule): + def start(self): + # output redirection stack + self.outputStack = [] + # thread-safe stdout swap + self.stdout = self._api.getStdout() + self._api.setStdout(myResponseWrapper(self)) + # memoize storage + try: self.memoizeCache = self._api.getServerObject().memoized + except AttributeError: + self.memoizeCache = self._api.getServerObject().memoized = {} + def finish(self, theError=None): + # close all redirects + while self.outputStack: + self.pop() + # thread-safe stdout swap back + self._api.setStdout(self.stdout) + def push(self, file=None): + 'Redirect stdout to buffer' + old_response = self._api.getResponse() + old_response_mod = self._api.getModule('response') + new_response = spyceCaptureResponse(old_response) + self._api.setResponse(new_response) + new_response_mod = self._api.spyceModule('response', 'response.py')(self._api) + self._api.setModule('response', new_response_mod) + new_response_mod.start() + self.outputStack.append( (file, old_response, old_response_mod) ) + def pop(self): + 'Return buffer value, and possible write to file' + self._api.getModule('response').finish() + buffer = self._api.getResponse().getCapturedOutput() + file, old_response, old_response_mod = self.outputStack.pop() + self._api.setModule('response', old_response_mod) + self._api.setResponse(old_response) + if file: + file = os.path.join(os.path.dirname(self._api.getFilename()), file) + out = None + try: + out = open(file, 'w') + out.write(buffer) + finally: + if out: out.close() + return buffer + def capture(self, _spyceReserved, *args, **kwargs): + 'Capture the output side-effects of a function' + f = _spyceReserved # placeholder not to collide with kwargs + self.push() + r = apply(f, args, kwargs) + s = self.pop() + return r, s + def __repr__(self): + if hasattr(self, 'outputStack'): + return 'stdout depth: %s' % len(self.outputStack) + else: + return 'stdout not started' + +class myResponseWrapper: + def __init__(self, mod): + self._mod = mod + mod._api.registerModuleCallback(self.syncResponse) + self.syncResponse() + def syncResponse(self): + response = self._mod._api.getModule('response') + # functions (for performance) + self.write = response.write + self.writeln = response.writeln + self.flush = response.flush + def close(self): + raise 'method not allowed' + +class spyceCaptureResponse: + "Capture output, and let everything else through." + def __init__(self, old_response): + self._old_response = old_response + self._buf = StringIO() + def write(self, s): + self._buf.write(s) + def close(self): + raise 'cannot close output while capturing output' + def clear(self): + self._buf = StringIO() + def sendHeaders(self): + raise 'cannot sendHeaders while capturing output!' + def flush(self, stopFlag=0): + pass + def unbuffer(self): + raise 'cannot unbuffer output while capturing output!' + def __getattr__(self, name): + return eval('self._old_response.%s'%name) + def getCapturedOutput(self): + return self._buf.getvalue() + diff --git a/spyce-2.1/modules/stdout.pyc b/spyce-2.1/modules/stdout.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f4d5ecb7e13ee9005fe68658e7536990783952d Binary files /dev/null and b/spyce-2.1/modules/stdout.pyc differ diff --git a/spyce-2.1/modules/taglib.py b/spyce-2.1/modules/taglib.py new file mode 100755 index 0000000000000000000000000000000000000000..e3562d55f84f8ae21f5ece442ccae7c3ef987086 --- /dev/null +++ b/spyce-2.1/modules/taglib.py @@ -0,0 +1,103 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: taglib.py 1178 2006-09-12 13:14:19Z ellisj $ +################################################## + +from spyceModule import spyceModule +import os.path + +__doc__ = '''Spyce tags functionality.''' + +class taglib(spyceModule): + def start(self): + self.stack = [] + self.taglibs = {} + self._api.registerModuleCallback(self.__syncModules) + self.__syncModules() + def __syncModules(self): + modules = self._api.getModules() + self.mod_response = modules['response'] + self.mod_stdout = modules['stdout'] + def finish(self, theError): + self._api.unregisterModuleCallback(self.__syncModules) + for taglib in self.taglibs.keys(): + self.unload(taglib) + # load and unload tag libraries + def load(self, libname, libfrom=None, libas=None): + thename = libname + if libas: thename = libas + if self.taglibs.has_key(thename): + return + lib = self._api.spyceTaglib( + libname, libfrom, self._api.getFilename())(libname) + lib.start() + self.taglibs[thename] = lib + def unload(self, libname): + lib = None + try: + lib = self.taglibs[libname] + del self.taglibs[libname] + except KeyError: pass + if lib: lib.finish() + # tag processing + def tagPush(self, libname, tagname, tagid, context, attr, pair): + try: parent = self.stack[-1] + except: parent = None + lib = self.taglibs[libname] + sys.path.append(os.path.dirname(lib.__file__)) + tag = lib.getTag(self._api, tagname, tagid, context, attr, pair, parent) + self.stack.append(tag) + def tagPop(self): + sys.path.reverse() + sys.path.remove(os.path.dirname(self.stack[-1]._lib.__file__)) + sys.path.reverse() + self.outPopCond() + if self.stack: self.stack.pop() + def getTag(self): + return self.stack[-1] + def outPush(self): + tag = self.stack[-1] + if tag.buffer: + tag.setBuffered(1) + return self.mod_stdout.push() + def outPopCond(self): + tag = self.stack[-1] + if tag.getBuffered(): + tag.setBuffered(0) + return self.mod_stdout.pop() + def tagBegin(self): + tag = self.getTag() + tag.setOut(self.mod_response) + result = tag.begin(**tag._attrs) + self.outPush() + return result + def tagExport(self): + tag = self.getTag() + return tag.export() + def tagBody(self): + contents = self.outPopCond() + tag = self.getTag() + tag.setOut(self.mod_response) + result = tag.body(contents) + if result: self.outPush() + return result + def tagEnd(self): + self.outPopCond() + tag = self.getTag() + tag.setOut(self.mod_response) + return tag.end() + def tagCatch(self): + self.outPopCond() + tag = self.getTag() + tag.setOut(self.mod_response) + # class-based exception instances are in [1], but for string + # exceptions [1] is None, so pass [0] (the string raised) + tag.catch(sys.exc_info()[1] or sys.exc_info()[0]) + def __repr__(self): + return 'prefixes: %s; stack: %s' % ( + ', '.join(self.taglibs.keys()), + ', '.join([x.name for x in self.stack])) + diff --git a/spyce-2.1/modules/transform.py b/spyce-2.1/modules/transform.py new file mode 100755 index 0000000000000000000000000000000000000000..a07652f94e30292956aa7476d7bddab8ec2f9475 --- /dev/null +++ b/spyce-2.1/modules/transform.py @@ -0,0 +1,159 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: transform.py 598 2005-04-20 22:18:20Z jbe $ +################################################## + +from spyceModule import spyceModule +import types, re, string + +__doc__ = '''Transform module intercepts different kinds of Spyce ouput, and +can install functions to perform processing. It also includes some standard +Spyce transformation functions.''' + +OUTPUT_POSITON = 20 + +class transform(spyceModule): + def start(self): + self.ident = lambda x, **kwargs: x + self._filter = FilterFn(self.ident, self.ident, self.ident) + # install filter functions into response module + self._prevfilter = self._api.getModule('response').addFilter(OUTPUT_POSITON, self._filter) + def finish(self, theError=None): + self._prevfilter = self._api.getModule('response').addFilter(OUTPUT_POSITON, self._prevfilter) + # set filters + def dynamic(self, fn=None): + if not fn: fn = self.ident + self._filter.dynamicFilter = self.create(fn) + def static(self, fn=None): + if not fn: fn = self.ident + self._filter.staticFilter = self.create(fn) + def expr(self, fn=None): + if not fn: fn = self.ident + self._filter.exprFilter = self.create(fn) + # create filter + def create(self, fn): + '''Create filter function.''' + if fn==None or fn==() or fn==[]: + # identity + return self.ident + elif type(fn) == types.FunctionType: + # function type + return fn + elif type(fn) == type(''): + # string + file_name = string.split(fn, ':') + if len(file_name)==1: file, name = None, file_name[0] + else: file, name = file_name[:2] + if file: fn = loadModule(name, file, self._api.getFilename()) + else: fn = eval(name) + return fn + elif type(fn) == type(()) or type(fn) == type([]): + # tuple or array + fn0 = self.create(fn[0]) + fn1 = self.create(fn[1:]) + def filterfn(x, _fn0=fn0, _fn1=fn1, **kwargs): + x = apply(_fn0, (x,), kwargs) + return apply(_fn1, (x,), kwargs) + return filterfn + # commonly used transformations + def html_encode(self, s, also='', **kwargs): + '''Return HTML-encoded string.''' + return html_encode(s, also) + def url_encode(self, s, **kwargs): + '''Return url-encoded string.''' + return url_encode(s) + def __repr__(self): + return 'static: %s, expr: %s, dynamic: %s' % ( + str(self._filter.staticFilter!=self.ident), + str(self._filter.exprFilter!=self.ident), + str(self._filter.dynamicFilter!=self.ident), + ) + +try: + eval('Filter') +except NameError: + from response import Filter +class FilterFn(Filter): + def __init__(self, dynamicFilter=None, staticFilter=None, exprFilter=None): + ident = lambda x: x + if not dynamicFilter: dynamicFilter = ident + if not staticFilter: staticFilter = ident + if not exprFilter: exprFilter = ident + self.dynamicFilter = dynamicFilter + self.staticFilter = staticFilter + self.exprFilter = exprFilter + def dynamicImpl(self, s, *args, **kwargs): + return apply(self.dynamicFilter, (s,)+args, kwargs) + def staticImpl(self, s, *args, **kwargs): + return apply(self.staticFilter, (s,)+args, kwargs) + def exprImpl(self, s, *args, **kwargs): + return apply(self.exprFilter, (s,)+args, kwargs) + def flushImpl(self): + pass + def clearImpl(self): + pass + +# standard transformation functions +def ignore_none(o, **kwargs): + '''Does not print None.''' + if o==None: return '' + else: return o + +def silence(o, **kwargs): + '''Gobbles anything.''' + return '' + +def truncate(o, maxlen=None, **kwargs): + '''Limits output to a maximum string length.''' + if maxlen!=None: return str(o)[:maxlen] + else: return o + +_html_enc = { + chr(34): '"', chr(38): '&', chr(60): '<', chr(62): '>', + chr(160): ' ', chr(161): '¡', chr(162): '¢', chr(163): '£', + chr(164): '¤', chr(165): '¥', chr(166): '¦', chr(167): '§', + chr(168): '¨', chr(169): '©', chr(170): 'ª', chr(171): '«', + chr(172): '¬', chr(173): '­', chr(174): '®', chr(175): '¯', + chr(176): '°', chr(177): '±', chr(178): '²', chr(179): '³', + chr(180): '´', chr(181): 'µ', chr(182): '¶', chr(183): '·', + chr(184): '¸', chr(185): '¹', chr(186): 'º', chr(187): '»', + chr(188): '¼', chr(189): '½', chr(190): '¾', chr(191): '¿', + chr(192): 'À', chr(193): 'Á', chr(194): 'Â', chr(195): 'Ã', + chr(196): 'Ä', chr(197): 'Å', chr(198): 'Æ', chr(199): 'Ç', + chr(200): 'È', chr(201): 'É', chr(202): 'Ê', chr(203): 'Ë', + chr(204): 'Ì', chr(205): 'Í', chr(206): 'Î', chr(207): 'Ï', + chr(208): 'Ð', chr(209): 'Ñ', chr(210): 'Ò', chr(211): 'Ó', + chr(212): 'Ô', chr(213): 'Õ', chr(214): 'Ö', chr(215): '×', + chr(216): 'Ø', chr(217): 'Ù', chr(218): 'Ú', chr(219): 'Û', + chr(220): 'Ü', chr(221): 'Ý', chr(222): 'Þ', chr(223): 'ß', + chr(224): 'à', chr(225): 'á', chr(226): 'â', chr(227): 'ã', + chr(228): 'ä', chr(229): 'å', chr(230): 'æ', chr(231): 'ç', + chr(232): 'è', chr(233): 'é', chr(234): 'ê', chr(235): 'ë', + chr(236): 'ì', chr(237): 'í', chr(238): 'î', chr(239): 'ï', + chr(240): 'ð', chr(241): 'ñ', chr(242): 'ò', chr(243): 'ó', + chr(244): 'ô', chr(245): 'õ', chr(246): 'ö', chr(247): '÷', + chr(248): 'ø', chr(249): 'ù', chr(250): 'ú', chr(251): 'û', + chr(252): 'ü', chr(253): 'ý', chr(254): 'þ', chr(255): 'ÿ', +} +_html_ch = re.compile(r'['+reduce(lambda n, i: n+i, _html_enc.keys())+']') +def html_encode(o, also='', **kwargs): + '''Return HTML-encoded string.''' + o = _html_ch.sub(lambda match: _html_enc[match.group(0)], str(o)) + for c in also: + try: r=_html_enc[c] + except: r='&#%d;' % ord(c) + o=o.replace(c, r) + return o + +_url_ch = re.compile(r'[^A-Za-z0-9_.!~*()-]') # RFC 2396 section 2.3 +def url_encode(o, **kwargs): + '''Return URL-encoded string.''' + return _url_ch.sub(lambda match: "%%%02X" % ord(match.group(0)), str(o)) + +_nb_space_ch = re.compile(' ') +def nb_space(o, **kwargs): + '''Return string with spaces converted to be non-breaking.''' + return _nb_space_ch.sub(lambda match: ' ', str(o)) diff --git a/spyce-2.1/modules/transform.pyc b/spyce-2.1/modules/transform.pyc new file mode 100644 index 0000000000000000000000000000000000000000..724904473b36dd8b91eca90d8d2f7b27e7f46363 Binary files /dev/null and b/spyce-2.1/modules/transform.pyc differ diff --git a/spyce-2.1/pcqueue.py b/spyce-2.1/pcqueue.py new file mode 100755 index 0000000000000000000000000000000000000000..c3bf8a162cbb94ce8e788bdac103e91355a0825f --- /dev/null +++ b/spyce-2.1/pcqueue.py @@ -0,0 +1,75 @@ +import random, threading, time +import Queue as queue + +# producer/consumer queue, with (potentially) multiple consumers +class PCQueue: + def __init__(self, minthreads, maxthreads=0, qsize=0, name=None, q=None): + if not name: + name = self.__class__.__name__ + if maxthreads == 0: + maxthreads = minthreads + if minthreads > maxthreads: + raise ValueError('minthreads may not be greater than max') + if not q: + q = queue.Queue() + self.q = q + self.running = True + self.name = name + self.minthreads = minthreads + self.maxthreads = maxthreads + self.threadcount = 0 + for i in range(minthreads): + self.add_consumer() + if maxthreads > minthreads: + self.put = self._put + else: + self.put = lambda obj: self.q.put(obj) + def run_consumer(self): + while self.running: + try: + obj = self.next() + except queue.Empty: + if self.threadcount > self.minthreads: + # (30) slows down thread destruction w/ minimal overhead + if not random.randrange(30): + break + continue + self.consume(obj) + self.q.mutex.acquire() + self.threadcount -= 1 + self.q.mutex.release() + def next(self): + """ + Return an object to call consume on. Should raise queue.Empty + if nothing is available to process. Should not block indefinitely. + """ + return self.q.get(timeout=1) + def _put(self, obj): + # this method is unused if maxthreads == minthreads + self.q.put(obj) + self.q.mutex.acquire() + try: + if self.q._qsize() > 1 and self.threadcount < self.maxthreads: + self.add_consumer() + finally: + self.q.mutex.release() + def consume(self, obj): + raise NotImplementedError() + def add_consumer(self): + self.threadcount += 1 + th = threading.Thread(target=self.run_consumer, name='%s consumer %d' + % (self.name, self.threadcount)) + th.start() + def stop(self): + self.running = False + def join(self): + while self.q.qsize(): + time.sleep(0.1) + self.stop() + while self.threadcount: + time.sleep(0.1) + def __repr__(self): + return '%s(%s, %s)' % (self.__class__.__name__, self.threadcount, self.q) + def __getattr__(self, name): + return getattr(self.q, name) + diff --git a/spyce-2.1/pcqueue.pyc b/spyce-2.1/pcqueue.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ee8258448a444c395a54970765b284a0949cd3e Binary files /dev/null and b/spyce-2.1/pcqueue.pyc differ diff --git a/spyce-2.1/run_spyceCGI.py b/spyce-2.1/run_spyceCGI.py new file mode 100755 index 0000000000000000000000000000000000000000..f06026a512620c42662fce222c81ba035abb45a6 --- /dev/null +++ b/spyce-2.1/run_spyceCGI.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: run_spyceCGI.py 143 2002-09-11 01:12:01Z batripler $ +################################################## + +__doc__ = '''Version checking spyceCGI.py wrapper.''' + +script = 'spyceCGI.py' + +import sys, os, verchk + +if __name__ == '__main__': + spycePath = os.path.abspath(os.path.dirname(sys.modules['verchk'].__file__)) + sys.argv[0] = os.path.join(spycePath, script) + sys.argv.insert(0, os.path.join(spycePath, 'verchk.py')) + execfile(sys.argv[0]) diff --git a/spyce-2.1/run_spyceCmd.py b/spyce-2.1/run_spyceCmd.py new file mode 100755 index 0000000000000000000000000000000000000000..c4704e9753daef531da0ea9eff2cbe051d103df9 --- /dev/null +++ b/spyce-2.1/run_spyceCmd.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: run_spyceCmd.py 742 2005-05-21 01:42:20Z jbe $ +################################################## + +__doc__ = '''Version checking spyceCmd.py wrapper.''' + +script = 'spyceCmd.py' + +import sys, os, verchk + +if __name__ == '__main__': + spycePath = os.path.abspath(os.path.dirname(sys.modules['verchk'].__file__)) + sys.argv[0] = os.path.join(spycePath, script) + sys.argv.insert(0, os.path.join(spycePath, 'verchk.py')) + execfile(sys.argv[0]) + diff --git a/spyce-2.1/run_spyceModpy.py b/spyce-2.1/run_spyceModpy.py new file mode 100755 index 0000000000000000000000000000000000000000..6db77749b32afa6d444c502aa67fb73563f75b1d --- /dev/null +++ b/spyce-2.1/run_spyceModpy.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: run_spyceModpy.py 143 2002-09-11 01:12:01Z batripler $ +################################################## + +__doc__ = '''Version checking spyceModpy.py wrapper.''' + +try: + import _apache + from mod_python import apache +except: pass + +def spyceMain(apacheRequest): + return spyceMainVersion(apacheRequest) + +def spyceMainVersion(apacheRequest): + "Version checking Apache entry point." + import verchk + if not verchk.checkversion(verchk.REQUIRED): + import sys + apacheRequest.content_type = 'text/plain' + apacheRequest.send_http_header() + apacheRequest.write('Spyce can not run on this version of Python.\n') + apacheRequest.write('Python version '+sys.version[:3]+' detected.\n') + apacheRequest.write('Python version '+verchk.REQUIRED+' or greater required.\n') + try: + return apache.OK + except: pass + else: + global spyceMain + import spyceModpy + spyceMain = spyceModpy.spyceMain + return spyceModpy.spyceMain(apacheRequest) + +if __name__ == '__main__': + print "********** ERROR: **********" + print "This program can not be run from the command-line." + print "Use run_spyceCmd.py, or run via Apache." + print "For configuring Apache, have a look at 'spyceApache.conf'." + print + print "Also, please read the documentation at:" + print " http://spyce.sourceforge.net" + print "for other options." + diff --git a/spyce-2.1/scheduler.py b/spyce-2.1/scheduler.py new file mode 100755 index 0000000000000000000000000000000000000000..bce3eca3cfc37146f918a71a02ebb7223e8621f1 --- /dev/null +++ b/spyce-2.1/scheduler.py @@ -0,0 +1,195 @@ +""" +A module for scheduling arbitrary callables to run at given times +or intervals, modeled on the naviserver API. Scheduler runs in +its own thread; callables run in this same thread, so if you have +an unusually long callable to run you may wish to give it its own +thread, for instance, + +schedule(3600, lambda: threading.Thread(target=longcallable).start()) + +Scheduler does not provide subsecond resolution. + +Public functions are threadsafe. +""" + +import atexit, sys, threading, time, traceback + + +logger = lambda s: sys.stderr.write('%s\n' % s) +def debuglogger(s): + import spyce + spyce.DEBUG(s) + +def schedule(interval, callable, once=False): + """ + Schedules callable to be run every interval seconds. + Returns the scheduled Task object. + """ + if interval < 1: + raise Exception("Interval must be postitive") + return _insertsorted(Task(time.time() + interval, interval, callable, once)) + +def _nearest_epoch(hours, minutes): + L = list(time.localtime(time.time())) + L[3] = hours + L[4] = minutes + L[5] = 0 + epoch = time.mktime(L) + if epoch < time.time(): + epoch += 24 * 3600 + return epoch + +def schedule_daily(hours, minutes, callable, once=False): + """ + Schedules callable to be run at hours:minutes every day. + (Hours is a 24-hour format.) + Returns the scheduled Task object. + """ + if hours > 23 or hours < 0: + raise ValueError("Invalid hours %s" % hours) + if minutes > 59 or minutes < 0: + raise ValueError("Invalid minutes %s" % minutes) + epoch = _nearest_epoch(hours, minutes) + return _insertsorted(Task(epoch, 24 * 3600, callable, once)) + +def schedule_weekly(day, hours, minutes, callable, once=False): + """ + Schedules callable to be run at hours:minutes on the given + zero-based day of the week. (Monday is 0.) + Returns the scheduled Task object. + """ + if day > 6 or day < 0: + raise ValueError("Invalid day %s" % day) + if hours > 23 or hours < 0: + raise ValueError("Invalid hours %s" % hours) + if minutes > 59 or minutes < 0: + raise ValueError("Invalid minutes %s" % minutes) + epoch = _nearest_epoch(hours, minutes) + while time.localtime(epoch)[6] != day: + epoch += 24 * 3600 + return _insertsorted(Task(epoch, 7 * 24 * 3600, callable, once)) + +def unschedule(task): + """ + Removes the given task from the scheduling queue. + """ + _qlock.acquire() + try: + _queue.remove(task) + finally: + _qlock.release() + +class Task: + """ + Instantiated by the schedule methods. + + Instance variables: + nextrun: epoch seconds at which to run next + interval: seconds before repeating + callable: function to invoke + last: if True, will be unscheduled after nextrun + + (Note that by manually setting last on a Task instance, you + can cause it to run an arbitrary number of times.) + """ + def __init__(self, firstrun, interval, callable, once): + self.nextrun = firstrun + self.interval = interval + self.callable = callable + self.last = once + def __repr__(self): + return 'Task(nextrun=%r, interval=%d, callable=%s, last=%s)' \ + % (time.asctime(time.localtime(self.nextrun)), self.interval, self.callable, self.last) + +_qlock = threading.RLock() +# (we don't use a Queue object here since we need to do our own locking anyway) +_queue = [] + +def _insertsorted(task): + _qlock.acquire() + i = len(_queue) + while i > 0: + if task.nextrun < _queue[i - 1].nextrun: + break + i -= 1 + _queue.insert(i, task) + _qlock.release() + return task + +_keepgoing = True +_paused = False + +def _process(): + """True if a task was run""" + try: + _qlock.acquire() + if not len(_queue): + return False + if _queue[-1].nextrun > time.time(): + return False + task = _queue.pop() + finally: + _qlock.release() + debuglogger('running scheduled task %s' % task.callable) + try: + task.callable() + except Exception: + logger(traceback.format_exc()) + if not task.last: + task.nextrun = max(task.nextrun + task.interval, time.time()) + _insertsorted(task) + return True + +def _loop(): + sleep = False + while _keepgoing: + if sleep: + time.sleep(1) + if _paused: + sleep = True + continue + sleep = not _process() + debuglogger("Scheduler thread has stopped") + +_thread = threading.Thread(target=_loop, name='spyce-scheduler') +_thread.start() + +def pause(): + """Temporarily suspend running scheduled tasks""" + _paused = True + +def unpause(): + """ + Resume running scheduled tasks. If a task came due while + it was paused, it will run immediately after unpausing. + """ + _paused = False + +def _cleanup(): + debuglogger('Waiting for scheduler thread...') + global _keepgoing + _keepgoing = False + _thread.join() + +atexit.register(_cleanup) + +# yes, I need more and better tests... you're welcome to add some :) +if __name__ == "__main__": + d = {} + def foo(): + d['test'] = 1 + print schedule(1, lambda: threading.Thread(target=foo).start(), True) + while not d: + time.sleep(1) + print d + assert not _queue + + d = {} + hours, minutes, _, day = time.localtime(time.time() + 61)[3:7] + print schedule_weekly(day, hours, minutes, foo) + print '(waiting... could be up to 60 seconds)' + while not d: + time.sleep(1) + print d + assert len(_queue) == 1 + diff --git a/spyce-2.1/scheduler.pyc b/spyce-2.1/scheduler.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52baf889deb5b25fa7084805f21805841cb7972b Binary files /dev/null and b/spyce-2.1/scheduler.pyc differ diff --git a/spyce-2.1/spyce.mime b/spyce-2.1/spyce.mime new file mode 100755 index 0000000000000000000000000000000000000000..b9e5a936f3eb2fe3d54b8bf992d075790466b098 --- /dev/null +++ b/spyce-2.1/spyce.mime @@ -0,0 +1,495 @@ +# This is the mime.types file from the Apache web server distribution (1.3.22) +# with local modifications. + +# This file controls what Internet media types are sent to the client for +# given file extension(s). Sending the correct media type to the client +# is important so they know how to handle the content of the file. +# Extra types can either be added here or by using an AddType directive +# in your config files. For more information about Internet media types, +# please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type +# registry is at . + +# MIME type Extension +application/EDI-Consent +application/EDI-X12 +application/EDIFACT +application/activemessage +application/andrew-inset ez +application/applefile +application/atomicmail +application/batch-SMTP +application/beep+xml +application/cals-1840 +application/commonground +application/cybercash +application/dca-rft +application/dec-dx +application/dvcs +application/eshop +application/http +application/hyperstudio +application/iges +application/index +application/index.cmd +application/index.obj +application/index.response +application/index.vnd +application/iotp +application/ipp +application/isup +application/font-tdpfr +application/mac-binhex40 hqx +application/mac-compactpro cpt +application/macwriteii +application/marc +application/mathematica +application/mathematica-old +application/msword doc +application/news-message-id +application/news-transmission +application/ocsp-request +application/ocsp-response +application/octet-stream bin dms lha lzh exe class so dll +application/oda oda +application/parityfec +application/pdf pdf +application/pgp-encrypted +application/pgp-keys +application/pgp-signature +application/pkcs10 +application/pkcs7-mime +application/pkcs7-signature +application/pkix-cert +application/pkix-crl +application/pkixcmp +application/postscript ai eps ps +application/prs.alvestrand.titrax-sheet +application/prs.cww +application/prs.nprend +application/qsig +application/remote-printing +application/riscos +application/rtf rtf +application/set-payment +application/set-payment-initiation +application/set-registration +application/set-registration-initiation +application/sgml +application/sgml-open-catalog +application/sieve +application/slate +application/timestamp-query +application/timestamp-reply +application/vemmi +application/vnd.3M.Post-it-Notes +application/vnd.FloGraphIt +application/vnd.accpac.simply.aso +application/vnd.accpac.simply.imp +application/vnd.acucobol +application/vnd.aether.imp +application/vnd.anser-web-certificate-issue-initiation +application/vnd.anser-web-funds-transfer-initiation +application/vnd.audiograph +application/vnd.businessobjects +application/vnd.bmi +application/vnd.canon-cpdl +application/vnd.canon-lips +application/vnd.claymore +application/vnd.commerce-battelle +application/vnd.commonspace +application/vnd.comsocaller +application/vnd.contact.cmsg +application/vnd.cosmocaller +application/vnd.cups-postscript +application/vnd.cups-raster +application/vnd.cups-raw +application/vnd.ctc-posml +application/vnd.cybank +application/vnd.dna +application/vnd.dpgraph +application/vnd.dxr +application/vnd.ecdis-update +application/vnd.ecowin.chart +application/vnd.ecowin.filerequest +application/vnd.ecowin.fileupdate +application/vnd.ecowin.series +application/vnd.ecowin.seriesrequest +application/vnd.ecowin.seriesupdate +application/vnd.enliven +application/vnd.epson.esf +application/vnd.epson.msf +application/vnd.epson.quickanime +application/vnd.epson.salt +application/vnd.epson.ssf +application/vnd.ericsson.quickcall +application/vnd.eudora.data +application/vnd.fdf +application/vnd.ffsns +application/vnd.framemaker +application/vnd.fsc.weblaunch +application/vnd.fujitsu.oasys +application/vnd.fujitsu.oasys2 +application/vnd.fujitsu.oasys3 +application/vnd.fujitsu.oasysgp +application/vnd.fujitsu.oasysprs +application/vnd.fujixerox.ddd +application/vnd.fujixerox.docuworks +application/vnd.fujixerox.docuworks.binder +application/vnd.fut-misnet +application/vnd.grafeq +application/vnd.groove-account +application/vnd.groove-identity-message +application/vnd.groove-injector +application/vnd.groove-tool-message +application/vnd.groove-tool-template +application/vnd.groove-vcard +application/vnd.hhe.lesson-player +application/vnd.hp-HPGL +application/vnd.hp-PCL +application/vnd.hp-PCLXL +application/vnd.hp-hpid +application/vnd.hp-hps +application/vnd.httphone +application/vnd.hzn-3d-crossword +application/vnd.ibm.afplinedata +application/vnd.ibm.MiniPay +application/vnd.ibm.modcap +application/vnd.informix-visionary +application/vnd.intercon.formnet +application/vnd.intertrust.digibox +application/vnd.intertrust.nncp +application/vnd.intu.qbo +application/vnd.intu.qfx +application/vnd.irepository.package+xml +application/vnd.is-xpr +application/vnd.japannet-directory-service +application/vnd.japannet-jpnstore-wakeup +application/vnd.japannet-payment-wakeup +application/vnd.japannet-registration +application/vnd.japannet-registration-wakeup +application/vnd.japannet-setstore-wakeup +application/vnd.japannet-verification +application/vnd.japannet-verification-wakeup +application/vnd.koan +application/vnd.lotus-1-2-3 +application/vnd.lotus-approach +application/vnd.lotus-freelance +application/vnd.lotus-notes +application/vnd.lotus-organizer +application/vnd.lotus-screencam +application/vnd.lotus-wordpro +application/vnd.mcd +application/vnd.mediastation.cdkey +application/vnd.meridian-slingshot +application/vnd.mif mif +application/vnd.minisoft-hp3000-save +application/vnd.mitsubishi.misty-guard.trustweb +application/vnd.mobius.daf +application/vnd.mobius.dis +application/vnd.mobius.msl +application/vnd.mobius.plc +application/vnd.mobius.txf +application/vnd.motorola.flexsuite +application/vnd.motorola.flexsuite.adsi +application/vnd.motorola.flexsuite.fis +application/vnd.motorola.flexsuite.gotap +application/vnd.motorola.flexsuite.kmr +application/vnd.motorola.flexsuite.ttc +application/vnd.motorola.flexsuite.wem +application/vnd.mozilla.xul+xml +application/vnd.ms-artgalry +application/vnd.ms-asf +application/vnd.ms-excel xls +application/vnd.ms-lrm +application/vnd.ms-powerpoint ppt +application/vnd.ms-project +application/vnd.ms-tnef +application/vnd.ms-works +application/vnd.mseq +application/vnd.msign +application/vnd.music-niff +application/vnd.musician +application/vnd.netfpx +application/vnd.noblenet-directory +application/vnd.noblenet-sealer +application/vnd.noblenet-web +application/vnd.novadigm.EDM +application/vnd.novadigm.EDX +application/vnd.novadigm.EXT +application/vnd.osa.netdeploy +application/vnd.palm +application/vnd.pg.format +application/vnd.pg.osasli +application/vnd.powerbuilder6 +application/vnd.powerbuilder6-s +application/vnd.powerbuilder7 +application/vnd.powerbuilder7-s +application/vnd.powerbuilder75 +application/vnd.powerbuilder75-s +application/vnd.previewsystems.box +application/vnd.publishare-delta-tree +application/vnd.pvi.ptid1 +application/vnd.pwg-xhtml-print+xml +application/vnd.rapid +application/vnd.s3sms +application/vnd.seemail +application/vnd.shana.informed.formdata +application/vnd.shana.informed.formtemplate +application/vnd.shana.informed.interchange +application/vnd.shana.informed.package +application/vnd.sss-cod +application/vnd.sss-dtf +application/vnd.sss-ntf +application/vnd.street-stream +application/vnd.svd +application/vnd.swiftview-ics +application/vnd.triscape.mxs +application/vnd.trueapp +application/vnd.truedoc +application/vnd.tve-trigger +application/vnd.ufdl +application/vnd.uplanet.alert +application/vnd.uplanet.alert-wbxml +application/vnd.uplanet.bearer-choice-wbxml +application/vnd.uplanet.bearer-choice +application/vnd.uplanet.cacheop +application/vnd.uplanet.cacheop-wbxml +application/vnd.uplanet.channel +application/vnd.uplanet.channel-wbxml +application/vnd.uplanet.list +application/vnd.uplanet.list-wbxml +application/vnd.uplanet.listcmd +application/vnd.uplanet.listcmd-wbxml +application/vnd.uplanet.signal +application/vnd.vcx +application/vnd.vectorworks +application/vnd.vidsoft.vidconference +application/vnd.visio +application/vnd.vividence.scriptfile +application/vnd.wap.sic sic +application/vnd.wap.slc slc +application/vnd.wap.wbxml wbxml +application/vnd.wap.wmlc wmlc +application/vnd.wap.wmlscriptc wmlsc +application/vnd.webturbo +application/vnd.wrq-hp3000-labelled +application/vnd.wt.stf +application/vnd.xara +application/vnd.xfdl +application/vnd.yellowriver-custom-menu +application/whoispp-query +application/whoispp-response +application/wita +application/wordperfect5.1 +application/x-bcpio bcpio +application/x-bzip2 bz2 +application/x-cdlink vcd +application/x-chess-pgn pgn +application/x-compress +application/x-cpio cpio +application/x-csh csh +application/x-director dcr dir dxr +application/x-dvi dvi +application/x-futuresplash spl +application/x-gtar gtar +application/x-gzip gz tgz +application/x-hdf hdf +application/x-javascript js +application/x-kword kwd kwt +application/x-kspread ksp +application/x-kpresenter kpr kpt +application/x-kchart chrt +application/x-killustrator kil +application/x-koan skp skd skt skm +application/x-latex latex +application/x-netcdf nc cdf +application/x-ogg ogg + +application/x-rpm rpm + +application/x-sh sh +application/x-shar shar +application/x-shockwave-flash swf +application/x-stuffit sit +application/x-sv4cpio sv4cpio +application/x-sv4crc sv4crc +application/x-tar tar +application/x-tcl tcl +application/x-tex tex +application/x-texinfo texinfo texi +application/x-troff t tr roff +application/x-troff-man man +application/x-troff-me me +application/x-troff-ms ms +application/x-ustar ustar +application/x-wais-source src +application/x400-bp +application/xhtml+xml xhtml xht +application/xml +application/xml-dtd +application/xml-external-parsed-entity +application/zip zip +audio/32kadpcm +audio/g.722.1 +audio/l16 +audio/midi mid midi kar +audio/mp4a-latm +audio/mpa-robust +audio/mpeg mpga mp2 mp3 +audio/parityfec +audio/prs.sid +audio/telephone-event +audio/tone +audio/vnd.cisco.nse +audio/vnd.cns.anp1 +audio/vnd.cns.inf1 +audio/vnd.digital-winds +audio/vnd.everad.plj +audio/vnd.lucent.voice +audio/vnd.nortel.vbk +audio/vnd.nuera.ecelp4800 +audio/vnd.nuera.ecelp7470 +audio/vnd.nuera.ecelp9600 +audio/vnd.octel.sbc +audio/vnd.qcelp +audio/vnd.rhetorex.32kadpcm +audio/vnd.vmx.cvsd +audio/x-mpegurl m3u +audio/x-realaudio ra +chemical/x-pdb pdb +chemical/x-xyz xyz +image/bmp bmp +image/cgm +image/g3fax +image/gif gif +image/ief ief +image/jpeg jpeg jpg jpe +image/naplps +image/png png +image/prs.btif +image/prs.pti +image/tiff tiff tif +image/vnd.cns.inf2 +image/vnd.djvu djvu djv +image/vnd.dwg +image/vnd.dxf +image/vnd.fastbidsheet +image/vnd.fpx +image/vnd.fst +image/vnd.fujixerox.edmics-mmr +image/vnd.fujixerox.edmics-rlc +image/vnd.mix +image/vnd.net-fpx +image/vnd.svf +image/vnd.wap.wbmp wbmp +image/vnd.xiff +image/x-cmu-raster ras +image/x-portable-anymap pnm +image/x-portable-bitmap pbm +image/x-portable-graymap pgm +image/x-portable-pixmap ppm +image/x-rgb rgb +image/x-xbitmap xbm +image/x-xpixmap xpm +image/x-xwindowdump xwd +message/delivery-status +message/disposition-notification +message/external-body +message/http +message/news +message/partial +message/rfc822 +message/s-http +model/iges igs iges +model/mesh msh mesh silo +model/vnd.dwf +model/vnd.flatland.3dml +model/vnd.gdl +model/vnd.gs-gdl +model/vnd.gtw +model/vnd.mts +model/vnd.vtu +model/vrml wrl vrml +multipart/alternative +multipart/appledouble +multipart/byteranges +multipart/digest +multipart/encrypted +multipart/form-data +multipart/header-set +multipart/mixed +multipart/parallel +multipart/related +multipart/report +multipart/signed +multipart/voice-message +text/calendar +text/css css +text/directory +text/enriched +text/parityfec +text/plain asc txt +text/prs.lines.tag +text/rfc822-headers +text/richtext rtx +text/rtf rtf +text/sgml sgml sgm +text/tab-separated-values tsv +text/t140 +text/uri-list +text/vnd.DMClientScript +text/vnd.IPTC.NITF +text/vnd.IPTC.NewsML +text/vnd.abc +text/vnd.curl +text/vnd.flatland.3dml +text/vnd.fly +text/vnd.fmi.flexstor +text/vnd.in3d.3dml +text/vnd.in3d.spot +text/vnd.latex-z +text/vnd.motorola.reflex +text/vnd.ms-mediapackage +text/vnd.wap.si si +text/vnd.wap.sl sl +text/vnd.wap.wml wml +text/vnd.wap.wmlscript wmls +text/x-setext etx +text/xml xml xsl +text/xml-external-parsed-entity +video/mp4v-es +video/mpeg mpeg mpg mpe +video/parityfec +video/pointer +video/quicktime qt mov +video/vnd.fvt +video/vnd.motorola.video +video/vnd.motorola.videop +video/vnd.mpegurl mxu +video/vnd.mts +video/vnd.nokia.interleaved-multimedia +video/vnd.vivo +video/x-msvideo avi +video/x-sgi-movie movie +x-conference/x-cooltalk ice +audio/x-pn-realaudio rmm ram +audio/vnd.rn-realaudio ra +application/smil smi smil +text/vnd.rn-realtext rt +video/vnd.rn-realvideo rv +image/vnd.rn-realflash rf swf +application/x-shockwave-flash2-preview rf swf +application/sdp sdp +application/x-sdp sdp +application/vnd.rn-realmedia rm +image/vnd.rn-realpix rp +audio/wav wav +audio/x-wav wav +audio/x-pn-wav wav +audio/x-pn-windows-acm wav +audio/basic au +audio/x-pn-au au +audio/aiff aiff af +audio/x-aiff aiff af +audio/x-pn-aiff aiff af +text/html html htm diff --git a/spyce-2.1/spyce.nsi b/spyce-2.1/spyce.nsi new file mode 100755 index 0000000000000000000000000000000000000000..81c587d8c1f2620d1a552883200173cf5ed00b7e --- /dev/null +++ b/spyce-2.1/spyce.nsi @@ -0,0 +1,208 @@ +; spyce.nsi +; Spyce Installer (NSIS script) + +;##################################### +;VERSION + +!define VERSION 2.1 +!define RELEASE 3 + +;##################################### +;DEFINES + +!define NAME Spyce +!define NAME_SMALL spyce +!define Desc "Spyce - Python Server Pages" +!define REG_PROG "SOFTWARE\${NAME}" +!define REG_UNINST "Software\Microsoft\Windows\CurrentVersion\Uninstall\${NAME}" +!define PYTHON "python.exe" +!define REG_PYTHONLOC "${REG_PROG}" + +!define COMPILE 1 + +;##################################### +;OPTIONS + +OutFile "${NAME_SMALL}-${VERSION}.exe" +InstallDir $PROGRAMFILES\${NAME_SMALL} +InstallDirRegKey HKLM ${REG_PROG} "location" + +Name "${NAME}" +Caption "${NAME} Windows Installer" +UninstallCaption "${NAME} Windows Uninstaller" +DirText "${NAME} Windows Installer" +ComponentText "${NAME} Windows Installer" +CompletedText "${NAME} Windows Installer is finished" +UninstallText "${NAME} Windows Uninstaller" +BrandingText " " + +CRCCheck on +AutoCloseWindow true +EnabledBitmap misc/one-check.bmp +DisabledBitmap misc/one-nocheck.bmp +ShowInstDetails show +ShowUninstDetails show +;BGGradient +SilentUnInstall silent +Icon misc\pics\spyce-border.ico ; MUST contain a 32x32x16 color icon +UninstallIcon misc\pics\spyce-border.ico +WindowIcon on +SetOverwrite on +SetCompress auto +SetDatablockOptimize on +SetDateSave off + +;##################################### +;SECTIONS + +Section "${NAME} engine" + ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python" + SetOutPath $INSTDIR + ; create and register uninstaller + WriteUninstaller "uninstall.exe" + WriteRegStr HKLM ${REG_PROG} "location" "$INSTDIR" + WriteRegStr HKLM "${REG_UNINST}" "DisplayName" "${NAME}: ${DESC} (remove only)" + WriteRegStr HKLM "${REG_UNINST}" "UninstallString" '"$INSTDIR\uninstall.exe"' + + ; copy spyce engine files + File *.py + File CHANGES LICENCE README THANKS spyceApache.conf spyce.conf.eg misc\pics\spyce.ico spyce.mime + SetOutPath "$INSTDIR\modules" + File modules\*.py + SetOutPath "$INSTDIR\tags" + File tags\*.py + SetOutPath - + ; pre-compile the sources + !ifdef COMPILE + DetailPrint "Compile Spyce sources." + ExecWait `"$9" "$INSTDIR\spyceParser.py"` + ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR"` + ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR"` + ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR\modules"` + ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR\modules"` + ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR\tags"` + ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR\tags"` + !endif +SectionEnd + +Section "${NAME} documentation" + SectionIn RO + ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python" + ; copy Spyce documentation files + SetOutPath "$INSTDIR\docs" + File docs\*.spy docs\*.gif + SetOutPath "$INSTDIR\docs\examples" + File docs\examples\*.spy docs\examples\*.spi docs\examples\*.tmpl docs\examples\*.py docs\examples\*.gif + SetOutPath "$INSTDIR\docs\inc" + File docs\inc\*.spi + SetOutPath - + ; compile documentation + !ifdef COMPILE + DetailPrint "Compile Spyce documentation." + ExecWait `"$9" "$INSTDIR\run_spyceCmd.py" "-O" "$INSTDIR\docs\*.spy"` + !endif +SectionEnd + +SectionDivider "Options" + +Section "Create start menu shortcuts" + CreateDirectory "$SMPROGRAMS\${NAME}" + CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Documentation.lnk" "$INSTDIR\docs\index.html" "" "$INSTDIR\spyce.ico" + CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Documentation -- localhost.lnk" "http://localhost/spyce/" "" "" + CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Online.lnk" "http://spyce.sf.net/" "" "" + CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Examples.lnk" "$INSTDIR\docs\examples" "" "$INSTDIR\spyce.ico" + CreateShortCut "$SMPROGRAMS\${NAME}\Uninstall Spyce.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 +SectionEnd + +Section "Create shell extensions" + WriteRegStr HKCR ".spy" "" "SpyceFile" + WriteRegStr HKCR "SpyceFile" "" "Spyce dynamic HTML file" + WriteRegStr HKCR "SpyceFile\DefaultIcon" "" $INSTDIR\spyce.ico,0 + WriteRegStr HKCR "SpyceFile\shell\open\command" "" 'notepad.exe "%1"' + WriteRegStr HKCR "SpyceFile\shell\compile" "" "Compile Spyce" + WriteRegStr HKCR "SpyceFile\shell\compile\command" "" '"$9" "$INSTDIR\run_spyceCmd.py" -O "%1"' + WriteRegStr HKCR "SpyceFile\shell" "" "compile" +SectionEnd + +Section "Configure Apache" + DetailPrint "Configuring Apache..." + ExecWait `"$9" "$INSTDIR\installHelper.py" "--apache=$INSTDIR"` + ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheRestart"` + MessageBox MB_OK|MB_ICONEXCLAMATION "$\nApache reconfigured and restarted.$\nIf everything is ok, you should be able to browse to: http://localhost/spyce/$\nIf not, please check your httpd.conf file and/or restart Apache." + +SectionEnd + + +Section "Uninstall" + ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python" + DetailPrint "Unconfiguring Apache..." + ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheUN"` + ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheRestart"` + RMDir /r "$INSTDIR" + RMDir /r "$SMPROGRAMS\${NAME}" + DeleteRegKey HKLM ${REG_UNINST} + DeleteRegKey HKLM ${REG_PROG} + DeleteRegKey HKCR ".spy" + DeleteRegKey HKCR "SpyceFile" +SectionEnd + +;##################################### +;FUNCTIONS + +Function detectPython + ; see if there is any python interpreter + ClearErrors + ExecShell "open" "${PYTHON}" `-c "print 'Python is alive!'"` SW_SHOWMINIMIZED + IfErrors 0 NoAbort + MessageBox MB_OK|MB_ICONEXCLAMATION "Unable to find Python interpreter. Please install Python first." + Abort + NoAbort: + ; find out where it is + GetTempFileName $9 + GetTempFileName $8 + FileOpen $7 $9 w + FileWrite $7 'import sys$\n' + FileWrite $7 "f=open(r'$8', 'w')$\n" + FileWrite $7 'f.write(sys.executable)$\n' + FileWrite $7 'f.close()$\n' + FileClose $7 + ExecShell "open" "${PYTHON}" `"$9"` SW_SHOWMINIMIZED + IntOp $0 0 + 0 + Loop: + FileOpen $7 $8 r + FileRead $7 $6 + FileClose $7 + StrCmp $6 "" 0 EndLoop + Sleep 100 + IntOp $0 $0 + 1 + IntCmp $0 50 EndLoop + Goto Loop + EndLoop: + Delete $9 + StrCpy $9 "$6" ; put the python path in $9 -- GLOBAL + StrCmp $9 "" 0 NoAbort2 + MessageBox MB_OK|MB_ICONEXCLAMATION "Mechanism for discovering Python path via sys.executable did not work.$\nSorry, but automatic installation is unable to proceed. Please contact the author." + Abort + NoAbort2: + WriteRegStr HKLM ${REG_PYTHONLOC} "python" "$9" +FunctionEnd + +Function .onInit + Call detectPython +FunctionEnd + +Function .onInstSuccess + DetailPrint "Spyce successfully installed." + MessageBox MB_OK "Spyce successfully installed." + ExecShell "open" "$INSTDIR\docs\index.html" SW_SHOWMAXIMIZED +FunctionEnd + +Function un.onInit + MessageBox MB_YESNO|MB_ICONQUESTION "Are you sure that you want to uninstall Spyce?" IDYES NoAbort + Abort + NoAbort: +FunctionEnd + +Function un.onUninstSuccess + MessageBox MB_OK "Spyce successfully uninstalled." +FunctionEnd diff --git a/spyce-2.1/spyce.nsi.in b/spyce-2.1/spyce.nsi.in new file mode 100755 index 0000000000000000000000000000000000000000..1005f7a59e181b92b85b826b0f7f0e60bdf65ab4 --- /dev/null +++ b/spyce-2.1/spyce.nsi.in @@ -0,0 +1,208 @@ +; spyce.nsi +; Spyce Installer (NSIS script) + +;##################################### +;VERSION + +!define VERSION __VERSION__ +!define RELEASE __RELEASE__ + +;##################################### +;DEFINES + +!define NAME Spyce +!define NAME_SMALL spyce +!define Desc "Spyce - Python Server Pages" +!define REG_PROG "SOFTWARE\${NAME}" +!define REG_UNINST "Software\Microsoft\Windows\CurrentVersion\Uninstall\${NAME}" +!define PYTHON "python.exe" +!define REG_PYTHONLOC "${REG_PROG}" + +!define COMPILE 1 + +;##################################### +;OPTIONS + +OutFile "${NAME_SMALL}-${VERSION}.exe" +InstallDir $PROGRAMFILES\${NAME_SMALL} +InstallDirRegKey HKLM ${REG_PROG} "location" + +Name "${NAME}" +Caption "${NAME} Windows Installer" +UninstallCaption "${NAME} Windows Uninstaller" +DirText "${NAME} Windows Installer" +ComponentText "${NAME} Windows Installer" +CompletedText "${NAME} Windows Installer is finished" +UninstallText "${NAME} Windows Uninstaller" +BrandingText " " + +CRCCheck on +AutoCloseWindow true +EnabledBitmap misc/one-check.bmp +DisabledBitmap misc/one-nocheck.bmp +ShowInstDetails show +ShowUninstDetails show +;BGGradient +SilentUnInstall silent +Icon misc\pics\spyce-border.ico ; MUST contain a 32x32x16 color icon +UninstallIcon misc\pics\spyce-border.ico +WindowIcon on +SetOverwrite on +SetCompress auto +SetDatablockOptimize on +SetDateSave off + +;##################################### +;SECTIONS + +Section "${NAME} engine" + ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python" + SetOutPath $INSTDIR + ; create and register uninstaller + WriteUninstaller "uninstall.exe" + WriteRegStr HKLM ${REG_PROG} "location" "$INSTDIR" + WriteRegStr HKLM "${REG_UNINST}" "DisplayName" "${NAME}: ${DESC} (remove only)" + WriteRegStr HKLM "${REG_UNINST}" "UninstallString" '"$INSTDIR\uninstall.exe"' + + ; copy spyce engine files + File *.py + File CHANGES LICENCE README THANKS spyceApache.conf spyce.conf.eg misc\pics\spyce.ico spyce.mime + SetOutPath "$INSTDIR\modules" + File modules\*.py + SetOutPath "$INSTDIR\tags" + File tags\*.py + SetOutPath - + ; pre-compile the sources + !ifdef COMPILE + DetailPrint "Compile Spyce sources." + ExecWait `"$9" "$INSTDIR\spyceParser.py"` + ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR"` + ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR"` + ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR\modules"` + ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR\modules"` + ExecWait `"$9" "$INSTDIR\installHelper.py" "--py=$INSTDIR\tags"` + ;ExecWait `"$9" -OO "$INSTDIR\installHelper.py" "--py=$INSTDIR\tags"` + !endif +SectionEnd + +Section "${NAME} documentation" + SectionIn RO + ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python" + ; copy Spyce documentation files + SetOutPath "$INSTDIR\docs" + File docs\*.spy docs\*.gif + SetOutPath "$INSTDIR\docs\examples" + File docs\examples\*.spy docs\examples\*.spi docs\examples\*.tmpl docs\examples\*.py docs\examples\*.gif + SetOutPath "$INSTDIR\docs\inc" + File docs\inc\*.spi + SetOutPath - + ; compile documentation + !ifdef COMPILE + DetailPrint "Compile Spyce documentation." + ExecWait `"$9" "$INSTDIR\run_spyceCmd.py" "-O" "$INSTDIR\docs\*.spy"` + !endif +SectionEnd + +SectionDivider "Options" + +Section "Create start menu shortcuts" + CreateDirectory "$SMPROGRAMS\${NAME}" + CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Documentation.lnk" "$INSTDIR\docs\index.html" "" "$INSTDIR\spyce.ico" + CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Documentation -- localhost.lnk" "http://localhost/spyce/" "" "" + CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Online.lnk" "http://spyce.sf.net/" "" "" + CreateShortCut "$SMPROGRAMS\${NAME}\Spyce Examples.lnk" "$INSTDIR\docs\examples" "" "$INSTDIR\spyce.ico" + CreateShortCut "$SMPROGRAMS\${NAME}\Uninstall Spyce.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0 +SectionEnd + +Section "Create shell extensions" + WriteRegStr HKCR ".spy" "" "SpyceFile" + WriteRegStr HKCR "SpyceFile" "" "Spyce dynamic HTML file" + WriteRegStr HKCR "SpyceFile\DefaultIcon" "" $INSTDIR\spyce.ico,0 + WriteRegStr HKCR "SpyceFile\shell\open\command" "" 'notepad.exe "%1"' + WriteRegStr HKCR "SpyceFile\shell\compile" "" "Compile Spyce" + WriteRegStr HKCR "SpyceFile\shell\compile\command" "" '"$9" "$INSTDIR\run_spyceCmd.py" -O "%1"' + WriteRegStr HKCR "SpyceFile\shell" "" "compile" +SectionEnd + +Section "Configure Apache" + DetailPrint "Configuring Apache..." + ExecWait `"$9" "$INSTDIR\installHelper.py" "--apache=$INSTDIR"` + ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheRestart"` + MessageBox MB_OK|MB_ICONEXCLAMATION "$\nApache reconfigured and restarted.$\nIf everything is ok, you should be able to browse to: http://localhost/spyce/$\nIf not, please check your httpd.conf file and/or restart Apache." + +SectionEnd + + +Section "Uninstall" + ReadRegStr $9 HKLM ${REG_PYTHONLOC} "python" + DetailPrint "Unconfiguring Apache..." + ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheUN"` + ExecWait `"$9" "$INSTDIR\installHelper.py" "--apacheRestart"` + RMDir /r "$INSTDIR" + RMDir /r "$SMPROGRAMS\${NAME}" + DeleteRegKey HKLM ${REG_UNINST} + DeleteRegKey HKLM ${REG_PROG} + DeleteRegKey HKCR ".spy" + DeleteRegKey HKCR "SpyceFile" +SectionEnd + +;##################################### +;FUNCTIONS + +Function detectPython + ; see if there is any python interpreter + ClearErrors + ExecShell "open" "${PYTHON}" `-c "print 'Python is alive!'"` SW_SHOWMINIMIZED + IfErrors 0 NoAbort + MessageBox MB_OK|MB_ICONEXCLAMATION "Unable to find Python interpreter. Please install Python first." + Abort + NoAbort: + ; find out where it is + GetTempFileName $9 + GetTempFileName $8 + FileOpen $7 $9 w + FileWrite $7 'import sys$\n' + FileWrite $7 "f=open(r'$8', 'w')$\n" + FileWrite $7 'f.write(sys.executable)$\n' + FileWrite $7 'f.close()$\n' + FileClose $7 + ExecShell "open" "${PYTHON}" `"$9"` SW_SHOWMINIMIZED + IntOp $0 0 + 0 + Loop: + FileOpen $7 $8 r + FileRead $7 $6 + FileClose $7 + StrCmp $6 "" 0 EndLoop + Sleep 100 + IntOp $0 $0 + 1 + IntCmp $0 50 EndLoop + Goto Loop + EndLoop: + Delete $9 + StrCpy $9 "$6" ; put the python path in $9 -- GLOBAL + StrCmp $9 "" 0 NoAbort2 + MessageBox MB_OK|MB_ICONEXCLAMATION "Mechanism for discovering Python path via sys.executable did not work.$\nSorry, but automatic installation is unable to proceed. Please contact the author." + Abort + NoAbort2: + WriteRegStr HKLM ${REG_PYTHONLOC} "python" "$9" +FunctionEnd + +Function .onInit + Call detectPython +FunctionEnd + +Function .onInstSuccess + DetailPrint "Spyce successfully installed." + MessageBox MB_OK "Spyce successfully installed." + ExecShell "open" "$INSTDIR\docs\index.html" SW_SHOWMAXIMIZED +FunctionEnd + +Function un.onInit + MessageBox MB_YESNO|MB_ICONQUESTION "Are you sure that you want to uninstall Spyce?" IDYES NoAbort + Abort + NoAbort: +FunctionEnd + +Function un.onUninstSuccess + MessageBox MB_OK "Spyce successfully uninstalled." +FunctionEnd diff --git a/spyce-2.1/spyce.py b/spyce-2.1/spyce.py new file mode 100755 index 0000000000000000000000000000000000000000..5113f569444b3e2903f500019d9e63c5ae5cdf5c --- /dev/null +++ b/spyce-2.1/spyce.py @@ -0,0 +1,861 @@ +#!/usr/bin/env python + +__version__ = '2.1' +__release__ = '3' + +DEBUG_ERROR = False + +def DEBUG(s): + if DEBUG_ERROR: + sys.stderr.write('%s\n' % s) + +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to LICENCE for legalese +# +# Name: spyce +# Author: Rimon Barr +# Start date: 8 April 2002 +# Purpose: Python Server Pages +# WWW: http://spyce.sourceforge.net/ +################################################## + +# note: doc string used in documentation: doc/index.spy +__doc__ = ''' +Spyce is a server-side language that supports elegant and +efficient Python-based dynamic HTML generation. +Spyce allows embedding Python in pages similar to how JSP embeds Java, +but Spyce is far more than a JSP clone. Out of the box, Spyce provides +development as rapid as other modern frameworks like Rails, but with an +cohesive design rather than a morass of special cases. +''' + +import sys, os, copy, string, imp, Queue, time, traceback +import spyceCompile, spyceException +import spyceModule, spyceTag +import spyceLock, spyceCache, spyceUtil + +################################################## +# Spyce engine globals +# + +MAX_STACK = 100 +WRAPPER_LIMIT = 3 + +# spyceServer object - one per engine instance +SPYCE_SERVER = None +def getServer(config=None): + if not SPYCE_SERVER: + # constructor sets SPYCE_SERVER + if not config: + import spycePreload + config = spycePreload.getConfigModule() # guess + DEBUG('creating server with config from ' + config.__file__) + spyceServer(config) + return SPYCE_SERVER + +SPYCE_GLOBALS = None +def getServerGlobals(): + global SPYCE_GLOBALS + return SPYCE_GLOBALS + +SPYCE_LOADER = 'spyceLoader' +SPYCE_ENTRY = 'SPYCE_ENTRY' +DEFAULT_MODULES = ('request', 'response', 'stdout', 'error') + +# handler ids can change when tag is recompiled, so we need to invalidate +# dependant pages when a tag changes +tag_dependencies = {} + +################################################## +# Spyce core objects +# + +class spyceServerObject: + "serverObject placeholder" + pass + +class spyceServer: + "One per server, stored in SPYCE_SERVER (above) at processing of first request." + def __init__(self, config): + global SPYCE_SERVER, SPYCE_GLOBALS + # it's possible to call getServer recursively -- one way is from an import + # in spyceconf. This ensures all calls get the same object. + SPYCE_SERVER = self + # server object + self.serverobject = spyceServerObject() + + # http headers + try: self.entry = os.environ[SPYCE_ENTRY] + except: self.entry = 'UNKNOWN' + + self.config = config + global DEBUG_ERROR, SPYCE_GLOBALS + DEBUG_ERROR = self.config.debug + self.globals = self.config.globals + SPYCE_GLOBALS = self.globals # hack + + # spyce module search path + self.path = self.config.path + self.imports = self.config.imports + + # spyce module cache + self.module_cache = {} + self.module_coderefs = {} + # page error handler + pageerror = self.config.pageerrortemplate + if pageerror[0]=='string': + pageerror = pageerror[0], self.loadModule(pageerror[2], pageerror[1]+'.py') + self.pageerror = pageerror + # engine error handler + self.error = self.config.errorhandler + + # spyce thread-safe stdout object + if self.multithreaded(): + self.stdout = spyceUtil.ThreadedWriter(sys.stdout) + sys.stdout = self.stdout + else: + self.stdout = None + + # spyce compilation cache + raw_cache = self.config.cache + if raw_cache in ('file',): + raw_cache = spyceCache.fileCache(self.config.cachedir) + elif raw_cache in ('mem', 'memory'): + raw_cache = {} + else: + raise Exception('Unrecognized cache type ' + str(raw_cache)) + + # spyce_cache needs to lock to avoid potential multiple spyceCode objects + # for the same spyce file or string + # (or, potentially corrupt compiled code in the case of a fileCache) + cache_lock = self.createLock('spycecache') + + self.spyce_cache = spyceCache.semanticCache(raw_cache, spyceCacheValid, spyceCacheGenerate, cache_lock) + + # ensure that global tags at least import correctly + checker = spyceTag.spyceTagChecker(self) + for tagtuple in self.config.globaltags: + L = list(tagtuple) + L.append(None) # relfile for loadmodule + try: + checker.loadLib(*L) + except: + sys.stderr.write(spyceUtil.exceptionString() + '\n') + raise Exception('Error loading global taglib %s' % (tagtuple,)) + + # moved these here so imported modules can interact w/ getServer + for i in self.config.imports: + exec('import ' + i) + + def threaded(self): + return 'spyceWWW' in sys.modules + def multithreaded(self): + return self.threaded() and self.config.minthreads > 1 and self.config.maxthreads > 1 + + def createLock(self, lockname): + if self.threaded(): + if self.multithreaded(): + return spyceLock.threadLock() + else: + return spyceLock.dummyLock() + return spyceLock.fileLock(os.path.join(self.config.tmp, lockname)) + + def _findModule(self, name, file, rel_file): + """ + Find (and cache) the path a spyce module given by may be loaded from + ("file" argument does not include any path information) + rel_file is relevant because the "current directory" is always searched first. + """ + if not file: file=name+'.py' + key = name, file, rel_file + try: + pathkey = self.module_cache[key] + path = pathkey[0] + except KeyError: + pathkey = None # 1st-level cache miss + + if not pathkey: + if rel_file: + # don't use path.append; we don't want to modify it for everybody! + L = [os.path.dirname(rel_file)] + self.path + else: + L = self.path + for path in L: + f = None + path = os.path.realpath(os.path.join(path, file)) + if os.path.exists(path) and os.access(path, os.R_OK): + self.module_cache[key] = pathkey = (path, name) + break + if not pathkey: + raise ImportError('unable to find module "%s" in path %s' % (file, L)) + return pathkey + + def loadModule(self, name, file=None, rel_file=None): + """ + Find and load a spyce module, with caching. + (I.e., return the class inheriting spyceModule from the python module + in the given file -- the actual python module is not returned!) + + This is also used to load stuff that isn't actually a spyceModule -- + spyceTags and defaultErrorTemplate, for instance. + + Caching is performed + at the name/actual filename [not file parameter] level. + (This is done so that .spy files in different directories can ask + for tags in their directory w/o worrying about name conflicts.) + The 2nd level is the necessary one to avoid unnecessary reloads + (which can break things that only expect to be loaded once); + the first is just an optimization to avoid repeatedly walking the + spyce path looking for the right source file. + """ + pathkey = self._findModule(name, file, rel_file) + path = pathkey[0] + + try: + (mod, mtime) = self.module_cache[pathkey] + except KeyError: + DEBUG('cache miss for %s' % (pathkey,)) + else: + if not self.config.check_mtime or mtime >= spyceCacheGetmtime(path): + DEBUG('cache hit (%d) for %s in %s' % (mtime, name, path)) + return mod + for callback in tag_dependencies.get(path, []): + callback() + # else continue w/ (re)load + + def loadModuleHelper(p=path, name=name, pathkey=pathkey): + try: + f = open(p) + if (spyceUtil.isTagCollection(f)): + realname = spyceCompile.SPYCE_LIBNAME + code, coderefs, modrefs, tagrefs = \ + spyceCompile.spyceCompile(f.read(), p, '', getServer(), True) + def clear(): + del self.module_cache[pathkey] + for taglibsrc in tagrefs: + tag_dependencies.setdefault(taglibsrc, []).append(clear) + f.close() + f = os.tmpfile() + DEBUG('compiled tag library:\n%s' % code) + f.write(code) + f.flush() + f.seek(0) + else: + realname = name + + try: + imported = getattr(imp.load_source(SPYCE_LOADER, p, f), realname) + except: + sys.stderr.write('exception loading %s from %s:\n%s' % (name, p, spyceUtil.exceptionString())) + ex = sys.exc_info() + info = traceback.format_exception_only(ex[0], ex[1])[0][:-1] + try: + raise 'Error loading Spyce module %s -- %s' % (name, info) + except: + raise spyceException.spyceRuntimeException() + try: + imported.__file__ = p + except AttributeError: + pass # "module" being loaded was str or other __slots__ user + self.module_cache[(p, name)] = (imported, spyceCacheGetmtime(p)) + try: + self.module_coderefs[p] = coderefs + except NameError: + pass + return imported + finally: + if f: f.close() + + dict = {'loadModuleHelper': loadModuleHelper} + return loadModuleHelper() + def fileHandler(self, request, response, filename, sig='', args=None, kwargs=None): + return self.commonHandler(request, response, ('file', (filename, sig)), args, kwargs) + def stringHandler(self, request, response, code, sig='', args=None, kwargs=None): + return self.commonHandler(request, response, ('string', (code, sig)), args, kwargs) + def commonHandler(self, request, response, spyceInfo, args=None, kwargs=None): + "Handle a request. This method is threadsafe." + start = time.time() + try: + thespyce = None + theError = None + try: + spycecode = self.spyce_cache[spyceInfo] + DEBUG('elapsed to get spycecode: %s' % (time.time() - start)) + thespyce = spycecode.newWrapper() # does own locking + try: + thespyce.spyceInit(request, response) + DEBUG('elapsed to init wrapper: %s' % (time.time() - start)) + if args is None: args=[] + if kwargs is None: kwargs={} + parent_code = thespyce.spyceProcess(*args, **kwargs) + if parent_code: + return parent_code + except spyceException.spyceRuntimeException, theError: + DEBUG('caught RuntimeException') + pass + finally: + if DEBUG_ERROR and theError: + sys.stderr.write(spyceUtil.exceptionString() + '\n') + if thespyce: + thespyce.spyceDestroy(theError) + spycecode.returnWrapper(thespyce) + DEBUG('elapsed to finish: %s' % (time.time() - start)) + except spyceException.spyceDone: pass + except spyceException.spyceRedirect, e: + return spyceFileHandler(request, response, e.filename) + except KeyboardInterrupt: raise + except (spyceException.spyceNotFound, spyceException.spyceForbidden, + spyceException.spyceSyntaxError, spyceException.pythonSyntaxError, + SyntaxError), e: + DEBUG('sending %s to errorhandler' % e) + return self.error(self, request, response, e) + except SystemExit: pass + except: + errorString = spyceUtil.exceptionString() + try: + import cgi + response.clear() + response.write('
\n')
+        response.write('Unexpected exception: (please report!)\n')
+        response.write(cgi.escape(errorString))
+        response.write('\n
\n') + response.returncode = response.RETURN_OK + except: + sys.stderr.write(errorString+'\n') + return response.returncode + +class spyceRequest: + """Underlying Spyce request object. All implementations (CGI, Apache...) + should subclass and implement the methods marked 'not implemented'.""" + def __init__(self): + self._in = None + self._stack = [] + def read(self, limit=None): + if limit: + return self._in.read(limit) + else: + return self._in.read() + def readline(self, limit=None): + if limit: + return self._in.readline(limit) + else: + return self._in.readline() + def env(self, name=None): + raise 'not implemented' + def getHeader(self, type=None): + raise 'not implemented' + def getServerID(self): + raise 'not implemented' + +class spyceResponse: + """Underlying Spyce response object. All implementations (CGI, Apache...) + should subclass and implement the methods marked 'not implemented', and + also properly define the RETURN codes.""" + RETURN_CONTINUE = 100 + RETURN_SWITCHING_PROTOCOLS = 101 + RETURN_OK = 200 + RETURN_CREATED = 201 + RETURN_ACCEPTED = 202 + RETURN_NON_AUTHORITATIVE_INFORMATION = 203 + RETURN_NO_CONTENT = 204 + RETURN_RESET_CONTENT = 205 + RETURN_PARTIAL_CONTENT = 206 + RETURN_MULTIPLE_CHOICES = 300 + RETURN_MOVED_PERMANENTLY = 301 + RETURN_MOVED_TEMPORARILY = 302 + RETURN_SEE_OTHER = 303 + RETURN_NOT_MODIFIED = 304 + RETURN_USE_PROXY = 305 + RETURN_TEMPORARY_REDIRECT = 307 + RETURN_BAD_REQUEST = 400 + RETURN_UNAUTHORIZED = 401 + RETURN_PAYMENT_REQUIRED = 402 + RETURN_FORBIDDEN = 403 + RETURN_NOT_FOUND = 404 + RETURN_METHOD_NOT_ALLOWED = 405 + RETURN_NOT_ACCEPTABLE = 406 + RETURN_PROXY_AUTHENTICATION_REQUIRED = 407 + RETURN_REQUEST_TIMEOUT = 408 + RETURN_CONFLICT = 409 + RETURN_GONE = 410 + RETURN_LENGTH_REQUIRED = 411 + RETURN_PRECONDITION_FAILED = 412 + RETURN_REQUEST_ENTITY_TOO_LARGE = 413 + RETURN_REQUEST_URI_TOO_LONG = 414 + RETURN_UNSUPPORTED_MEDIA_TYPE = 415 + RETURN_REQUEST_RANGE_NOT_SATISFIABLE = 416 + RETURN_EXPECTATION_FAILED = 417 + RETURN_INTERNAL_SERVER_ERROR = 500 + RETURN_NOT_IMPLEMENTED = 501 + RETURN_BAD_GATEWAY = 502 + RETURN_SERVICE_UNAVAILABLE = 503 + RETURN_GATEWAY_TIMEOUT = 504 + RETURN_HTTP_VERSION_NOT_SUPPORTED = 505 + RETURN_CODE = { + RETURN_CONTINUE: 'CONTINUE', + RETURN_SWITCHING_PROTOCOLS: 'SWITCHING PROTOCOLS', + RETURN_OK: 'OK', + RETURN_CREATED: 'CREATED', + RETURN_ACCEPTED: 'ACCEPTED', + RETURN_NON_AUTHORITATIVE_INFORMATION: 'NON AUTHORITATIVE INFORMATION', + RETURN_NO_CONTENT: 'NO CONTENT', + RETURN_RESET_CONTENT: 'RESET CONTENT', + RETURN_PARTIAL_CONTENT: 'PARTIAL CONTENT', + RETURN_MULTIPLE_CHOICES: 'MULTIPLE CHOICES', + RETURN_MOVED_PERMANENTLY: 'MOVED PERMANENTLY', + RETURN_MOVED_TEMPORARILY: 'MOVED TEMPORARILY', + RETURN_SEE_OTHER: 'SEE OTHER', + RETURN_NOT_MODIFIED: 'NOT MODIFIED', + RETURN_USE_PROXY: 'USE PROXY', + RETURN_TEMPORARY_REDIRECT: 'TEMPORARY REDIRECT', + RETURN_BAD_REQUEST: 'BAD REQUEST', + RETURN_UNAUTHORIZED: 'UNAUTHORIZED', + RETURN_PAYMENT_REQUIRED: 'PAYMENT REQUIRED', + RETURN_FORBIDDEN: 'FORBIDDEN', + RETURN_NOT_FOUND: 'NOT FOUND', + RETURN_METHOD_NOT_ALLOWED: 'METHOD NOT ALLOWED', + RETURN_NOT_ACCEPTABLE: 'NOT ACCEPTABLE', + RETURN_PROXY_AUTHENTICATION_REQUIRED: 'PROXY AUTHENTICATION REQUIRED', + RETURN_REQUEST_TIMEOUT: 'REQUEST TIMEOUT', + RETURN_CONFLICT: 'CONFLICT', + RETURN_GONE: 'GONE', + RETURN_LENGTH_REQUIRED: 'LENGTH REQUIRED', + RETURN_PRECONDITION_FAILED: 'PRECONDITION FAILED', + RETURN_REQUEST_ENTITY_TOO_LARGE: 'REQUEST ENTITY TOO LARGE', + RETURN_REQUEST_URI_TOO_LONG: 'REQUEST URI TOO LONG', + RETURN_UNSUPPORTED_MEDIA_TYPE: 'UNSUPPORTED MEDIA TYPE', + RETURN_REQUEST_RANGE_NOT_SATISFIABLE: 'REQUEST RANGE NOT SATISFIABLE', + RETURN_EXPECTATION_FAILED: 'EXPECTATION FAILED', + RETURN_INTERNAL_SERVER_ERROR: 'INTERNAL SERVER ERROR', + RETURN_NOT_IMPLEMENTED: 'NOT IMPLEMENTED', + RETURN_BAD_GATEWAY: 'BAD GATEWAY', + RETURN_SERVICE_UNAVAILABLE: 'SERVICE UNAVAILABLE', + RETURN_GATEWAY_TIMEOUT: 'GATEWAY TIMEOUT', + RETURN_HTTP_VERSION_NOT_SUPPORTED: 'HTTP VERSION NOT SUPPORTED', + } + def __init__(self): + pass + def write(self, s): + raise 'not implemented' + def writeErr(self, s): + raise 'not implemented' + def close(self): + raise 'not implemented' + def clear(self): + raise 'not implemented' + def sendHeaders(self): + raise 'not implemented' + def clearHeaders(self): + raise 'not implemented' + def setContentType(self, content_type): + raise 'not implemented' + def setReturnCode(self, code): + raise 'not implemented' + def addHeader(self, type, data, replace=0): + raise 'not implemented' + def flush(self): + raise 'not implemented' + def unbuffer(self): + raise 'not implemented' + +class spyceCode: + '''Takes care of compiling the Spyce file, and generating a wrapper''' + def __init__(self, code, key, filename=None, sig=''): + # store variables + self._filename = filename + # generate code + self._code, self._coderefs, self._modrefs, tagrefs = \ + spyceCompile.spyceCompile(code, filename, sig, getServer()) + def clear(): + del getServer().spyce_cache[key] + for taglibsrc in tagrefs: + tag_dependencies.setdefault(taglibsrc, []).append(clear) + # wrapper instantiation is slow, so we keep a pool around + self._wrapperQueue = Queue.Queue() + # wrappers + def newWrapper(self): + """ + Get a wrapper for this code from queue, or make new one. + Threadsafe thanke to queue object. + """ + try: return self._wrapperQueue.get_nowait() + except Queue.Empty: pass + DEBUG('creating new wrapper for %s\n' % self._filename) + return spyceWrapper(self) + def returnWrapper(self, w): + """ + Return wrapper back to queue after use + (Mostly) Threadsafe thanke to queue object. + ("Mostly" because we could actually store more wrappers than "limit" + but this is not a problem worth introducing another lock to solve.) + """ + if self._wrapperQueue.qsize() < WRAPPER_LIMIT: + self._wrapperQueue.put(w) + # serialization -- used by spyceCache.fileCache + def __getstate__(self): + return self._filename, self._code, self._coderefs, self._modrefs + def __setstate__(self, state): + self._filename, self._code, self._coderefs, self._modrefs = state + # it's faster to recreate wrappers than to write them out/read back in + self._wrapperQueue = Queue.Queue() + # accessors + def getCode(self): + "Return processed Spyce (i.e. Python) code" + return self._code + def getFilename(self): + "Return source filename, if it exists" + return self._filename + def getCodeRefs(self): + "Return python-to-Spyce code line references" + return self._coderefs + def getModRefs(self): + "Return list of Spyce modules imported by Spyce code" + return self._modrefs + +class spyceWrapper: + """Wrapper object runs the entire show, bringing together the code, the + Spyce environment, the request and response objects and the modules. + This object is generated by a spyceCode object. The common Spyce handler + code calls the 'processing' functions. Module writers interact with this + object via the spyceModuleAPI calls. This is arguably the trickiest portion + of the Spyce so don't touch unless you know what you are doing.""" + def __init__(self, spycecode): + # store variables + self._spycecode = spycecode + # api object + self._api = self + # module tracking + self._modCache = {} + self._modstarted = [] + self._modules = {} + # insert compiled python code into the _codeenv context + self._codeenv = { + spyceCompile.SPYCE_WRAPPER: self._api, + 'db': getServer().config.db + } + try: exec self.getCode() in self._codeenv + except SyntaxError: raise spyceException.pythonSyntaxError(self) + # remember what freshly loaded context looked like, so we can + # re-use this wrapper for others requests to the same Spyce. + self._initialEnvKeys = self._codeenv.keys() + # request, response + self._response = self._request = None + self._responseCallback = {} + self._moduleCallback = {} + self._parent = None + def _startModule(self, name, file=None, asx=None, force=0): + "Initialise module for current request." + if asx==None: asx=name + if force or not self._codeenv.has_key(asx): + DEBUG(asx+'.load') + modclass = getServer().loadModule(name, file, self._spycecode.getFilename()) + mod = modclass(self._api) + self.setModule(asx, mod, 0) + DEBUG(asx+'.start') + mod.start() + self._modstarted.append((asx, mod)) + else: mod = self._codeenv[asx] + return mod + # spyce processing + def spyceInit(self, request, response): + "Initialise a Spyce for processing." + self._parent = None + self._request = request + self._response = response + for mod in DEFAULT_MODULES: + self._startModule(mod) + self._modstarteddefault = self._modstarted + self._modstarted = [] + for (modname, modfrom, modas) in self.getModRefs(): + self._startModule(modname, modfrom, modas, 1) + instance = self._codeenv[spyceCompile.SPYCE_CLASS]() + self.process = getattr(instance, spyceCompile.SPYCE_PROCESS_FUNC) + def spyceProcess(self, *args, **kwargs): + """ + Process current request, including recursing to parent tags; + returns parent return code, or None + """ + if self._hasParent(): + self.getModules()['stdout'].push() + try: + self.spyceProcessSingle(*args, **kwargs) + finally: + if self._hasParent(): + result = self.getModules()['stdout'].pop() + if self._hasParent(): + if self._parent: # may have been if-d out + if len(self._request._stack) >= MAX_STACK: + try: + # spoof a runtimeexception + raise 'Maximum stack depth exceeded! (infinite parent template loop?)' + except: + raise spyceException.spyceRuntimeException() + else: + err = self._finishUserModules() + if err: raise err + (src, childargs) = self._parent + childargs['_body'] = result + return spyceFileHandler(self._request, self._response, src, 'child', kwargs={'child': spyceUtil.attrdict(childargs)}) + else: + # could have had a parent, but didn't: write body to the "real" outstream + self._response.write(result) + return None + def spyceProcessSingle(self, *args, **kwargs): + "Process the current Spyce request; no parent tag processing" + path = sys.path + try: + # munge path so .spy can import from .py in current directory + if self._spycecode.getFilename(): + path = copy.copy(sys.path) + sys.path.append(os.path.dirname(self._spycecode.getFilename())) + dict = { '_spyce_process': self.process, + '_spyce_args': args, '_spyce_kwargs': kwargs, } + exec '_spyce_result = _spyce_process(*_spyce_args, **_spyce_kwargs)' in dict + return dict['_spyce_result'] + finally: + sys.path = path + def _finishUserModules(self, theError=None): + try: + self._modstarted.reverse() + for asx, mod in self._modstarted: + try: + DEBUG(asx+'.finish') + mod.finish(theError) + except spyceException.spyceDone: pass + except spyceException.spyceRedirect, e: + # We don't want to just return a new handler here because + # (a) we want to finish out the other modules first, + # (b) it's not a form of state management we want to encourage, + # (as a user, figuring out which module's redirect takes precedence could get tricky) + # (c) finish is designed for a module to cleanup after itself, not to + # silently affect the page it was on ("explicit is better than implicit") + try: + raise Exception("Modules may not perform internal redirects in their start/finish methods. (External redirects are permitted, if you really must do this.)") + except: + theError = spyceException.spyceRuntimeException(self._api) + except KeyboardInterrupt: raise + except SystemExit: pass + except: + # let error module show an error page when it finishes + theError = spyceException.spyceRuntimeException(self._api) + finally: + self._modstarted = [] + return theError + def spyceDestroy(self, theError=None): + "Cleanup after the request processing." + theError = self._finishUserModules(theError) or theError + finishError = None + try: + # default modules + self._modstarteddefault.reverse() + for asx, mod in self._modstarteddefault: + try: + DEBUG(asx+'.finish') + mod.finish(theError) + except: finishError = 1 + self._request = None + self._response = None + if finishError: raise + finally: + self.spyceCleanup() + DEBUG('finished destroy for %s' % str(self._spycecode._filename)) + def spyceCleanup(self): + "Sweep the Spyce environment." + self._modstarteddefault = [] + # done by _finishUserModules: self._modstarted = [] + self._modules = {} + # changes to existing keys stick around but other globals are nuked + # we'd reset back to a virgin dict entirely but that's too expensive + # -- in practice this is "good enough" + for e in self._codeenv.keys(): + if e not in self._initialEnvKeys: + del self._codeenv[e] + def _hasParent(self): + return self._codeenv[spyceCompile.SPYCE_CLASS]._has_parent + # API methods + def getStack(self): + "Return spyce call stack (includes, parent templates, internal redirects)" + return self._request._stack + def getFilename(self): + "Return filename of current Spyce" + return self._spycecode.getFilename() + def getCode(self): + "Return processed Spyce (i.e. Python) code" + return self._spycecode.getCode() + def getCodeRefs(self): + "Return python-to-Spyce code line references" + return self._spycecode.getCodeRefs() + def getModRefs(self): + "Return list of import references in Spyce code" + return self._spycecode.getModRefs() + def getServerObject(self): + "Return unique (per engine instance) server object" + return getServer().serverobject + def getServerGlobals(self): + "Return server configuration globals" + return getServer().globals + def getServerID(self): + "Return unique server identifier" + return self._request.getServerID() + def getPageError(self): + "Return default page error value" + return getServer().pageerror + def getRequest(self): + "Return internal request object" + return self._request + def getResponse(self): + "Return internal response object" + return self._response + def setResponse(self, o): + "Set internal response object" + self._response = o + for f in self._responseCallback.keys(): f() + def registerResponseCallback(self, f): + "Register a callback for when internal response changes" + self._responseCallback[f] = 1 + def unregisterResponseCallback(self, f): + "Unregister a callback for when internal response changes" + try: del self._responseCallback[f] + except KeyError: pass + def getModules(self): + "Return references to currently loaded modules" + return self._modules + def getModule(self, name, asx=None): + """Get module reference. The module is dynamically loaded and initialised + if it does not exist (ie. if it was not explicitly imported, but requested + by another module during processing)""" + return self._startModule(name, asx=asx) + def setModule(self, name, mod, notify=1): + "Add existing module (by reference) to Spyce namespace (used for includes)" + self._codeenv[name] = mod + self._modules[name] = mod + if notify: + for f in self._moduleCallback.keys(): + f() + def delModule(self, name, notify=1): + "Add existing module (by reference) to Spyce namespace (used for includes)" + del self._codeenv[name] + del self._modules[name] + if notify: + for f in self._moduleCallback.keys(): + f() + def getGlobals(self): + "Return the Spyce global namespace dictionary" + return self._codeenv + def registerModuleCallback(self, f): + "Register a callback for modules change" + self._moduleCallback[f] = 1 + def unregisterModuleCallback(self, f): + "Unregister a callback for modules change" + try: del self._moduleCallback[f] + except KeyError: pass + def spyceFile(self, file): + "Return a spyceCode object of a file" + return getServer().spyce_cache[('file', file)] + def spyceString(self, code): + "Return a spyceCode object of a string" + return getServer().spyce_cache[('string', code)] + def spyceModule(self, name, file=None, rel_file=None): + "Return Spyce module class" + return getServer().loadModule(name, file, rel_file) + def spyceTaglib(self, name, file=None, rel_file=None): + "Return Spyce taglib class" + return getServer().loadModule(name, file, rel_file) + def setStdout(self, out): + "Set the stdout stream (thread-safe)" + serverout = getServer().stdout + if serverout: serverout.setObject(out) + else: sys.stdout = out + def getStdout(self): + "Get the stdout stream (thread-safe)" + serverout = getServer().stdout + if serverout: return serverout.getObject() + else: return sys.stdout + +################################################## +# Spyce cache +# + +def spyceFileCacheValid(key, validity): + "Determine whether compiled Spyce is valid" + try: + filename, sig = key + except: + filename, sig = key, '' + if not os.path.exists(filename): + return 0 + if not os.access(filename, os.R_OK): + return 0 + return not getServer().config.check_mtime or spyceCacheGetmtime(filename) == validity + +def spyceFileCacheGenerate(key): + "Generate new Spyce wrapper (recompiles)." + try: filename, sig = key + except: filename, sig = key, '' + DEBUG('generating new spyceCode for %s' % filename) + # ensure file exists and we have permissions + if not os.path.exists(filename): + raise spyceException.spyceNotFound(filename) + if not os.access(filename, os.R_OK): + raise spyceException.spyceForbidden(filename) + # generate + mtime = spyceCacheGetmtime(filename) + f = None + try: + f = open(filename) + code = f.read() + finally: + if f: f.close() + s = spyceCode(code, key, filename=filename, sig=sig) + return mtime, s + +def spyceStringCacheValid(code, validity): + return 1 + +def spyceStringCacheGenerate(key): + try: + code, sig = key + except: + code, sig = key, '' + s = spyceCode(code, key, sig=sig) + return None, s + +def spyceCacheValid((type, key), validity): + return { + 'string': spyceStringCacheValid, + 'file': spyceFileCacheValid, + }[type](key, validity) + +def spyceCacheGenerate((type, key)): + return { + 'string': spyceStringCacheGenerate, + 'file': spyceFileCacheGenerate, + }[type](key) + +def spyceCacheGetmtime(fname): + while os.path.islink(fname): # chase links + fname = os.path.join(os.path.dirname(fname), os.readlink(fname)) + return os.path.getmtime(fname) + +################################################## +# Spyce common entry points +# + +def spyceFileHandler(request, response, filename, sig='', args=None, kwargs=None, config=None): + filename = os.path.realpath(filename) # normalize for cache's benefit + request._stack.append(filename) + return _spyceCommonHandler(request, response, ('file', (filename, sig)), args, kwargs, config) + +def spyceStringHandler(request, response, code, sig='', args=None, kwargs=None, config=None): + request._stack.append('string') + return _spyceCommonHandler(request, response, ('string', (code, sig)), args, kwargs, config) + +def _spyceCommonHandler(request, response, spyceInfo, args=None, kwargs=None, config=None): + return getServer(config).commonHandler(request, response, spyceInfo, args, kwargs) + +if __name__ == '__main__': + execfile(os.path.join(os.path.split(__file__)[0],'run_spyceCmd.py')) + diff --git a/spyce-2.1/spyce.pyc b/spyce-2.1/spyce.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fa4a451ce870d03850abb4173c4ae2af640e15b Binary files /dev/null and b/spyce-2.1/spyce.pyc differ diff --git a/spyce-2.1/spyce.spec.in b/spyce-2.1/spyce.spec.in new file mode 100755 index 0000000000000000000000000000000000000000..87e36c55e16b18b65a0c994de74e3a877afebc42 --- /dev/null +++ b/spyce-2.1/spyce.spec.in @@ -0,0 +1,73 @@ +%define name spyce +%define version __VERSION__ +%define release __RELEASE__ + +%define httpd_conf /etc/httpd/conf/httpd.conf +%define httpd_conf_beginline ### BEGIN SPYCE CONFIG MARKER +%define httpd_conf_endline ### END SPYCE CONFIG MARKER +%define html_dir /var/www/html + +Summary: SPYCE - Python Server Pages, Python-based HTML scripting engine +Name: %{name} +Version: %{version} +Release: %{release} +Group: System/Servers +Packager: Rimon Barr +URL: http://spyce.sourceforge.net +License: Refer to LICENCE.TXT +BuildArchitectures: noarch +BuildRoot: %{_builddir}/%{name}-root +Requires: python >= 2.2 +Requires: httpd >= 2.0 +Source0: %{name}-%{version}-%{release}.tar.gz + +%description +SPYCE is a server-side engine that supports simple and efficient Python-based +dynamic HTML generation. Those who like Python and are familiar with JSP, or +PHP, or ASP, should have a look at this engine. It allows one to generate +dynamic HTML content just as easily, using Python for the dynamic parts. Its +modular design makes it very flexible and extensible. It can also be used as a +command-line utility for HTML pre-processing. + +%prep +%setup -q + +%build +make all + +%install +rm -rf ${RPM_BUILD_ROOT} +make DESTDIR=${RPM_BUILD_ROOT} install + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root) +/usr/share/spyce + +%post +ln -sf /usr/share/spyce/run_spyceCmd.py /usr/bin/spyce +ln -sf /usr/share/spyce/docs /usr/share/doc/spyce +echo -n "Adding Spyce config to httpd.conf..." +cp %httpd_conf %httpd_conf.spyce-install.bak +sed -e "/%httpd_conf_beginline/,/%httpd_conf_endline/d" \ + < %httpd_conf.spyce-install.bak > %httpd_conf +echo "%httpd_conf_beginline" >> %httpd_conf +cat /usr/share/spyce/spyceApache.conf | sed -e "s+XXX+/usr/share/spyce+g" >> %httpd_conf +echo "%httpd_conf_endline" >> %httpd_conf +echo " done." +/usr/sbin/apachectl graceful + +%postun +if [ $1 == 0 ]; then + rm -f /usr/bin/spyce + rm -f /usr/share/doc/spyce + rm -f %html_dir/spyce + echo -n "Removing Spyce config from httpd.conf..." + cp %httpd_conf %httpd_conf.spyce-uninstall.bak + sed -e "/%httpd_conf_beginline/,/%httpd_conf_endline/d" \ + < %httpd_conf.spyce-uninstall.bak > %httpd_conf + echo " done." + /usr/sbin/apachectl graceful +fi diff --git a/spyce-2.1/spyceApache.conf b/spyce-2.1/spyceApache.conf new file mode 100755 index 0000000000000000000000000000000000000000..19a4857e86cf8a5e3ebace773bf225ba0a834edb --- /dev/null +++ b/spyce-2.1/spyceApache.conf @@ -0,0 +1,83 @@ + +# XXX = Spyce program directory + +# ################ +# Documentation alias + +# This section asks your web server to serve the Spyce documentation +# from http://localhost/spyce/. If you change your Spyce server root +# directory, you'll have to modify this appropriately as well as move +# www/docs from the distribution. + +Alias /spyce/ "XXX/www/" + + Options Indexes + AllowOverride None + Order allow,deny + Allow from all + + +################### +# Spyce via proxy (on port 8000) + +# This section direct Apache to process Spyce requests via +# a Spyce proxy server. +# NB: Remember to start the Spyce proxy server... +# spyce -l -p 8000 /document_root +# If you would like to run your server on another port, +# start the proxy on that port (using a modified spyceconf.py) +# and change the RewriteRules below accordingly. + + + RewriteEngine On + # all .spy requests are sent to Spyce + RewriteRule ^(.*\.spy) http://localhost:8000$1 [p] + # send .vuh requests to Spyce too so Apache doesn't display source + RewriteRule ^(.*\.vuh) http://localhost:8000$1 [p] + # if no file matching the request is found, also send to Spyce + # for .vuh or 404 handling + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*) http://localhost:8000$1 [p] + + +################### +# Spyce via cgi or fcgi + +# On a vanilla Apache installation this will be done via CGI, which is +# quite slow. If the FastCGI module is properly installed, that should +# automatically be used instead. + +# AddHandler spyce-cgi-handler spy +# Action spyce-cgi-handler "/spyce-cgi/run_spyceCGI.py" +# ScriptAlias /spyce-cgi/ "XXX/" +# +# +# # If mod_fastcgi not installed, we get plain cgi +# SetHandler fastcgi-script +# +# +# If FastCGI is installed, it will be picked up +# automatically. On Linux, you can also omit this section +# and use a dynamic fcgi server instead. +# +# FastCgiServer "XXX/run_spyceCGI.py" -port 7654 -initial-env FCGI_PORT=7654 +# +# On Windows ONLY, please uncomment the following line. +# ScriptInterpreterSource registry + +################### +# Spyce via mod_python + +# This section allows Spyce to be invoked via the mod_python, yet +# another alternative with decent performance. Comment the proxy +# section above entirely, and uncomment the following lines, if you +# choose to use this instead. (Note that the doubly commented lines, +# can remain commented depending on your configuration). + +# +# AddHandler python-program .spy +# PythonHandler run_spyceModpy::spyceMain +# PythonPath "sys.path+[r'XXX']" +# #PythonOption SPYCE_CONFIG "/mydir/spyceconf.py" +# #PythonOptimize On +# diff --git a/spyce-2.1/spyceCGI.py b/spyce-2.1/spyceCGI.py new file mode 100755 index 0000000000000000000000000000000000000000..07cff2b8d6d6c071aef55cb2925b485c13872bf2 --- /dev/null +++ b/spyce-2.1/spyceCGI.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +################################################## + +import os, sys +import spyceCmd, spyce, spyceUtil +import fcgi + +__doc__ = '''(F)CGI-based Spyce entry point.''' + +def findScriptFile(path): + origpath = path + while path and not path=='/': + if os.path.isfile(path): + return path + path = os.path.dirname(path) + return origpath + +def doSpyce( (stdin, stdout, stderr, environ) ): + if spyce.getServer().config.cgi_allow_only_redirect: + sys.argv = [ sys.argv[0] ] + if environ.get('REDIRECT_STATUS', '') != '200': + stdout.write("Content-type: text/plain\n\nForbidden") + return -1 + path = None + if len(sys.argv)<=1 or not os.path.isfile(sys.argv[1]): + try: path = findScriptFile(environ['PATH_TRANSLATED']) + except: pass + result = spyceCmd.spyceMain(cgimode=1, cgiscript=path, + stdout=stdout, stdin=stdin, stderr=stderr, environ=environ) + return result + +def main(): + cgi = fcgi.FCGI() + if cgi.socket: os.environ[spyce.SPYCE_ENTRY] = 'fcgi' + else: os.environ[spyce.SPYCE_ENTRY] = 'cgi' + while True: + more = cgi.accept() + if spyce.getServer().config.check_modules_and_restart: + L = spyceUtil.scan_modules() + if L: + os._exit(3) + if not more: + break + doSpyce(more) + cgi.finish() + +if __name__=='__main__': + if sys.platform == "win32": + import os, msvcrt + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + main() + diff --git a/spyce-2.1/spyceCache.py b/spyce-2.1/spyceCache.py new file mode 100755 index 0000000000000000000000000000000000000000..181798d7342720a841646645f18506654d09e45d --- /dev/null +++ b/spyce-2.1/spyceCache.py @@ -0,0 +1,165 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: spyceCache.py 1095 2006-08-09 22:28:10Z ellisj $ +################################################## + +import spyceLock, spyceUtil +import os, string +from md5 import md5 +try: from cPickle import dumps, loads +except: from pickle import dumps, loads + +__doc__ = '''Caching related functionality.''' + +# rimtodo: specify some sort of cache size limit + +################################################## +# Generic cache +# + +class cache: + "Generic cache" + def __getitem__(self, key): + raise 'not implemented' + def __setitem__(self, key, value): + raise 'not implemented' + def __delitem__(self, key): + raise 'not implemented' + def keys(self): + raise 'not implemented' + def has_key(self, key): + raise 'not implemented' + +################################################## +# Storage caches +# + +class memoryCache(cache): + "In-memory cache" + def __init__(self, infoStr=None): + self.cache = {} + self.info = infoStr + def __getitem__(self, key): + return self.cache[key] + def __setitem__(self, key, value): + self.cache[key]=value + def __delitem__(self, key): + del self.cache[key] + def keys(self): + return self.cache.keys() + def has_key(self, key): + return self.cache.has_key(key) + +class fileCache(cache): + "File-based cache" + def __init__(self, infoStr): + self._cachedir = string.strip(infoStr) + def _filename(self, key): + return os.path.join(self._cachedir, self._encodeKey(key)) + def __getitem__(self, key): + f = None + try: + try: + f = open(self._filename(key), 'rb') + return loads(f.read()) + finally: + if f: f.close() + except IOError: pass + except EOFError: pass + raise KeyError() + def __setitem__(self, key, value): + try: + if self[key]==value: return + except KeyError: pass + f = None + try: + f = open(self._filename(key), 'wb') + f.write(dumps(value, -1)) + finally: + if f: f.close() + def __delitem__(self, key): + filename = self._filename(key) + if os.path.exists(filename): + os.remove(filename) + def keys(keys): + raise 'not implemented' + def has_key(self, key): + try: + self[key] + return 1 + except KeyError: + return 0 + def _encodeKey(self, key): + return 'spyceCache-' + md5(str(key)).hexdigest() + + +################################################## +# Policy caches +# + +#rimtodo: + +################################################## +# Semantic cache +# + +class semanticCache(cache): + """Cache that knows how to validate and generate its own data. Note, that the + cache stores elements as (validity, data) tuples. The valid is a function + invoked as valid(key,validity), returning a boolean; and generate is a + function invoked as generate(key) returning (validity, data). The get() + method returns only the data.""" + def __init__(self, cache, valid, generate, lock=None): + self.valid = valid + self.generate = generate + self.cache = cache + if lock is None: + lock = spyceLock.dummyLock() + self.lock = lock + def get(self, key): + "Get (or generate) a cache element." + try: + self.lock.acquire() + except: + # must be a file lock, rlock won't raise + raise "couldn't lock %s -- did you set tmp to be a writable directory in your spyceconf?\n\nexact exception was %s" % (self.lock.name, spyceUtil.exceptionString()) + try: + if self.cache is None: + return self.generate(key)[1] + else: + if not key in self.cache or not self.valid(key, self.cache[key][0]): + self.cache[key] = self.generate(key) + return self.cache[key][1] + finally: + self.lock.release() + def purge(self, key): + "Remove a cache element, if it exists." + if self.cache.has_key(key): + del self.cache[key] + # standard dictionary methods + def __getitem__(self, key): + return self.get(key) + def __delitem__(self, key): + return self.purge(key) + def has_key(self, key): + if self.cache: + return self.cache.has_key() + else: + return 0 + def keys(self): + if self.cache: + return self.cache.keys() + else: + return [] + def values(self): + if self.cache: + return map(lambda x: x[1], self.cache.values()) + else: + return [] + def clear(self): + if self.cache: + self.cache.clear() + diff --git a/spyce-2.1/spyceCache.pyc b/spyce-2.1/spyceCache.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a3d7983b80c34e684cfd0b046ecc772f34c914e Binary files /dev/null and b/spyce-2.1/spyceCache.pyc differ diff --git a/spyce-2.1/spyceCmd.py b/spyce-2.1/spyceCmd.py new file mode 100755 index 0000000000000000000000000000000000000000..074f4e4d44f23afa13e920db072ff260ded6cbde --- /dev/null +++ b/spyce-2.1/spyceCmd.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python + +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: spyceCmd.py 1159 2006-08-16 23:33:19Z ellisj $ +################################################## + +import imp, getopt, sys, traceback, os, os.path, string, glob, copy +import spyce, spyceException, spyceUtil, spyceCompile, spycePreload + +__doc__ = '''Command-line and CGI-based Spyce entry point.''' + +################################################## +# Output +# + +# output version +def showVersion(out=sys.stdout): + "Emit version information." + out.write('Spyce %s\n' % spyce.__version__) + +# output syntax +def showUsage(out=sys.stdout): + "Emit command-line usage information." + showVersion(out) + out.write('Command-line usage:\n') + out.write(' spyce -c [-o filename.html] \n') + out.write(' spyce -w <-- CGI\n') + out.write(' spyce -O filename(s).spy <-- batch process\n') + out.write(' spyce -l [-d file ] <-- proxy server\n') + out.write(' spyce -h | -v\n') + out.write(' -h, -?, --help display this help information\n') + out.write(' -v, --version display version\n') + out.write(' -o, --output send output to given file\n') + out.write(' -O send outputs of multiple files to *.html\n') + out.write(' -c, --compile compile only; do not execute\n') + out.write(' -w, --web cgi mode: emit headers (or use run_spyceCGI.py)\n') + out.write(' -q, --query set QUERY_STRING environment variable\n') + out.write(' -l, --listen run in HTTP server mode\n') + out.write(' -d, --daemon run as a daemon process with given pidfile\n') + out.write(' --conf [file] Spyce configuration file\n') + out.write('To configure Apache, please refer to: spyceApache.conf\n') + out.write('For more details, refer to the documentation.\n') + out.write(' http://spyce.sourceforge.net\n') + out.write('Send comments, suggestions and bug reports to .\n') + +################################################## +# Request / Response handlers +# + +class spyceCmdlineRequest(spyce.spyceRequest): + 'CGI/Command-line Spyce request object. (see spyce.spyceRequest)' + def __init__(self, input, env, filename): + spyce.spyceRequest.__init__(self) + self._in = input + self._env = dict(env) + if not self._env.has_key('SERVER_SOFTWARE'): + self._env['SERVER_SOFTWARE'] = 'spyce %s Command-line' % spyce.__version__ + if not self._env.has_key('REQUEST_URI'): + self._env['REQUEST_URI']=filename + if not self._env.has_key('REQUEST_METHOD'): + self._env['REQUEST_METHOD']='spyce' + if not self._env.has_key('QUERY_STRING'): + self._env['QUERY_STRING']='' + self._headers = { + 'Content-Length': self.env('CONTENT_LENGTH'), + 'Content-Type': self.env('CONTENT_TYPE'), + 'User-Agent': self.env('HTTP_USER_AGENT'), + 'Accept': self.env('HTTP_ACCEPT'), + 'Accept-Encoding': self.env('HTTP_ACCEPT_ENCODING'), + 'Accept-Language': self.env('HTTP_ACCEPT_LANGUAGE'), + 'Accept-Charset': self.env('HTTP_ACCEPT_CHARSET'), + 'Cookie': self.env('HTTP_COOKIE'), + 'Referer': self.env('HTTP_REFERER'), + 'Host': self.env('HTTP_HOST'), + 'Connection': self.env('HTTP_CONNECTION'), + 'Keep-Alive': self.env('HTTP_KEEP_ALIVE'), + } + def env(self, name=None): + return spyceUtil.extractValue(self._env, name) + def getHeader(self, type=None): + return spyceUtil.extractValue(self._headers, type) + def getServerID(self): + return os.getpid() + +class spyceCmdlineResponse(spyce.spyceResponse): + 'CGI/Command-line Spyce response object. (see spyce.spyceResponse)' + def __init__(self, out, err, cgimode=0): + spyce.spyceResponse.__init__(self) + if not cgimode: + self.RETURN_OK = 0 + self.RETURN_CODE[self.RETURN_OK] = 'OK' + self.origout = out + self.out, self.err = spyceUtil.BufferedOutput(out), err + self.cgimode = cgimode + self.headers, self.headersSent = [], 0 + self.CT = None + self.returncode = self.RETURN_OK + # functions (for performance) + self.write = self.out.write + self.writeErr = self.err.write + self.clear = self.out.clear + def close(self): + self.flush() + self.out.close() + def sendHeaders(self): + if self.cgimode and not self.headersSent: + resultText = self.RETURN_CODE.get(self.returncode) + self.origout.write('Status: %3d "%s"\n' % (self.returncode, resultText)) + if not self.CT: self.setContentType('text/html') + self.origout.write('Content-Type: %s\n' % self.CT) + for h in self.headers: self.origout.write('%s: %s\n'%h) + self.origout.write('\n') + self.headersSent = 1 + def clearHeaders(self): + if self.headersSent: raise Exception, 'headers already sent' + self.headers = [] + def setContentType(self, content_type): + if self.headersSent: raise Exception, 'headers already sent' + self.CT = content_type + def setReturnCode(self, code): + if self.headersSent: raise Exception, 'headers already sent' + self.returncode = code + def addHeader(self, type, data, replace=0): + if self.headersSent: raise Exception, 'headers already sent' + if type=='Content-Type': self.setContentType(data) + else: + if replace: + self.headers = filter(lambda (type, _), t2=type: type!=t2, self.headers) + self.headers.append((type, data)) + def flush(self, stopFlag=0): + if stopFlag: return + self.sendHeaders() + self.out.flush() + def unbuffer(self): + self.sendHeaders() + self.out.unbuffer() + +################################################## +# Daemonizing +# + +def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null', pidfile=None): + '''Forks current process into a daemon. stdin, stdout, and stderr arguments + are file names that are opened and used in place of the standard file descriptors + in sys.stdin, sys.stdout, and sys.stderr, which default to /dev/null. + Note that stderr is unbuffered, so output may interleave with unexpected order + if shares destination with stdout.''' + def forkToChild(): + try: + if os.fork()>0: sys.exit(0) # exit parent. + except OSError, e: + sys.stderr.write("fork failed: (%d) %s\n" % (e.errno, e.strerror)) + sys.exit(1) + # First fork; decouple + forkToChild() + os.chdir("/") + os.umask(0) + os.setsid() + # Second fork; create pidfile; redirect descriptors + forkToChild() + pid = str(os.getpid()) + if pidfile: + f = open(pidfile,'w+') + f.write("%s\n" % pid) + f.close() + si = open(stdin, 'r') + so = open(stdout, 'a+') + se = open(stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + # I am a daemon! + +################################################## +# Command-line entry point +# + +#for debugging/profiling only +#sys.stdout = spyceUtil.NoCloseOut(sys.stdout) + +def spyceMain(cgimode=0, cgiscript=None, + stdout=sys.stdout, stdin=sys.stdin, stderr=sys.stderr, environ=os.environ): + "Command-line and CGI entry point." + # defaults + compileonlyMode = 0 + outputFilename = None + defaultOutputFilename = 0 + httpmode = 0 + daemon = None + configFile = None + # parse options + if cgimode and cgiscript: args = [cgiscript] + else: + try: + opts, args = getopt.getopt(sys.argv[1:], 'h?vco:Owq:ld:p:', + ['help', 'version', 'compile', 'output=', 'web', 'query=', 'listen', 'daemon=', 'conf=',]) + except getopt.error: + if cgimode: stdout.write('Content-Type: text/plain\n\n') + stdout.write('syntax: unknown switch used\n') + stdout.write('Use -h option for help.\n') + return -1 + for o, a in opts: + if o in ("-v", "--version"): showVersion(); return + if o in ("-h", "--help", "-?"): showUsage(); return + if o in ("-c", "--compileonly"): compileonlyMode = 1 + if o in ("-o", "--output"): outputFilename = a + if o in ("-O", ): defaultOutputFilename = 1 + if o in ("-w", "--web"): cgimode = 1 + if o in ("-q", "--query"): environ['QUERY_STRING'] = a + if o in ("-l", "--listen"): httpmode = 1 + if o in ("-d", "--daemon"): daemon = a + if o in ("--conf", ): configFile = a + + if compileonlyMode and httpmode: + stdout.write('compile mode (-c) is incompatible with http listen mode (-l)\n') + return -1 + if not configFile: configFile = spycePreload.defaultConfigFile() + # web server mode + if httpmode: + import spyceWWW + return spyceWWW.spyceHTTPserver(configFile, daemon=daemon) + configmod = spycePreload.getConfigModule(configFile) + # some checks + if not cgimode and not defaultOutputFilename and len(args)>1: + stdout.write('syntax: too many files to process\n') + stdout.write('Use -h option for help.\n') + return -1 + # file globbing + if defaultOutputFilename: + globbed = map(glob.glob, args) + args = [] + for g in globbed: + for f in g: args.append(f) + if not len(args): + if cgimode: stdout.write('Content-Type: text/plain\n\n') + stdout.write('syntax: please specify a spyce file to process\n') + stdout.write('Use -h option for help.\n') + return -1 + # run spyce + result=0 + try: + while len(args): + result, script = 0, args[0] + del args[0] + if cgimode: + dir = os.path.dirname(script) + if dir: + script = os.path.basename(script) + os.chdir(dir) + try: + output = stdout + if defaultOutputFilename: + outputFilename = os.path.splitext(script)[0]+'.html' + stdout.write('Processing: %s\n'%script) + stdout.flush() + if outputFilename: + output = open(outputFilename, 'w') + server = spyce.getServer(configmod) + if compileonlyMode: + f = file(script) + if spyceUtil.isTagCollection(f): + code, coderefs, modrefs = spyceCompile.spyceCompile(f.read(), script, '', server, True) + output.write(code) + else: + s = server.spyce_cache['file', script] + output.write(s.getCode()) + f.close() + output.write('\n') + else: + request = spyceCmdlineRequest(stdin, environ, script) + response = spyceCmdlineResponse(output, stderr, cgimode) + result = spyce.spyceFileHandler(request, response, script, config=configmod) + response.close() + except (SystemExit, KeyboardInterrupt): raise + except (spyceException.spyceForbidden, spyceException.spyceNotFound), e: + if cgimode: + stdout.write('Content-Type: text/plain\n\n') + stdout.write(str(e)+'\n') + except: + if cgimode: + stdout.write('Content-Type: text/plain\n\n') + stdout.write(spyceUtil.exceptionString()+'\n') + if output: + output.close() + except KeyboardInterrupt: stdout.write('Break!\n') + return result + +ABSPATH = 1 +if ABSPATH: + for i in range(len(sys.path)): + sys.path[i] = os.path.abspath(sys.path[i]) + ABSPATH = 0 + +# Command-line entry point +if __name__=='__main__': + os.environ[spyce.SPYCE_ENTRY] = 'cmd' + try: sys.exit(spyceMain(cgimode=0)) + except KeyboardInterrupt: print 'Break!' + except SystemExit: pass + except: + traceback.print_exc() + sys.exit(1) + diff --git a/spyce-2.1/spyceCmd.pyc b/spyce-2.1/spyceCmd.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75d6e02eb796505618976f035ba22fb64724ef66 Binary files /dev/null and b/spyce-2.1/spyceCmd.pyc differ diff --git a/spyce-2.1/spyceCompile.py b/spyce-2.1/spyceCompile.py new file mode 100755 index 0000000000000000000000000000000000000000..3c68e3d2fbd905ea867abe1622e3d87359f4d7ef --- /dev/null +++ b/spyce-2.1/spyceCompile.py @@ -0,0 +1,1638 @@ +################################################# +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +################################################## + +#rimtodo: +# - fix compaction (it assumed newlines parsed independently) + +import re # otherwise apache 2.0 pcre library conflicts + # we just can't win! either stack limits (sre), or + # library conflicts (pre)! :) + +from cStringIO import StringIO +import sys, string, token, tokenize, os, md5, base64 +import spyceTag, spyceUtil, spyce +from spyceException import spyceSyntaxError + +__doc__ = '''Compile Spyce files into Python code.''' + +################################################## +# Special method names +# + +# external interface of generated code +SPYCE_CLASS = 'spyceImpl' +SPYCE_PROCESS_FUNC = 'spyceProcess' +SPYCE_LIBNAME = 'spyceTagcollection' +SPYCE_WRAPPER = '_spyceWrapper' # for raising spyceRuntimeException + +# codepoints +GLOBAL_CODEPATH = ['global'] +CLASS_CODEPATH = [SPYCE_CLASS] +SPYCEPROCESS_CODEPATH = [SPYCE_PROCESS_FUNC] +# MODULES_CODEPATH is different depending on whether we are compiling a +# tagcollection, and will be created later +TAGLIB_CODEPATH = [SPYCE_PROCESS_FUNC, 'taglibs'] +LOGIN_CODEPATH = [SPYCE_PROCESS_FUNC, 'login'] +HANDLER_CODEPATH = [SPYCE_PROCESS_FUNC, 'handler'] + +################################################## +# Dos-to-Unix linebreaks +# + +# split a buffer into lines (regardless of terminators) +def splitLines(buf): + lines=[] + f=StringIO(buf) + l=f.readline() + while l: + while l and l[-1] in ['\r', '\n']: + l=l[:-1] + lines.append(l) + l=f.readline() + return lines + +# encode document with LF +def CRLF2LF(s): + return string.join(splitLines(s), '\n')+'\n' + +# encode document with CRLF +def LF2CRLF(s): + return string.join(splitLines(s), '\r\n')+'\r\n' + + +################################################## +# Tokens +# + +T_ESC = -2 +T_EOF = -1 +T_TEXT = 0 +T_EVAL = 1 +T_STMT = 2 +T_CHUNK = 3 +T_CHUNKC = 4 +T_DIRECT = 5 +T_LAMBDA = 6 +T_END = 7 +T_CMNT = 8 +T_END_CMNT = 9 + +TOKENS = ( + # in the order that they should be tested + # (i.e. usually longest first) + (T_ESC, r'\\\[\[', r'\\<%', r'\\\]\]', r'\\%>'), # escapes + (T_CHUNKC, r'\[\[!', r'<%!'), # open class chunk + (T_CHUNK, r'\[\[\\', r'<%\\'), # open chunk + (T_EVAL, r'\[\[=', r'<%='), # open eval + (T_DIRECT, r'\[\[\.', r'<%\.', r'<%@'), # open directive + (T_LAMBDA, r'\[\[spy', r'<%spy'), # open lambda + (T_CMNT, r'\[\[--', r'<%--'), # open comment + (T_END_CMNT, r'--\]\]', r'--%>'), # close comment + (T_STMT, r'\[\[', r'<%'), # open statement + (T_END, r'\]\]', r'%>'), # close +) + +def genTokensRE(tokens): + regexp = [] + typelookup = [None,] + for group in tokens: + type, matchstrings = group[0], group[1:] + for s in matchstrings: + regexp.append('(%s)' % s) + typelookup.append(type) + regexp = string.join(regexp, '|') + return re.compile(regexp, re.M), typelookup + +RE_TOKENS = None +TOKEN_TYPES = None +if not RE_TOKENS: + RE_TOKENS, TOKEN_TYPES = genTokensRE(TOKENS) + +def spyceTokenize(buf): + # scan using regexp + tokens = [] + buflen = len(buf) + pos = 0 + brow = bcol = erow = ecol = 0 + while pos < buflen: + m = RE_TOKENS.search(buf, pos) + try: + mstart, mend = m.start(), m.end() + other, token = buf[pos:mstart], buf[mstart:mend] + if other: + tokens.append((T_TEXT, other, pos, mstart)) + try: + type = TOKEN_TYPES[m.lastindex] + except AttributeError, e: + # Python 1.5 does not support lastindex + lastindex = 1 + for x in m.groups(): + if x: break + lastindex = lastindex + 1 + type = TOKEN_TYPES[lastindex] + if type==T_ESC: + token = token[1:] + type = T_TEXT + tokens.append((type, token, mstart, mend)) + pos = mend + except AttributeError, e: + # handle text before EOF... + other = buf[pos:] + if other: + tokens.append((T_TEXT, other, pos, buflen)) + pos = buflen + # compute row, col + brow, bcol = 1, 0 + tokens2 = [] + for type, text, begin, end in tokens: + lines = string.split(text[:-1], '\n') + numlines = len(lines) + erow = brow + numlines - 1 + ecol = bcol + if numlines>1: ecol = 0 + ecol = ecol + len(lines[-1]) + tokens2.append((type, text, (brow, bcol), (erow, ecol))) + if text[-1]=='\n': + brow = erow + 1 + bcol = 0 + else: + brow = erow + bcol = ecol + 1 + return tokens2 + + +def spyceTokenize4Parse(buf): + # add eof and reverse (so that you can pop() tokens) + tokens = spyceTokenize(buf) + try: + _, _, _, end = tokens[-1] + except: + end = 0; + tokens.append((T_EOF, '', end, end)) + tokens.reverse() + return tokens + +def processMagic(buf): + if buf[:2]=='#!': + buf = string.join(string.split(buf, '\n')[1:], '\n') + return buf + +################################################## +# Directives / Active Tags / Multi-line quotes +# + +DIRECTIVE_NAME = re.compile('[a-zA-Z][-a-zA-Z0-9_:]*') +DIRECTIVE_ATTR = re.compile( + r'\s*([a-zA-Z_][-.:a-zA-Z_0-9]*)(\s*=\s*' + r'(\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9./:;+*%?!&$\(\)_#=~]*))?') +def parseDirective(text): + "Parse a Spyce directive into name and an attribute list." + attrs = {} + match = DIRECTIVE_NAME.match(text) + if not match: return None, {} + name = string.lower(text[:match.end()]) + text = string.strip(text[match.end()+1:]) + while text: + match = DIRECTIVE_ATTR.match(text) + if not match: break + attrname, rest, attrvalue = match.group(1, 2, 3) + if not rest: attrvalue = None + elif attrvalue[:1] == "'" == attrvalue[-1:] or \ + attrvalue[:1] == '"' == attrvalue[-1:]: + attrvalue = attrvalue[1:-1] + attrs[string.lower(attrname)] = attrvalue + text = text[match.end()+1:] + return name, attrs + +RE_LIB_TAG = re.compile(r'''< # beginning of tag + (?P/?) # ending tag + (?P[a-zA-Z][-.a-zA-Z0-9_]*): # lib name + (?P[a-zA-Z][-.a-zA-Z0-9_]*) # tag name + (?P(?:\s+ # attributes + (?:[a-zA-Z_][-.:a-zA-Z0-9_]* # attribute name + (?:\s*=\s* # value indicator + (?:'[^']*' # LITA-enclosed value + |"[^"]*" # LIT-enclosed value + |[^'">\s]+ # bare value + ) + )? + ) + )*) + \s* # trailing whitespace + (?P/?) # single / unpaired tag + >''', re.VERBOSE) # end of tag +def calcEndPos(begin, str): + if not str: raise 'empty string' + beginrow, begincol = begin + eol = 0 + if str[-1]=='\n': + str = str[:-1]+' ' + eol = 1 + lines = string.split(str, '\n') + endrow = beginrow + len(lines)-1 + if endrow!=beginrow: + begincol = 0 + endcol = begincol + len(lines[-1]) - 1 + beginrow, begincol = endrow, endcol + 1 + if eol: + begincol = 0 + beginrow = beginrow + 1 + return (endrow, endcol), (beginrow, begincol) + +def removeMultiLineQuotes(s): + def findMultiLineQuote(s): + quotelist = [] + def eatToken(type, string, begin, end, _, quotelist=quotelist): + if type == token.STRING: + if string.startswith('r"""') or string.startswith("r'''") \ + or string.startswith('"""') or string.startswith("'''"): + quotelist.append((string, begin, end)) + tokenize.tokenize(StringIO(s).readline, eatToken) + return quotelist + def replaceRegionWithLine(s, begin, end, s2): + (beginrow, begincol), (endrow, endcol) = begin, end + beginrow, endrow = beginrow-1, endrow-1 + s = string.split(s, '\n') + s1, s3 = s[:beginrow], s[endrow+1:] + s2 = s[beginrow][:begincol] + s2 + s[endrow][endcol:] + return string.join(s1 + [s2] + s3, '\n') + match = findMultiLineQuote(s) + offsets = {} + for _, (obr, _), (oer, _) in match: + offsets[obr] = oer - obr + while match: + s2, begin, end = match[0] + s = replaceRegionWithLine(s, begin, end, `eval(s2)`) + match = findMultiLineQuote(s) + return s, offsets + +################################################## +# Pre-Python AST +# + +# ast node types +AST_PY = 0 +AST_PYEVAL = 1 +AST_TEXT = 2 +AST_COMPACT = 3 + +# compacting modes +COMPACT_OFF = 0 +COMPACT_LINE = 1 +COMPACT_SPACE = 2 +COMPACT_FULL = 3 + +class Codepoint: + def __init__(self, path): + self.elements = {} + self.fragments = [] + self.path = path + def add(self, code): + self.fragments.append(code) + if isinstance(code, Codepoint): + self.elements[code.path[-1]] = code + def __str__(self, depth=0): + L = [] + indent = '\t' * depth + L.append(indent + 'path: ' + str(self.path)) + for code in self.fragments: + if isinstance(code, Codepoint): + L.append(code.__str__(depth + 1)) + else: + L.append(indent + str(code)) + return '\n'.join(L) + +class Leaf: + def __init__(self, type, code, ref): + self.type = type + self.code = code + self.ref = ref + def __str__(self): + return str((self.type, self.code, self.ref)) + +class ppyAST: + "Generate a pre-Python AST" + def __init__(self): + "Initialise parser data structures, AST, token handlers, ..." + # set up ast + self._root = Codepoint([]) + self._activepoint = self._root + self._mods = [] + self._taglibs = {} + # routines to navigate AST + def mergeCode(self, codepath): + restore_point = self._activepoint + self.selectCodepath(codepath) + restore_point.fragments += self._activepoint.fragments + self._activepoint.fragments = [] + for key in self._activepoint.elements: + restore_point.elements[key] = self._activepoint.elements[key] + self._activepoint.elements = {} + self._activepoint = restore_point + def selectCodepath(self, codepath): + """ + codepath is a list of which key in the elements dict to follow. + selectCodepath starts at the top of the AST and follows this path down. + """ + self._activepoint = self._root + for point in codepath: + self._descendCodepath(point) + def getCodepath(self): + return self._activepoint.path + def _descendCodepath(self, codepoint): + try: + self._activepoint = self._activepoint.elements[codepoint] + except KeyError: + raise 'codepoint %s not found in elements %s of ast %s' % (codepoint, self._activepoint.elements, self._root) + # routines that modify the ast + def appendCodepoint(self, pointname, ref=None, descend=True): + new_path = list(self._activepoint.path) + new_path.append(pointname) + self._activepoint.add(Codepoint(new_path)) + if descend: + self._descendCodepath(pointname) + def addCode(self, code, ref=None, path=None): + if path is not None: + restore_point = self.getCodepath() + self.selectCodepath(path) + self._activepoint.add(Leaf(AST_PY, code, ref)) + if path is not None: + self.selectCodepath(restore_point) + def addEval(self, eval, ref=None): + self._activepoint.add(Leaf(AST_PYEVAL, eval, ref)) + def addCodeIndented(self, code, ref, classcode=0): + code, replacelist = removeMultiLineQuotes(code) + # funky hack: put in NULLs to preserve indentation + # NULLs don't appear in code, and the BraceConverter will + # turn them back into spaces. If we leave them as spaces, + # BraceConverter is just going to ignore them and pay attention + # only to the braces. (not the best compile-time performance!) + code = string.split(code, '\n') + code = map(lambda l: (len(l)-len(string.lstrip(l)), l), code) + code = map(lambda (indent, l): chr(0)*indent + l, code) + code.append('') + # split code lines + (brow, bcol), (erow, ecol), text, file = ref + row = brow + for l in code: + cbcol = 0 + cecol = len(l) + if row==brow: cbcol = bcol + if row==erow: cecol = ecol + try: row2 = row + replacelist[row-brow+1] + except: row2 = row + ref = (row, cbcol), (row2, cecol), l, file + if classcode: self.addCode(l, ref, CLASS_CODEPATH) + else: self.addCode(l, ref) + row = row2 + 1 + def addText(self, text, ref=None): + self._activepoint.add(Leaf(AST_TEXT, text, ref)) + def addCompact(self, compact, ref): + self._activepoint.add(Leaf(AST_COMPACT, compact, ref)) + def addModule(self, modname, modfrom, modas): + self._mods.append((modname, modfrom, modas)) + +################################################## +# Parse +# + +class spyceParse: + def initStandard(self, sig): + self._ast.appendCodepoint(GLOBAL_CODEPATH[0]) + self._ast.addCode('from spyceException import spyceDone, spyceRedirect, spyceRuntimeException, HandlerError') + self._ast.selectCodepath([]) + + # class codepoint, for class chunk when we start compiling + # todo: allow this in compiled libraries? + self._ast.appendCodepoint(SPYCE_CLASS) + self._ast.addCode('class %s: {' % (SPYCE_CLASS)) + # define spyceProcess + self._ast.selectCodepath([]) + self._ast.appendCodepoint(SPYCE_PROCESS_FUNC) + if sig: + sig = 'self, ' + sig + else: + sig = 'self' + self._ast.addCode('def %s(%s): {' % (SPYCE_PROCESS_FUNC, sig)) + self._ast.addCode('try:{') + + global MODULES_CODEPATH + MODULES_CODEPATH = [SPYCE_PROCESS_FUNC, 'spymod'] + self._ast.appendCodepoint(MODULES_CODEPATH[-1], descend=False) + + self._ast.appendCodepoint(LOGIN_CODEPATH[-1], descend=False) + + self._ast.appendCodepoint(HANDLER_CODEPATH[-1]) + self._ast.addCode("_handlers = {}") + self._ast.addCode("_forms_by_handler = {}") + self._ast.selectCodepath(SPYCEPROCESS_CODEPATH) + + # set up a point to add taglib load calls later + self._ast.appendCodepoint(TAGLIB_CODEPATH[-1], descend=False) + + def finishStandard(self): + # login stuff, must allow login_required to terminate execution before running handlers! + self._ast.selectCodepath(LOGIN_CODEPATH) + # munge login_required tag invocation to the end of this codepath, + # after the request.login_id stuff + old_login_fragments = self._ast._activepoint.fragments + self._ast._activepoint.fragments = [] + + if self.filename: + # (if no filename, it's a spylamba, and we don't need to re-munge request obj b/c + # it's passed in from a real page request) + self._ast.addCode("import _coreutil") + # login_id() is a callable so that there is no overhead if it's not needed. + # (w/o explicitly binding request in lambda you will get "unknown global 'request'" errors) + self._ast.addCode("request.login_id = lambda request=request, _coreutil=_coreutil: _coreutil.login_from_cookie(request)") + if self._login_possible: + self._ast.addCode("if _coreutil.login_pending(request):{") + self._ast.addCode(" if self._login_validator:{") + self._ast.addCode(" from spyceCompile import _evalWithImport") + self._ast.addCode(" _validator = _evalWithImport(self._login_validator)") + self._ast.addCode(" }else:{") + self._ast.addCode(" import spyceConfig") + self._ast.addCode(" _validator = spyceConfig.login_defaultvalidator") + self._ast.addCode(" }") + self._ast.addCode(" _login_id = _coreutil.login_perform(request, _validator)") + self._ast.addCode(" request.login_id = lambda _login_id=_login_id: _login_id") + self._ast.addCode("}") + if self._login_required: + self._ast.addCode("if not request.login_id():{") + # TODO fix hack of special-casing core taglib here (general case done by addLoadTaglibs) + self._ast.addCode("taglib.load('core','core.py','spy')") + self._ast._activepoint.fragments.extend(old_login_fragments) + self._ast.addCode("}") + + # handlers + if self._call_handlers: + self._ast.selectCodepath(HANDLER_CODEPATH) + + # call handlers on postback + self._ast.addCode("_validation_error = {}") + self._ast.addCode("for _key in request.getpost():{") + self._ast.addCode(" if _key.startswith('_submit'):{") + self._ast.addCode(" _handlerid = _key[7:]") + # don't assume handler is there -- tag in question could have + # been output by included file or parent, which will run + # its own copy of this code + self._ast.addCode(" if _handlerid in _handlers:{") + self._ast.addCode(" from spyceCompile import _evalWithImport") + self._ast.addCode(" for _handler in _handlers[_handlerid]:{") + # imports done in tag scope won't be visible here; make up for that w/autoimport here + self._ast.addCode(" _f = _evalWithImport(_handler, locals())") + self._ast.addCode(" from spyceCompile import _marshallArgs") + self._ast.addCode(" _args, _kwargs = _marshallArgs(request, _f)") + self._ast.addCode(" try:{") + self._ast.addCode(" _f(*_args, **_kwargs)") + self._ast.addCode(" } except HandlerError, _e:{") + self._ast.addCode(" _form_id = _forms_by_handler[_handlerid]") + self._ast.addCode(" _validation_error[_form_id] = _e") + self._ast.addCode(" }") + self._ast.addCode(" break") + self._ast.addCode("}}}}") + if spyce.DEBUG_ERROR: + self._ast.addCode("if '_handlerid' not in locals():{") + self._ast.addCode(" import spyce; spyce.DEBUG('(no active handlers to run for this form)')") + self._ast.addCode("}") + self._ast.selectCodepath(SPYCEPROCESS_CODEPATH) + + # spyceProcess post + self._ast.addCode('} except spyceDone: pass') + self._ast.addCode('except spyceRedirect: raise') + self._ast.addCode('except KeyboardInterrupt: raise') + self._ast.addCode('except:{ raise spyceRuntimeException(%s) }'%SPYCE_WRAPPER) + self._ast.addCode('}}') # matches spyceProcess/SPYCE_CLASS + + self._ast.addCode('_has_parent = %r' % self._has_parent, path=CLASS_CODEPATH) + self._ast.addCode('_login_validator = %r' % self._login_validator, path=CLASS_CODEPATH) + + if self._taglibs_used: + self._ast.addModule('taglib', None, None) + self._ast.selectCodepath(TAGLIB_CODEPATH) + self.addLoadTaglibs() + if self._load_spylambda: self._ast.addModule('spylambda', None, None) + + def initTaglib(self): + global MODULES_CODEPATH + MODULES_CODEPATH = ['spymod'] + self._ast.appendCodepoint(MODULES_CODEPATH[-1], descend=False) + pass + + def finishTaglib(self): + self._ast.addCode(''' +class %s(spyceTagLibrary): + tags = [%s] +''' % (SPYCE_LIBNAME, ','.join(self._tagsdefined))) + + def next_tagid(self): + self._tagcount += 1 + return self._tagidprefix + str(self._tagcount) + + def addLoadTaglibs(self): + for taglib in self._taglibs_used: + libname, libfrom = self._ast._taglibs[taglib] + self._tag_dependencies.append(libfrom) + self._ast.addCode('taglib.load(%s, %s, %s)'%(repr(libname), repr(libfrom), repr(taglib))) + + def __init__(self, server, buf, filename, gentaglib, sig): + try: + # initialization + self._current_form_id = None + self._brace_stack = [] + self._tagcount = 0 + self._server = server + self._tagChecker = spyceTag.spyceTagChecker(server) + self._load_spylambda = False + self._has_parent = False + self._login_required = False + self._login_possible = False + self._login_validator = False + self._call_handlers = False + self._taglibs_used = {} + self._tag_dependencies = [] + self._tagsdefined = [] + self._definingtag = spyceUtil.attrdict() + self._gentaglib = gentaglib + self._curdir, self._curfile = os.getcwd(), '' + self._tagidprefix = base64.encodestring(md5.md5(buf).digest())[:-1] + self.filename = filename # so request-munger can tell if this is a spylambda (and req is already munged) + if filename: + self._curdir, self._curfile = os.path.split(filename) + if not self._curdir: + self._curdir = os.getcwd() # TODO this is sorta broken; we don't chdir for each request + self._path = os.path.join(self._curdir, self._curfile) + # prime ast + self._ast = ppyAST() + for tag_tuple in server.config.globaltags: + path, name = server._findModule(tag_tuple[0], tag_tuple[1], None) + # a 2.0-style active tag may not call other tags in the same collection + if path == self._path: + continue + args = list(tag_tuple) + [None, None] + self.addTaglib(*args) + + if gentaglib: + self.initTaglib() + else: + self.initStandard(sig) + + # spyceProcess body + self._tokens = spyceTokenize4Parse(processMagic(buf)) + self._tokenType = None + self.popToken() + self.processSpyce() + + if self._brace_stack: + ref = self._brace_stack[-1] + raise spyceSyntaxError("unclosed opening brace '{'", ref) + + if gentaglib: + self.finishTaglib() + else: + self.finishStandard() + + # post processing + self._tagChecker.finish() + except spyceSyntaxError, e: + raise + if e.info: + begin, end, text, _ = e.info + e.info = begin, end, text, self._curfile + raise e + def addTaglib(self, libname, libfrom, libas, fullfile, ref): + if not libas: libas=libname + self._tagChecker.loadLib(libname, libfrom, libas, fullfile, ref) + self._ast._taglibs[libas] = libname, libfrom + def info(self): + return self._ast._root, self._ast._mods, self._tag_dependencies + def popToken(self): + if self._tokenType!=T_EOF: + self._tokenType, self._tokenText, self._tokenBegin, self._tokenEnd = self._tokens.pop() + + def processSpyce(self): + while self._tokenType!=T_EOF: + [ + self.processText, # T_TEXT + self.processEval, # T_EVAL + self.processStmt, # T_STMT + self.processChunk, # T_CHUNK + self.processClassChunk, # T_CHUNKC + self.processDirective, # T_DIRECT + self.processUnexpected, # T_LAMBDA + self.processUnexpected, # T_END + self.processComment, # T_CMNT + self.processUnexpected, # T_END_CMNT + ][self._tokenType]() + self.popToken() + def processComment(self): + # collect comment + self.popToken() + while self._tokenType not in [T_END_CMNT, T_EOF]: + self.popToken() + if self._tokenType==T_EOF: + self.processUnexpected() + def addText(self, text, ref): + if self._gentaglib and not self._definingtag: + s = re.sub(r'''\s''', '', text) + if s: + raise spyceSyntaxError('text found outside of tag definition in tagcollection: "%s"' % s, ref) + return # whitespace is OK, but we don't want to add it to the AST + self._ast.addText(text, ref) + def processText(self): + "Process HTML (possibly with some active tags)" + html, begin, end = self._tokenText, self._tokenBegin, self._tokenEnd + while True: + # TODO refactor this so can share code with include.spycecode + # something like a second-stage parse that separates text into + # PLAIN and TAG sections... + m = RE_LIB_TAG.search(html) + if not m: + break + spyce.DEBUG('active tag hit: %s' % html[m.start():m.end()]) + # emit text literal before tag start, if any + plain = html[:m.start()] + if plain: + plain_end, tag_begin = calcEndPos(begin, plain) + self.addText(plain, (begin, plain_end, '', self._curfile)) + else: tag_begin = begin + + tag = m.group(0) + tag_end, begin = calcEndPos(tag_begin, tag) + self.processActiveTag(tag, + not not m.group('end'), m.group('lib'), m.group('name'), + m.group('attrs'), not m.group('single'), + tag_begin, tag_end) + html = html[m.end():] + self.addText(html, (begin, end, '', self._curfile)) + def processActiveTag(self, tag, tagend, taglib, tagname, tagattrs, tagpair, begin, end): + """ + Process HTML and Spyce tags + tagend: true if tag starts with + """ + ref = (begin, end, tag, self._curfile) + if self._gentaglib and not self._definingtag: + raise spyceSyntaxError("active tag used outside of tag definition in tagcollection", ref) + # make sure prefix belongs to loaded taglibrary + if not self._ast._taglibs.has_key(taglib): + self.addText(tag, (begin, end, '', self._curfile)) + return + # parse process tag attributes + _, tagattrs = parseDirective('x '+tagattrs) + # get tag class + tagclass = self._tagChecker.getTagClass(self._ast._taglibs[taglib], tagname, ref) + codepath = None + if tagclass.__name__ == 'tag_parent': + self._has_parent = True + elif tagclass.__name__ == 'tag_login_required': + self._login_required = True + codepath = LOGIN_CODEPATH + elif tagclass.__name__ == 'form_form': + if not self._gentaglib: + self._current_form_id = self.next_tagid() + self._ast.addCode("_current_form_id = %r" % self._current_form_id, ref) + if tagclass.__name__ in ['tag_login', 'tag_login_required']: + self._login_possible = True + if 'validator' in tagattrs: + self._login_validator = tagattrs['validator'] + if self._gentaglib and self._login_possible: + raise spyceSyntaxError("login tags may not currently be used in user-defined Active Tags") + # syntax check + if not tagend: # start tag + self._tagChecker.startTag(self._ast._taglibs[taglib], + tagname, tagattrs, tagpair, ref) + else: # end tag + self._tagChecker.endTag(self._ast._taglibs[taglib], tagname, ref) + if tag in self._taglibs_used.setdefault(taglib, {}): + firstuse = False + else: + firstuse = True + self._taglibs_used[taglib][tag] = True + if not tagend or not tagpair: # open or singleton tag + if tagclass.handlers is None: + tagid = None + if 'handler' in tagattrs: + raise spyceSyntaxError('''handler cannot apply to this "%s:%s" tag; handlers may only be assigned to tags that have a 'handlers' list, such as the form library's submit tag''' % (taglib, tagname), ref) + else: + tagid = self.next_tagid() + allhandlers = {} + for (subid, subhandlers) in tagclass.handlers.items(): + fullid = tagid + subid + allhandlers[fullid] = subhandlers + if 'handler' in tagattrs: + if tagattrs['handler'].startswith('='): + raise spyceSyntaxError("handler attribute may not be evaluated at runtime (i.e., may not start with '=')", ref) + L = tagattrs['handler'].split(',') + if not allhandlers: + allhandlers[tagid] = [] + for (fullid, subhandlers) in allhandlers.items(): + allhandlers[fullid] = subhandlers + L + # tag doesn't have to do anything further to set up handler, + # but we'll pass the attr along so it can perform any validation it wants + # (e.g, don't allow form action + handler) + elif not allhandlers: + allhandlers[tagid] = [] + for (fullid, L) in allhandlers.items(): + if not L: + continue + if self._gentaglib: + self._definingtag.handlers[fullid] = L + else: + self._call_handlers = True + self._ast.addCode("_handlers[%r] = %r" % (fullid, L), path=HANDLER_CODEPATH) + self._ast.addCode("_forms_by_handler[%r] = %r" % (fullid, self._current_form_id), path=HANDLER_CODEPATH) + # end handler code + + if firstuse: + if tagclass.classcode: + cref = tagclass.classcode + self.addChunk(cref[2], cref, True) + + self._ast.addCode('taglib.tagPush(%s, %s, %s, locals(), %s, %s)' % ( + repr(taglib), repr(tagname), repr(tagid), repr(tagattrs), repr(tagpair)), ref, codepath) + self._ast.addCode('try: {', ref, codepath) + if tagclass.catches: + self._ast.addCode('try: {', ref, codepath) + if tagclass.conditional: + self._ast.addCode('if taglib.tagBegin(): {', ref, codepath) + else: + self._ast.addCode('taglib.tagBegin()', ref, codepath) + if tagclass.mustend: + self._ast.addCode('try: {', ref, codepath) + if tagclass.loops: + self._ast.addCode('while 1: {', ref, codepath) + # handle exports + if tagclass.exports: + # use _foo instead of __foo to avoid problems with name mangling + # -- since we're in a class, the assignment gets mangled, but the + # code in exec does not, yeilding a NameError. Doh! + self._ast.addCode('_tagexports = taglib.tagExport()', ref, codepath) + self._ast.addCode('for _tagkey in _tagexports: {' + + """exec("%s = _tagexports['%s']" % (_tagkey, _tagkey))""" + + '}', + ref, codepath) + if tagend or not tagpair: # close or singleton tag + if tagclass.loops: + self._ast.addCode('if not taglib.tagBody(): break }', ref, codepath) + else: + self._ast.addCode('taglib.tagBody()', ref, codepath) + if tagclass.mustend: + self._ast.addCode('} finally: taglib.tagEnd()', ref, codepath) + else: + self._ast.addCode('taglib.tagEnd()', ref, codepath) + if tagclass.conditional: + self._ast.addCode('}', ref, codepath) + if tagclass.catches: + self._ast.addCode('} except: taglib.tagCatch()', ref, codepath) + self._ast.addCode('} finally: taglib.tagPop()', ref, codepath) + def processEval(self): + # collect expression + begin = self._tokenBegin + self.popToken() + expr = '' + while self._tokenType not in [T_END, T_EOF]: + if self._tokenType==T_TEXT: + expr = expr + self._tokenText + elif self._tokenType==T_LAMBDA: + expr = expr + self.processLambda() + else: self.processUnexpected() + self.popToken() + expr = string.strip(expr) + if not expr: self.processUnexpected() + # add expression to ast + self._ast.addEval(expr, (begin, self._tokenEnd, '='+expr, self._curfile)) + def processStmt(self): + # collect statement + self.popToken() + beginrow, begincol = self._tokenBegin + stmt = '' + while self._tokenType not in [T_END, T_EOF]: + if self._tokenType==T_TEXT: + stmt = stmt + self._tokenText + elif self._tokenType==T_LAMBDA: + stmt = stmt + self.processLambda() + else: self.processUnexpected() + endrow, endcol = self._tokenEnd + self.popToken() + if not string.strip(stmt): self.processUnexpected() + # add statement to ast, row-by-row + currow = beginrow + lines = string.split(stmt, '\n') + for l in lines: + if currow==beginrow: curcolbegin = begincol + else: curcolbegin = 0 + if currow==endrow: curcolend = endcol + else: curcolend = len(l) + l = string.strip(l) + if l: + ref = ((currow, curcolbegin), (currow, curcolend), l, self._curfile) + def braceTokenEater(type, string, begin, end, line): + if type==token.OP: + if string == '{': + self._brace_stack.append(ref) + elif string == '}': + if not self._brace_stack: + raise spyceSyntaxError("extra close brace '}'", ref) + self._brace_stack.pop() + try: + tokenize.tokenize(StringIO(l).readline, braceTokenEater) + except tokenize.TokenError: + # eof before close brace found; this is expected + pass + self._ast.addCode(l, ref) + currow = currow + 1 + def processChunk(self, classChunk=0): + # collect chunk + self.popToken() + begin = self._tokenBegin + chunk = '' + while self._tokenType not in [T_END, T_EOF]: + if self._tokenType==T_TEXT: + chunk = chunk + self._tokenText + elif self._tokenType==T_LAMBDA: + chunk = chunk + self.processLambda() + else: self.processUnexpected() + end = self._tokenEnd + self.popToken() + ref = (begin, end, chunk.strip(), self._curfile) + if self._gentaglib: + if not self._definingtag: + raise spyceSyntaxError('tagcollection code chunks may only appear inside tag definitions', ref ) + if classChunk: + self._definingtag.classcode = ref + return + # add chunk block at ast + if chunk: + self.addChunk(chunk, ref, classChunk) + + def addChunk(self, chunk, ref, classChunk): + (begin, end, _, curfile) = ref + chunk = string.split(chunk, '\n') + # eliminate initial blank lines + brow, bcol = begin + while chunk and not string.strip(chunk[0]): + chunk = chunk[1:] + brow = brow + 1 + bcol = 0 + begin = brow, bcol + if not chunk: self.processUnexpected() + # outdent chunk based on first line + # note: modifies multi-line strings having more spaces than first line outdent + # by removing outdent number of spaces at the beginning of each line. + # -- difficult to deal with efficiently (without parsing python) so just + # don't do this! + outdent = len(chunk[0]) - len(string.lstrip(chunk[0])) + for i in range(len(chunk)): + if string.strip(chunk[i][:outdent]): + chunk[i] = ' '*outdent + chunk[i] + chunk = map(lambda l, outdent=outdent: l[outdent:], chunk) + chunk = string.join(chunk, '\n') + ref = (begin, end, chunk, curfile) + try: + self._ast.addCodeIndented(chunk, ref, classChunk) + except tokenize.TokenError, e: + # removeMultiLineQuotes raised + msg, _ = e + raise spyceSyntaxError(msg, ref) + + def processClassChunk(self): + self.processChunk(1) + + def processDirective(self): + # collect directive + begin = self._tokenBegin + self.popToken() + directive = '' + while self._tokenType not in [T_END, T_EOF]: + if self._tokenType==T_TEXT: + directive = directive + self._tokenText + else: self.processUnexpected() + end = self._tokenEnd + self.popToken() + directive = string.strip(directive) + if not directive: self.processUnexpected() + ref = (begin, end, directive, self._curfile) + # process directives + name, attrs = parseDirective(directive) + if name=='compact': + compact_mode = COMPACT_FULL + if attrs.has_key('mode'): + mode = string.lower(attrs['mode']) + if mode=='off': + compact_mode = COMPACT_OFF + elif mode=='line': + compact_mode = COMPACT_LINE + elif mode=='space': + compact_mode = COMPACT_SPACE + elif mode=='full': + compact_mode = COMPACT_FULL + else: + raise spyceSyntaxError('invalid compacting mode "%s" specified'%mode, ref) + self._ast.addCompact(compact_mode, (begin, end, '', self._curfile)) + elif name in ('module', 'import'): + if not attrs.has_key('name') and not attrs.has_key('names'): + raise spyceSyntaxError('name or names attribute required', ref) + if attrs.has_key('names'): + mod_names = filter(None, map(string.strip, string.split(attrs['names'],','))) + for mod_name in mod_names: + self._ast.addModule(mod_name, None, None) + self._ast.addCode('%s.init()'%mod_name, ref) + else: + mod_name = attrs['name'] + mod_from = attrs.get('from') + mod_as = attrs.get('as') + mod_args = attrs.get('args', '') + if mod_as: theName=mod_as + else: theName=mod_name + self._ast.addModule(mod_name, mod_from, mod_as) + self._ast.addCode('%s.init(%s)' % (theName, mod_args), ref, MODULES_CODEPATH) + elif name in ('taglib',): + if not attrs.has_key('name') and not attrs.has_key('from'): + raise spyceSyntaxError('at least one of {name, from} attributes required', ref) + taglib_name = attrs.get('name', SPYCE_LIBNAME) # ignored if a tagcollection + taglib_from = attrs.get('from') + taglib_as = attrs.get('as') + path, name = self._server._findModule(taglib_name, taglib_from, self._path) + if path == self._path: + raise spyceSyntaxError('Compiled active tag may not reference other tags in the same library', ref) + self.addTaglib(taglib_name, taglib_from, taglib_as, self._path, ref) + elif name == 'tagcollection': + if not self._gentaglib: + raise spyceSyntaxError('tagcollection directive may only be used in a dedicated tag collection file', ref) + elif name == 'begin': + if not self._gentaglib: + raise spyceSyntaxError('begin directive may only be used in a dedicated tag collection file', ref) + if self._definingtag: + raise spyceSyntaxError('cannot nest begin directives; expected end for "%s" first' % self._definingtag.name, ref) + if not attrs.has_key('name'): + raise spyceSyntaxError('name attribute required', ref) + name = attrs['name'] + self._definingtag.name = name + self._definingtag.attrs = [] + self._definingtag.handlers = {} + self._definingtag.exports = [] + self._definingtag.classcode = None + self._definingtag.buffer = 'buffers' in attrs and eval(attrs['buffers']) + self._tagsdefined.append(name) + self._ast.addCode('class %s(spyceTagPlus):{' % name, ref) + self._ast.addCode("name = '%s'" % name, ref) + self._ast.addCode('buffer = %s' % self._definingtag.buffer, ref) + if 'singleton' in attrs and eval(attrs['singleton']): + if self._definingtag.buffer: + raise spyceSyntaxError('Buffer option is exclusive with singleton option', ref) + self._ast.addCode('def syntax(self):{', ref) + self._ast.addCode('self.syntaxSingleOnly()}', ref) + else: + self._ast.addCode('def syntax(self):{', ref) + self._ast.addCode('self.syntaxPairOnly()}', ref) + self._definingtag.kwattrs = 'kwattrs' in attrs and eval(attrs['kwattrs']) + + self._ast.appendCodepoint(name + 'Begin', descend=False) + elif name == 'attr': + if not self._definingtag: + raise spyceSyntaxError('attr found, but no corresponding begin', ref) + if not attrs.has_key('name'): + raise spyceSyntaxError('name attribute required', ref) + name = attrs['name'] + if 'default' in attrs: + self._definingtag.attrs.append((name, attrs['default'])) + else: + self._definingtag.attrs.append(name) + elif name == 'export': + if not attrs.has_key('var'): + raise spyceSyntaxError('var attribute required', ref) + if self._definingtag.buffer: + raise spyceSyntaxError('buffering tags may not export variables', ref) + if 'as' in attrs: + exportas = attrs['as'] + else: + exportas = attrs['var'] + self._definingtag.exports.append((attrs['var'], exportas)) + elif name == 'end': + if not self._definingtag: + raise spyceSyntaxError('end found, but no corresponding begin', ref) + if self._load_spylambda: + self._ast.addModule('spylambda', None, None) + self._load_spylambda = False + def attrsort(a, b): + return cmp(isinstance(a, tuple), isinstance(b, tuple)) + self._definingtag.attrs.sort(attrsort) + L = [] + for attr in self._definingtag.attrs: + if attr[0][0] != attr[0]: # not a string, must be a tuple + L.append("%s='%s'" % attr) + else: + L.append(attr) + if self._definingtag.kwattrs: + L.append('**kwargs') + s = ','.join(L) + if s: + s = ',' + s + self._ast.selectCodepath([self._definingtag.name + 'Begin']) + # always emit begin so tagchecker can veryify attrs + self._ast.addCode('def begin(self%s):{' % s, ref) + if self._definingtag.buffer: + self._ast.addCode('pass}', ref) + self._ast.addCode('def body(self, _content):{', ref) + for attr in self._definingtag.attrs: + if attr[0][0] != attr[0]: # not a string, must be a tuple + attr, default = attr + self._ast.addCode("try:{", ref) + self._ast.addCode(" %s = self._attrs['%s']" % (attr, attr), ref) + self._ast.addCode("} except KeyError: {", ref) + self._ast.addCode(" %s = '%s'" % (attr, default), ref) + self._ast.addCode("}", ref) + else: + self._ast.addCode("%s = self._attrs['%s']" % (attr, attr), ref) + for modname in spyce.DEFAULT_MODULES: + self._ast.addCode("%s = self._api.getModule('%s')" % (modname, modname)) + for modname, modfrom, modas in self._ast._mods: + self._ast.addCode('%s = self._api._startModule(%s, %s, %s)' % ( + modname, repr(modname), repr(modfrom), repr(modas)), ref) + self._ast.mergeCode(MODULES_CODEPATH) + self._ast._mods = [] + if self._taglibs_used: + self._ast.addCode("taglib = self._api.getModules()['taglib']", ref) + self.addLoadTaglibs() + self._taglibs_used = {} + if self._definingtag.handlers: + self._ast.addCode("if not self.getParent('form'):{", ref) + self._ast.addCode(" raise 'active handlers may not be used without a parent form active tag, i.e., f:form'}", ref) + + self._ast.selectCodepath([]) + # record exported values for export() to access + for (var, exportas) in self._definingtag.exports: + self._ast.addCode("self.%s = %s" % (var, var), ref) + self._ast.addCode('}', ref) + if self._definingtag.exports: + self._ast.addCode("exports = 1", ref) + self._ast.addCode("def export(self):{", ref) + L = [] + for (var, exportas) in self._definingtag.exports: + L.append("'%s': self.%s" % (exportas, var)) + self._ast.addCode("return {%s}" % ','.join(L)) + self._ast.addCode('}', ref) + # class code chunk, if any + if self._definingtag.classcode: + self._ast.addCode("classcode = %s" % repr(self._definingtag.classcode), ref) + # handlers + self._ast.addCode("handlers = %s" % repr(self._definingtag.handlers), ref) + self._ast.addCode('}', ref) + self._definingtag = spyceUtil.attrdict() + elif name=='include': + # deprecated (undocumented) post-1.3 + if not attrs.has_key('file'): + raise spyceSyntaxError('file attribute missing', ref) + filename = spyceUtil.url2file(attrs['file'], os.path.join(self._curdir, self._curfile)) + f = None + try: + try: + f = open(filename) + buf = f.read() + finally: + if f: f.close() + except KeyboardInterrupt: raise + except: + raise spyceSyntaxError('unable to open included file: %s'%filename, ref) + prev = (self._curdir, self._curfile, self._tokens, + self._tokenType, self._tokenText, self._tokenBegin, self._tokenEnd) + self._curdir, self._curfile = os.path.dirname(filename), filename + self._tokens = spyceTokenize4Parse(processMagic(buf)) + self.popToken() + self.processSpyce() + (self._curdir, self._curfile, self._tokens, + self._tokenType, self._tokenText, self._tokenBegin, self._tokenEnd) = prev + else: + raise spyceSyntaxError('invalid spyce directive', ref) + def processLambda(self): + # collect lambda + self.popToken() + begin = self._tokenBegin + lamb = '' + depth = 1 + while self._tokenType!=T_EOF: + if self._tokenType in [T_END,]: + depth = depth - 1 + if not depth: break + lamb = lamb + self._tokenText + elif self._tokenType in [T_EVAL, T_STMT, T_CHUNK, T_CHUNKC, T_DIRECT, T_LAMBDA]: + depth = depth + 1 + lamb = lamb + self._tokenText + elif self._tokenType==T_CMNT: + self.processComment() + else: + lamb = lamb + self._tokenText + end = self._tokenEnd + self.popToken() + # process lambda + lamb = string.split(lamb, ':') + try: + params = lamb[0] + memoize = 0 + if params and params[0]=='!': + params = params[1:] + memoize = 1 + lamb = string.join(lamb[1:],':') + except: + raise spyceSyntaxError('invalid spyce lambda', (begin, end, lamb, self._curfile)) + self._load_spylambda = True + lamb = 'spylambda.define(%s,%s,%d)' % (`string.strip(params)`, `lamb`, memoize) + return lamb + def processUnexpected(self): + raise spyceSyntaxError('unexpected token: "%s"'%self._tokenText, + (self._tokenBegin, self._tokenEnd, self._tokenText, self._curfile)) + +################################################## +# Peep-hole optimizer +# +class spyceOptimize: + def __init__(self, ast): + self.compaction(ast) + self.sideBySideWrites(ast) + #self.splitCodeLines(ast) + def splitCodeLines(self, ast): + nodes = ast.fragments + i = 0 + while i < len(nodes): + row = 1 + if isinstance(nodes[i], Codepoint): + self.splitCodeLines(nodes[i]) + elif nodes[i].type == AST_PY and nodes[i].ref: + code = nodes[i].code + (brow, bcol), (erow, ecol), code, file = nodes[i].ref + lines = string.split(code, '\n') + if code==text and len(lines)>1: + del nodes[i] + row = brow + for l in lines: + cbcol = 0 + cecol = len(l) + if row==brow: cbcol = bcol + if row==erow: becol = ecol + nodes.insert(i+(brow-row), Leaf(AST_PY, l, ((row, cbcol), (row, cecol), l, file))) + row = row + 1 + i = i + row + + def sideBySideWrites(self, ast): + nodes = ast.fragments + i = 0 + while i < len(nodes): + if isinstance(nodes[i], Codepoint): + self.sideBySideWrites(nodes[i]) + elif i + 1 < len(nodes) and isinstance(nodes[i + 1], Leaf): + type1, text1, ref1 = (nodes[i].type, nodes[i].code, nodes[i].ref) + type2, text2, ref2 = (nodes[i + 1].type, nodes[i + 1].code, nodes[i + 1].ref) + file1 = None + file2 = None + if ref1: + _, _, _, file1 = ref1 + if ref2: + _, _, _, file2 = ref2 + if type1==AST_TEXT and type2==AST_TEXT and file1==file2: + text = text1 + text2 + begin, _, orig, _ = ref1 + _, end, _, _ = ref2 + nodes[i] = Leaf(AST_TEXT, text, (begin, end, orig, file1)) + del nodes[i+1] + i -= 1 + i += 1 + + def compaction(self, ast): + nodes = ast.fragments + compact = COMPACT_LINE + i = 0 + while i < len(nodes): + if isinstance(nodes[i], Codepoint): + self.compaction(nodes[i]) + else: + type, text, ref = (nodes[i].type, nodes[i].code, nodes[i].ref) + if type==AST_COMPACT: + compact = text + elif type==AST_TEXT: + # line compaction + if compact==COMPACT_LINE or compact==COMPACT_FULL: + # remove any trailing whitespace + text = string.split(text, '\n') + for j in range(len(text)-1): + text[j] = string.rstrip(text[j]) + text = string.join(text, '\n') + # gobble the end of the line + ((row, _), _, _, file) = ref + rowtext = string.split(text, '\n') + if rowtext: rowtext = string.strip(rowtext[0]) + crow = row ; cfile = file + j = i - 1 + while j > 0 and not rowtext and isinstance(nodes[j], Leaf): + type2, text2, ref2 = (nodes[j].type, nodes[j].code, nodes[j].ref) + if ref2: (_, (crow, _), _, cfile) = ref2 + if crow != row or file != cfile: break + if type2 == AST_TEXT: + text2 = string.split(text2, '\n') + if text2: text2 = text2[-1] + rowtext += string.strip(text2) + elif type2 == AST_PYEVAL: + rowtext = 'x' + j -= 1 + if not rowtext: + text = string.split(text, '\n') + if text and not string.strip(text[0]): + text = text[1:] + text = string.join(text, '\n') + # gobble beginning of the line + (_, (row, _), _, file) = ref + rowtext = string.split(text, '\n') + if rowtext: rowtext = string.strip(rowtext[-1]) + crow = row ; cfile = file + j = i + 1 + while j < len(nodes) and not rowtext and isinstance(nodes[j], Leaf): + type2, text2, ref2 = (nodes[j].type, nodes[j].code, nodes[j].ref) + if ref2: ((crow, _), _, _, cfile) = ref2 + if crow != row or file != cfile: break + if type2 == AST_TEXT: + text2 = string.split(text2, '\n') + if text2: text2 = text2[0] + rowtext += string.strip(text2) + elif type2 == AST_PYEVAL: + rowtext = 'x' + j += 1 + if not rowtext: + text = string.split(text, '\n') + if text: text[-1] = string.strip(text[-1]) + text = string.join(text, '\n') + # space compaction + if compact==COMPACT_SPACE or compact==COMPACT_FULL: + text = spyceUtil.spaceCompact(text) + # update text, if any + if text: nodes[i] = Leaf(type, text, ref) + else: + del nodes[i] + i -= 1 + elif type in [AST_PY, AST_PYEVAL, None]: + pass + else: + raise 'error: unknown AST node type' + i = i + 1 + +################################################## +# Output classes +# + +class LineWriter: + "Output class that counts lines written." + def __init__(self, f, initialLine = 1): + self.f = f + self.lineno = initialLine + def write(self, s): + self.f.write(s) + self.lineno = self.lineno + len(string.split(s,'\n'))-1 + def writeln(self, s): + self.f.write(s+'\n') + def close(self): + self.f.close() + +class IndentingWriter: + "Output class that helps with indentation of code." + # Note: this writer is line-oriented. + def __init__(self, f, indentSize=2): + self._f = f + self._indentSize = indentSize + self._indent = 0 + self._indentString = ' '*(self._indent*self._indentSize) + self._currentLine = '' + def close(self): + if self._indent > 0: + raise 'unmatched open brace' + self._f.close() + def indent(self): + self._indent = self._indent + 1 + self._indentString = ' '*(self._indent*self._indentSize) + def outdent(self): + self._indent = self._indent - 1 + if self._indent<0: + raise 'unmatched close brace' + self._indentString = ' '*(self._indent*self._indentSize) + def dumpLine(self, s): + self._f.write(self._indentString+s+'\n') + def write(self, s): + self._currentLine = self._currentLine + s + lines = string.split(self._currentLine, '\n') + for l in lines[:-1]: + self.dumpLine(l) + self._currentLine=lines[-1] + def writeln(self, s=''): + self.write(s+'\n') + # remaining methods are defined in terms of writeln(), indent(), outdent() + def pln(self, s=''): + self.writeln(s) + def pIln(self, s=''): + self.indent(); self.pln(s) + def plnI(self, s=''): + self.pln(s); self.indent() + def pOln(self, s=''): + self.outdent(); self.pln(s) + def plnO(self, s=''): + self.pln(s); self.outdent() + def pOlnI(self, s=''): + self.outdent(); self.pln(s); self.indent() + def pIlnO(self, s=''): + self.indent(); self.pln(s); self.outdent() + +################################################## +# Print out Braced Python +# + +class emitBracedPython: + def __init__(self, out, ast, gentaglib): + self._gentaglib = gentaglib + out = LineWriter(out) + self._spyceRefs = {} + # text compaction + self.compact = COMPACT_LINE + self._gobblelineNumber = 1 + self._gobblelineText = '' + # do the deed! + self.emitSpyceRec(out, ast) + def getSpyceRefs(self): + return self._spyceRefs + def emitSpyceRec(self, out, ast): + nodes = ast.fragments + if self._gentaglib: + outstr = 'self._out' + else: + outstr = 'response' + for code in nodes: + if isinstance(code, Codepoint): + self.emitSpyceRec(out, code) + continue + type, text, ref = (code.type, code.code, code.ref) + line1 = out.lineno + if type==AST_TEXT: + out.write('%s.writeStatic(%s)\n' % (outstr, `text`)) + elif type==AST_PY: + out.write(text+'\n') + elif type==AST_PYEVAL: + out.write('%s.writeExpr(%s)\n' % (outstr, text)) + elif type==AST_COMPACT: + self.compact = text + else: + raise 'error: unknown AST node type' + line2 = out.lineno + if ref: + for l in range(line1, line2): + self._spyceRefs[l] = ref + if not nodes and not ast.elements: + out.write('pass\n') + +################################################## +# Print out regular Python +# + +class BraceConverter: + "Convert Python with braces into indented (normal) Python code." + def __init__(self, out): + self.out = IndentingWriter(out) + self.prevname = 0 + self.prevstring = 0 + self.dictlevel = 0 + def emitToken(self, type, string): + if type==token.NAME: + if self.prevname: self.out.write(' ') + if self.prevstring: self.out.write(' ') + self.out.write(string) + elif type==token.STRING: + if self.prevname: self.out.write(' ') + string = `eval(string)` # get rid of multi-line strings + self.out.write(string) + elif type==token.NUMBER: + if self.prevname: self.out.write(' ') + self.out.write(string) + elif type==token.OP: + if string=='{': + if self.prevcolon and not self.dictlevel: + self.out.plnI() + else: + self.dictlevel = self.dictlevel + 1 + self.out.write(string) + elif string=='}': + if not self.dictlevel: + self.out.plnO() + else: + self.dictlevel = self.dictlevel - 1 + self.out.write(string) + else: + self.out.write(string) + elif type==token.ERRORTOKEN and string==chr(0): + self.out.write(' ') + else: + self.out.write(string) + self.prevname = type==token.NAME + self.prevstring = type==token.STRING + self.prevcolon = type==token.OP and string==':' + +def emitPython(out, bracedPythonCode, spyceRefs): + out = LineWriter(out) + spyceRefs2 = {} + braceConv = BraceConverter(out) + def eatToken(type, string, begin, end, _, out=out, braceConv=braceConv, spyceRefs=spyceRefs, spyceRefs2=spyceRefs2): + try: + beginrow, _ = begin + line1 = out.lineno + try: + braceConv.emitToken(type, string) + except: + raise spyceSyntaxError('emitToken %s: %s' % (string, spyceUtil.exceptionString())) + line2 = out.lineno + if spyceRefs.has_key(beginrow): + for l in range(line1, line2): + spyceRefs2[l] = spyceRefs[beginrow] + except: + raise spyceSyntaxError('eatToken: %s' % spyceUtil.exceptionString()) + try: + tokenize.tokenize(StringIO(bracedPythonCode).readline, eatToken) + except tokenize.TokenError, e: + msg, (row, col) = e + raise spyceSyntaxError('tokenization error "%s" at (%d, %d) in\n%s' + % (msg, row, col, bracedPythonCode)) + return spyceRefs2 + +def calcRowCol(str, pos): + lines = string.split(str, '\n') + row = 1 + while pos > len(lines[0]): + pos = pos - len(lines[0]) - 1 + del lines[0] + row = row + 1 + return row, pos + +_bool_values = { + 'true': True, + 'false': False, + 't': True, + 'f': False, + 'yes': True, + 'no': False, + 'on': True, + 'off': False, + } +def _convertArg(v, convert_to): + if convert_to == 'int': + try: + v = int(v) + except ValueError: + raise ValueError('invalid %s: %s' % (convert_to, v)) + elif convert_to == 'float': + try: + v = float(v) + except ValueError: + raise ValueError('invalid %s: %s' % (convert_to, v)) + elif convert_to == 'bool': + if v: + try: + v = int(v) + except ValueError: + # (using eval would be a security hole) + try: + v = _bool_values[v.lower()] + except KeyError: + raise ValueError('invalid %s: %s' % (convert_to, v)) + else: + # int succeeded, now make bool + v = bool(v) + else: + # empty string is always False + v = False + return v + +def _evalWithImport(expr, env=None): + result = None + L = expr.split('.') + if len(L) > 1: + prefix = L[0] + try: + eval(prefix, env) + except NameError: + try: + code = "%s = __import__('%s')" % (prefix, prefix) + d = {} + # an "unqualified exec" causes strange errors in user code; + # see http://spyced.blogspot.com/2005/04/how-well-do-you-know-python-part-4.html + exec code in d + result = eval(expr, d) + except ImportError: + import spyceConfig, spyceUtil + msg = '''Unable to import %s while trying to execute %s. + You probably need to add its location to sys.path in your spyce config file + (%s) + + Raw error message was: %s''' % (prefix, expr, spyceConfig.__file__, spyceUtil.exceptionString()) + if not result: + result = eval(expr, env) + return result + +# called by generated code +def _marshallArgs(req, callable): + import inspect + (desired_args, _, _, defaults) = inspect.getargspec(callable) + if defaults: + args_with_defaults = dict(zip(desired_args[-len(defaults):], defaults)) + else: + args_with_defaults = {} + # ignore 'self' + if desired_args[0] == 'self': + desired_args = desired_args[1:] + if not desired_args: + return [] + + # pre-process request input + input = {} + for rawname, values in req.getpost().iteritems(): + L = rawname.split(':') + name, modifiers = L[0], L[1:] + op = 'assign' + convert = '' + for m in modifiers: + if m == 'list': + op = 'append' + elif m == 'int': + convert = 'int' + elif m == 'float': + convert = 'float' + elif m == 'bool': + convert = 'bool' + else: + raise 'invalid argument transformation %s' % m + if op == 'assign': + input[name] = _convertArg(values[0], convert) + else: + input[name] = [] + for v in values: + input[name].append(_convertArg(v, convert)) + spyce.DEBUG('marshalled input is %s' % input) + + # first non-self arg is modulefinder; others we try to look up in request + from spyceModule import moduleFinder + args = [moduleFinder(req._api)] + kwargs = {} + for argname in desired_args[1:]: + try: + v = input[argname] + except KeyError: + if argname in args_with_defaults: + continue + raise 'Required parameter %s not present in request' % argname + if argname in args_with_defaults: + kwargs[argname] = v + else: + args.append(v) + return args, kwargs + +############################################## +# Compile spyce files +# + +# (sig is usually '', but spylambda sticks its arguments there; spy:parent puts child there too) +def spyceCompile(buf, filename, sig, server, gentaglib=False): + # parse + ast, libs, tags = spyceParse(server, CRLF2LF(buf), filename, gentaglib, sig).info() + # optimize the ast + spyceOptimize(ast) + # generate braced code + out = StringIO() + refs = emitBracedPython(out, ast, gentaglib).getSpyceRefs() + # then, generate regular python code + bracedPython = out.getvalue() + + out = StringIO() + refs = emitPython(out, bracedPython, refs) + return out.getvalue(), refs, libs, tags + +def test(): + import spyce + f = open(sys.argv[1]) + gentaglib = spyceUtil.isTagCollection(f) + spycecode = f.read() + f.close() + tokens = spyceTokenize(processMagic(CRLF2LF(spycecode))) + print 'TOKENS:' + for type, text, begin, end in tokens: + print '%s (%s, %s): %s' % (type, begin, end, `text`) + pythoncode, refs, libs, tags = spyceCompile(spycecode, sys.argv[1], '', spyce.getServer(), gentaglib) + L = pythoncode.split('\n') + print 'CODE:' + for i in range(len(L)): + print '%s %s' % (str(i + 1).rjust(3), L[i]) + print 'REFS:' + for line, ref in refs.items(): + print '%s %s' % (str(line).rjust(3), ref) + print 'REFERENCED MODULES: %s' % libs + +if __name__ == '__main__': + test() diff --git a/spyce-2.1/spyceCompile.pyc b/spyce-2.1/spyceCompile.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ce685aeed6a7c23d028b8bf6393cbd3c20acb91 Binary files /dev/null and b/spyce-2.1/spyceCompile.pyc differ diff --git a/spyce-2.1/spyceConsole.py b/spyce-2.1/spyceConsole.py new file mode 100755 index 0000000000000000000000000000000000000000..71c211bd5f4f6dc5f62f974d3652f68cf1ded383 --- /dev/null +++ b/spyce-2.1/spyceConsole.py @@ -0,0 +1,75 @@ +""" +Basic usage: + start_console_thread(port) + +then just connect to the port you specified and hack away. +""" + +import socket, code, threading, sys + +# something internal in CodeCompiler writes to +# stdout; we don't want to clobber stdout for the rest of the app, +# so we have to do this as a workaround +class DummyWriter: + def __init__(self, sc, other_stream, threadname): + self.sc = sc + self.other_stream = other_stream + self.threadname = threadname + + def write(self, s): + if threading.currentThread().getName() == self.threadname: + self.sc.write(s) + else: + self.other_stream.write(s) + +class SocketConsole(code.InteractiveConsole): + def __init__(self, sock, locals=None): + code.InteractiveConsole.__init__(self, locals, 'socketconsole') + self.sock = sock + self.rfile = self.sock.makefile() + sys.stdout = DummyWriter(self, sys.stdout, 'socketconsole') + # stderr too, for completeness + sys.stderr = DummyWriter(self, sys.stderr, 'socketconsole') + def __del__(self): + sys.stdout = sys.stdout.other_stream + sys.stderr = sys.stderr.other_stream + + def raw_input(self, prompt=''): + self.write(prompt) + s = self.rfile.readline() + if not s: + raise EOFError() + return s.rstrip() + + def write(self, s): + self.sock.sendall(s) + +class MetaConsole: + """ + Hands incomming connections off to a SocketConsole. + No extra threading is done; only one SocketConsole will be + active at a time. + """ + def __init__(self, port): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.sock.bind(('', port)) + self.sock.listen(1) + + def one_session(self): + s, _ = self.sock.accept() + sc = SocketConsole(s) + sc.interact() + + def run(self): + while True: + try: + self.one_session() + except socket.error: + pass + +def start_console_thread(port=9999): + mc = MetaConsole(port) + t = threading.Thread(target=mc.run, name='socketconsole') + t.setDaemon(True) + t.start() diff --git a/spyce-2.1/spyceException.py b/spyce-2.1/spyceException.py new file mode 100755 index 0000000000000000000000000000000000000000..f095094e7b234ea17cb28bc27adb7a696ea1947a --- /dev/null +++ b/spyce-2.1/spyceException.py @@ -0,0 +1,152 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +################################################## + +__doc__ = '''Various Spyce-related exceptions''' + +import sys, string +import spyceUtil + +class HandlerError(Exception): + def __init__(self, element=None, description=None): + self.element = element + self.description = description + def __str__(self): + return repr(self) + def __repr__(self): + return 'HandlerError(%r, %r)' % (self.element, self.description) + +class CompoundHandlerError(HandlerError): + def __init__(self, sub_errors = None): + if sub_errors is None: + sub_errors = [] + self.sub_errors = sub_errors + def add(self, err): + self.sub_errors.append(err) + def __len__(self): + return len(self.sub_errors) + def __repr__(self): + return 'CompoundHandlerError(%r)' % self.sub_errors + +################################################## +# Syntax errors +# + +class pythonSyntaxError: + "Generate string out of current pythonSyntaxError exception" + def __repr__(self): + return self.str + def __init__(self, spycewrap): + self.str = '' + type, error, _ = sys.exc_info() + if type is type(SyntaxError): + raise 'instantiate pythonSyntaxError only when SyntaxError raised: %s' % `type` + if spycewrap.getCodeRefs().has_key(error.lineno): + begin, end, text, filename = spycewrap.getCodeRefs()[error.lineno] + if begin[0]==end[0]: + linestr = str(begin[0]) + else: + linestr = '%d-%d' % (begin[0], end[0]) + self.str = 'Python syntax error at %s:%s - %s\n %s\n' % (filename, linestr, error.msg, text) + else: + self.str = spyceUtil.exceptionString() + +class spyceSyntaxError: + "Generate string out of current spyceSyntaxError exception" + def __init__(self, msg, info=None): + self.msg = msg + self.info = info + def __repr__(self): + s = 'Spyce syntax error' + if self.info: + (begin, _), (end, _), text, filename = self.info + if begin==end: + linestr = str(begin) + else: + linestr = '%d-%d' % (begin, end) + s = s + ' at %s:%s - %s\n %s\n' % (filename, linestr, self.msg, text) + else: + s = s + ': '+self.msg + return s + +################################################## +# Runtime errors +# + +class spyceRuntimeException: + "Generate string out of current SpyceException exception." + # useful fields: str, type, value, traceback, msg + def __repr__(self): + return self.str + def __init__(self, spycewrap=None): + import spyce, spyceCompile + import traceback, string + e1, e2, tb = sys.exc_info() + tb = traceback.extract_tb(tb) + self.str = '' + self.type, self.value, self.traceback = e1, e2, tb + if e1 == spyceRuntimeException: + self.msg = str(e2) + else: + self.msg = string.join(traceback.format_exception_only(e1, e2)) + for i in range(len(tb)): + filename, lineno, funcname, text = tb[i] + coderefs = None + if filename == '' and spycewrap and spycewrap.getCodeRefs().has_key(lineno): + if funcname == spyceCompile.SPYCE_PROCESS_FUNC: + funcname = '(main)' + coderefs = spycewrap.getCodeRefs() + else: + try: + coderefs = spyce.getServer().module_coderefs[filename] + except KeyError: + pass + if coderefs: + try: + begin, end, text, filename = coderefs[lineno] + except KeyError: + spyce.DEBUG('coderefs are %s' % coderefs) + lineno = '%s???' % lineno + else: + if begin[0]==end[0]: + lineno = str(begin[0]) + else: + lineno = '%d-%d' % (begin[0], end[0]) + lineno=str(lineno) + tb[i] = filename, lineno, funcname, text + for i in range(len(tb)): + self.str = self.str + ' %s:%s, in %s: \n %s\n' % tb[i] + self.str = self.str + self.msg + +class spyceNotFound: + "Exception class to signal that Spyce file does not exist." + def __init__(self, file): + self.file = file + def __repr__(self): + return 'spyceNotFound exception: could not find "%s"' % self.file + +class spyceForbidden: + "Exception class to signal that Spyce file has access problems." + def __init__(self, file): + self.file = file + def __repr__(self): + return 'spyceForbidden exception: could not read "%s"' % self.file + +################################################## +# Special control-flow exceptions +# + +class spyceRedirect: + "Exception class to signal an internal redirect." + def __init__(self, filename): + self.filename = filename + def __repr__(self): + return 'spyceRedirect: "%s"' % self.filename + +class spyceDone: + "Exception class to immediately jump to the end of the spyceProcess method" + pass + diff --git a/spyce-2.1/spyceException.pyc b/spyce-2.1/spyceException.pyc new file mode 100644 index 0000000000000000000000000000000000000000..270aba533cba4d8d5a82b1b4b60bb07bfb7c335c Binary files /dev/null and b/spyce-2.1/spyceException.pyc differ diff --git a/spyce-2.1/spyceLock.py b/spyce-2.1/spyceLock.py new file mode 100755 index 0000000000000000000000000000000000000000..a48aa3829c42144fbf3b187167b76dc6dab288f9 --- /dev/null +++ b/spyce-2.1/spyceLock.py @@ -0,0 +1,110 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +################################################## + +import os + +__doc__ = 'Spyce locking-related functions' + +class dummyLock: + def acquire(self, block=True): + return True + def release(self): + pass + +try: + import threading +except ImportError: + threadLock = dummyLock +else: + threadLock = threading.RLock + +################################################## +# File locking +# + +# Adapted from portalocker.py, written by Jonathan Feinberg +# Used in rimap (http://rimap.sourceforge.net) before Spyce +# Methods: +# file_lock(file, flags) +# file_unlock(file) +# Constants: LOCK_EX, LOCK_SH, LOCK_NB +# -- RB + +# TODO dumb down to just provide "lock" and "lock_nb"; win32 code +# doesn't work, and the msvcrt code that DOES work doesn't map well +# to the "flags" approach here +try: + if os.name=='nt': + import win32con, win32file, pywintypes + LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK + LOCK_SH = 0 # the default + LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY + # is there any reason not to reuse the following structure? + __overlapped = pywintypes.OVERLAPPED() + def file_lock(file, flags): + hfile = win32file._get_osfhandle(file.fileno()) + win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped) + def file_unlock(file): + hfile = win32file._get_osfhandle(file.fileno()) + win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped) + elif os.name == 'posix': + import fcntl + LOCK_EX = fcntl.LOCK_EX + LOCK_SH = fcntl.LOCK_SH + LOCK_NB = fcntl.LOCK_NB + def file_lock(file, flags): + fcntl.flock(file.fileno(), flags) + def file_unlock(file): + fcntl.flock(file.fileno(), fcntl.LOCK_UN) + else: + raise 'locking not supported on this platform' +except: + LOCK_EX = 0 + LOCK_SH = 0 + LOCK_NB = 0 + # bring on the race conditions! :) + def file_lock(file, flags): pass + def file_unlock(file): pass + +class fileLock: + f=name=None + def __init__(self, name): + self.name=name+'.lock' + def acquire(self, block=1): + self.f=open(self.name, 'w') + if block: file_lock(self.f, LOCK_EX) + else: file_lock(self.f, LOCK_EX | LOCK_NB) + def release(self): + try: + if not self.f: return + file_unlock(self.f) + self.f.close() + os.unlink(self.name) + self.f=None + except: pass + + +class MultiLock: + """ + A lock that reduces contention by maintaining multiple locks + internally and mapping requests in a consistent manner to these. + """ + def __init__(self, locks, lock_generator): + self.locks = [] + for i in xrange(locks): + self.locks.append(lock_generator(i)) + def _lock(self, obj): + return self.locks[hash(obj) % len(self.locks)] + def acquire(self, obj, *args, **kwargs): + lock = self._lock(obj) + return lock.acquire(*args, **kwargs) + def release(self, obj, *args, **kwargs): + lock = self._lock(obj) + return lock.release(*args, **kwargs) + def locked(self, obj, *args, **kwargs): + lock = self._lock(obj) + return lock.locked(*args, **kwargs) diff --git a/spyce-2.1/spyceLock.pyc b/spyce-2.1/spyceLock.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23c8cc6f6ff84b6cd62fe18dca1a4d79346d6182 Binary files /dev/null and b/spyce-2.1/spyceLock.pyc differ diff --git a/spyce-2.1/spyceModpy.py b/spyce-2.1/spyceModpy.py new file mode 100755 index 0000000000000000000000000000000000000000..c110fe062b2c465253d976251b5ae1eeb0c07dd9 --- /dev/null +++ b/spyce-2.1/spyceModpy.py @@ -0,0 +1,139 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: spyceModpy.py 726 2005-05-13 16:29:50Z jbe $ +################################################## + +import string, sys, os +import spyce, spyceException, spyceUtil, spycePreload + +__doc__ = '''Apache mod_python-based Spyce entry point.''' + +################################################## +# Request / response handlers +# + +import _apache # fails when not running under apache +from mod_python import apache + +class NoFlush: + "Elide flush calls" + def __init__(self, apacheRequest): + self.write = apacheRequest.write + def flush(self): + pass + +# make sure that both sets of classes have identical functionality +class spyceModpyRequest(spyce.spyceRequest): + "Apache Spyce request object. (see spyce.spyceRequest)" + def __init__(self, apacheRequest): + spyce.spyceRequest.__init__(self) + self._in = apacheRequest + def env(self, name=None): + return spyceUtil.extractValue(self._in.subprocess_env, name) + def getHeader(self, type=None): + if type: + if self._in.headers_in.has_key(type): + return self._in.headers_in[type] + else: return self._in.headers_in + def getServerID(self): + return os.getpid() + +class spyceModpyResponse(spyce.spyceResponse): + "Apache Spyce response object. (see spyce.spyceResponse)" + def __init__(self, apacheRequest): + spyce.spyceResponse.__init__(self) + self.origout = apacheRequest + self.out = spyceUtil.BufferedOutput(NoFlush(apacheRequest)) + self.isCTset = 0 + self.headersSent = 0 + self.returncode = self.origout.status = self.RETURN_OK + # functions (for performance) + self.write = self.out.write + self.writeErr = sys.stderr.write + def close(self): + self.flush() + #self.out.close() + def clear(self): + self.out.clear() + def sendHeaders(self): + if self.headersSent: + return + if not self.isCTset: + self.setContentType("text/html") + self.origout.send_http_header() + self.headersSent = 1 + def clearHeaders(self): + if self.headersSent: + raise 'headers already sent' + for header in self.origout.headers_out.keys(): + del self.origout.headers_out[header] + def setContentType(self, content_type): + if self.headersSent: + raise 'headers already sent' + self.origout.content_type = content_type + self.isCTset = 1 + def setReturnCode(self, code): + if self.headersSent: + raise 'headers already sent' + self.returncode = self.origout.status = code + def addHeader(self, type, data, replace=0): + if self.headersSent: + raise 'headers already sent' + if replace: + self.origout.headers_out[type] = data + else: + self.origout.headers_out.add(type, data) + def flush(self, stopFlag=0): + if stopFlag: return + self.sendHeaders() + self.out.flush() + def unbuffer(self): + self.flush() + self.out.unbuffer() + +################################################## +# Apache config +# + +def getApacheConfig(apachereq, param, default=None): + "Return Apache mod_python configuration parameter." + try: + return apachereq.get_options()[param] + except: + return default + +################################################## +# Apache entry point +# + +configmod = None + +def spyceMain(apacheRequest): + "Apache entry point." + os.environ[spyce.SPYCE_ENTRY] = 'modpy' + apacheRequest.add_common_vars() + request = spyceModpyRequest(apacheRequest) + response = spyceModpyResponse(apacheRequest) + filename = apacheRequest.filename + global configmod + if configmod == None: + configFile = getApacheConfig(apacheRequest, 'SPYCE_CONFIG') + configmod = spycePreload.getConfigModule(configFile) + try: + result = spyce.spyceFileHandler(request, response, filename, config=configmod) + except (spyceException.spyceForbidden, spyceException.spyceNotFound), e: + response.clear() + response.setContentType('text/plain') + response.write(str(e)+'\n') + except: + response.clear() + response.setContentType('text/plain') + response.write(spyceUtil.exceptionString()+'\n') + try: + response.flush() + except: pass + return apache.OK + diff --git a/spyce-2.1/spyceModule.py b/spyce-2.1/spyceModule.py new file mode 100755 index 0000000000000000000000000000000000000000..ed2c4a5dadc1690427b61228ae381490616a90e2 --- /dev/null +++ b/spyce-2.1/spyceModule.py @@ -0,0 +1,98 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +################################################## + +__doc__ = '''Spyce modules functionality.''' + +################################################## +# Spyce module +# + +class spyceModule: + "All Spyce module should subclass this." + def __init__(self, wrapper): + self._api = wrapper + def start(self): + # called by spyce.py to initialize module + pass + def finish(self, theError=None): + # called by spyce.py to clean up + pass + def init(self, *args, **kwargs): + # called from compiled spyceProcess + pass + def __repr__(self): + return 'no information, '+str(self.__class__) + +class spyceModulePlus(spyceModule): + def __init__(self, wrapper): + spyceModule.__init__(self, wrapper) + self.wrapper = self._api # deprecated + self.modules = moduleFinder(wrapper) + self.globals = wrapper.getGlobals() + +from code import InteractiveConsole +import sys +class BreakpointConsole(InteractiveConsole): + def __init__(self, locals): + InteractiveConsole.__init__(self, locals, 'bpconsole') + from spyceConsole import DummyWriter + sys.stdout = DummyWriter(sys.__stdout__, sys.stdout, 'bpconsole') + sys.stderr = DummyWriter(sys.__stderr__, sys.stderr, 'bpconsole') + def __del__(self): + sys.stdout = sys.stdout.other_stream + sys.stderr = sys.stderr.other_stream + + def raw_input(self, prompt=''): + self.write(prompt) + s = raw_input().rstrip() + s = s.rstrip() + if not s or s == 'continue': + raise EOFError() + return s + +# this is what gets passed to handlers as "api" +class moduleFinder: + def __init__(self, wrapper): + self._wrapper = wrapper + def db(self): + return self._wrapper._codeenv.db + db = property(db) + def breakpoint(self, locals=None): + # (convenience method for active handlers) + if locals is None: + import sys + locals = sys._getframe(1).f_locals + import threading + th = threading.currentThread() + old_name = th.getName() + try: + th.setName('bpconsole') + bc = BreakpointConsole(locals) + sys.stdout.write('Spyce breakpoint console: type "continue" (without the quotes) to exit\n') + bc.interact() + finally: + th.setName(old_name) + def __repr__(self): + return 'moduleFinder' + def __getattr__(self, name): + return self._wrapper.getModule(name) + +################################################## +# Spyce module API +# + +spyceModuleAPI = [ 'getFilename', 'getCode', + 'getCodeRefs', 'getModRefs', + 'getServerObject', 'getServerGlobals', 'getServerID', + 'getModules', 'getModule', 'setModule', 'getGlobals', + 'registerModuleCallback', 'unregisterModuleCallback', + 'getRequest', 'getResponse', 'setResponse', + 'registerResponseCallback', 'unregisterResponseCallback', + 'spyceString', 'spyceFile', 'spyceModule', 'spyceTaglib', + 'setStdout', 'getStdout', +] + diff --git a/spyce-2.1/spyceModule.pyc b/spyce-2.1/spyceModule.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cdef96888bdf9b9cea74fc65bfa067e0d13fd41 Binary files /dev/null and b/spyce-2.1/spyceModule.pyc differ diff --git a/spyce-2.1/spycePreload.py b/spyce-2.1/spycePreload.py new file mode 100755 index 0000000000000000000000000000000000000000..91043321df7b9a5bb3228024edfc2c3cd5732475 --- /dev/null +++ b/spyce-2.1/spycePreload.py @@ -0,0 +1,27 @@ +import sys, os.path, imp + +def guessSpyceHome(): + try: + fname = sys.modules['__main__'].__file__ + except: + # for mod_python + fname = sys.modules['run_spyceModpy'].__file__ + bestguess = os.path.abspath(os.path.dirname(fname)) + for p in [bestguess] + sys.path: + path = os.path.join(p, 'spyce.py') + if os.path.exists(path): + return os.path.abspath(p) + raise 'unable to determine Spyce home directory; please add it to your PYTHONPATH' + +def defaultConfigFile(): + return os.path.join(guessSpyceHome(), 'spyceconf.py') + +def getConfigModule(configFile=None): + if not configFile: configFile = defaultConfigFile() + try: f = open(configFile) + except: raise Exception('Unable to read configuration file at ' + configFile) + try: + mod = imp.load_module('spyceConfig', f, configFile, ('.py', 'r', imp.PY_SOURCE)) + mod.__file__ = configFile # for user-friendly error messages + return mod + finally: f.close() diff --git a/spyce-2.1/spycePreload.pyc b/spyce-2.1/spycePreload.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03fbc0fac0d25a45cbcd70c06e48ab5f91abb0be Binary files /dev/null and b/spyce-2.1/spycePreload.pyc differ diff --git a/spyce-2.1/spyceProject.py b/spyce-2.1/spyceProject.py new file mode 100755 index 0000000000000000000000000000000000000000..e609bc9662e33d2ea8743964b365c19e5dfc5a16 --- /dev/null +++ b/spyce-2.1/spyceProject.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python + +import sys, os, shutil + +try: + (target,) = sys.argv[1:] +except: + print """spyceProject automates the creation of new Spyce projects. +Usage: spyceProject +e.g., spyceProject /var/firstproject""" + sys.exit() + +target = os.path.abspath(target) +if os.path.exists(target): + print "Error: %s already exists!" % target + sys.exit() + +try: + os.makedirs(target) +except: + print """Error creating %s. +Most likely you do not have appropriate permissions.""" % target + sys.exit() +print '\tcreated %s' % target + +for subdir in ['www', 'lib', 'login-tokens']: + subtarget = os.path.join(target, subdir) + os.mkdir(subtarget) + print '\tcreated %s' % subtarget + +# create _util for calendar js stuff +# (done manually instead of with shutil.copytree since we don't +# wan't to copy .svn directories if we're working from a subversion checkout.) +os.mkdir(os.path.join(target, 'www', '_util')) +for fname in os.listdir('util'): + if fname.startswith('.'): + continue + src = os.path.join('util', fname) + dst = os.path.join(target, 'www', '_util', fname) + shutil.copy2(src, dst) + +# write spyceconf +spyceconf = """ +from spyceconf import * + +# The root option defines the path from which Spyce requests are processed. +# I.e., when a request for http://yourserver/path/foo.spy arrives, +# Spyce looks for foo.spy in /path/. +root = r'%s' +# This allows you to import .py modules from the lib directory. +sys.path.append(r'%s') + +# Some commonly overridden options -- see spyceconf.py from the Spyce +# distribution for details and the full set of options. +debug = False # True to log more to stderr +port = 8000 # webserver port +indexExtensions = ['spy'] # list of extensions to check if directory requested + +# database connection +# Examples: +# db = SqlSoup('postgres://user:pass@localhost/dbname') +# db = SqlSoup('sqlite:///my.db') +# db = SqlSoup('mysql://user:pass@localhost/dbname') +# +# SqlSoup takes the same URLs as an SqlAlchemy Engine. See +# http://www.sqlalchemy.org/docs/dbengine.myt#dbengine_establishing +# for more examples. +db = None + +# authentication -- see spyceconf.py for how to customize these +login_validator = nevervalidator +login_storage = FileStorage(r'%s') +login_render = 'render:login' +loginrequired_render = 'render:login_required' +""".strip() % (os.path.join(target, 'www'), + os.path.join(target, 'lib'), + os.path.join(target, 'login-tokens')) +configfile = os.path.join(target, 'config.py') +open(configfile, 'w').write(spyceconf) +print '\tcreated %s' % configfile + +# write index skeleton +index = """ + + +Welcome to your new Spyce project! Feel free to join +the mailing list +and ask any questions you have. +""".strip() +indexfile = os.path.join(target, 'www', 'index.spy') +open(indexfile, 'w').write(index) +print '\tcreated %s' % indexfile + +# write default css +css = """ +.validationerror { + background-color: #44eeff; +} +.validationerror dt { + font-weight: bold; + margin-left: 1em; + margin-top: 0.5em; +} +.validationerror dd { + margin-left: 1em; + margin-bottom: 0.5em; +} +""".strip() +cssfile = os.path.join(target, 'www', 'style.css') +open(cssfile, 'w').write(css) +print '\tcreated %s' % cssfile + +# write parent skeleton +parent = """ + + + + [[= child.title ]] + + + +

[[= child.title ]]

+
+ + [[= child._body ]] + + + +""".strip() +parentfile = os.path.join(target, 'www', 'parent.spi') +open(parentfile, 'w').write(parent) +print '\tcreated %s' % parentfile + +# all done +print '\nSuccessfully created project skeleton at %s' % target +print '\nRun spyceCmd.py -l --conf "%s" to see it at http://localhost:8000/' % configfile diff --git a/spyce-2.1/spyceTag.py b/spyce-2.1/spyceTag.py new file mode 100755 index 0000000000000000000000000000000000000000..c14c613d843de9a8756fd25b08408ee55599f14b --- /dev/null +++ b/spyce-2.1/spyceTag.py @@ -0,0 +1,334 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: spyceTag.py 1174 2006-08-29 17:22:17Z ellisj $ +################################################## + +__doc__ = '''Spyce tags functionality.''' + +import sys, inspect +import spyce, spyceUtil +import spyceException, spyceModule + +################################################## +# Spyce tag library +# + +def invokeSingleton(api, tagname, memoize=False, **kwargs): + assert isinstance(api, spyce.spyceWrapper), 'expected spyceWrapper; got %s' % api.__class__ + spylambda = api.getModule('spylambda') + code = '[[ from spyceException import * ]] <%s %s />' % (tagname, ' '.join(['%s="=%s"' % (key, value) for key, value in kwargs.iteritems()])) + lamb = spylambda.define(','.join(kwargs.iterkeys()), code, memoize) + lamb(**kwargs) + +class spyceTagLibrary: + "All Spyce tag libraries should subclass this." + def __init__(self, prefix): + self._prefix = prefix + self._taghash = {} + for tag in self.tags: + self._taghash[tag.name] = tag + def getTag(self, api, name, id, context, attrs, paired, parent=None): + tag = self.getTagClass(name)(id, api, self._prefix, context, attrs, paired, parent) + tag._lib = self + return tag + def getTagClass(self, name): + return self._taghash[name] + + # functions to override + tags = [] + def start(self): + pass + def finish(self): + pass + +################################################## +# Spyce tag +# + +class spyceTag: + "All Spyce tags should subclass this." + def __init__(self, id, api, prefix, context, attrs, paired, parent=None): + "Initialize a tag; prefix = current library prefix" + self._id = id + if 0 and api: + assert isinstance(api, spyce.spyceWrapper) + self._api = api + self._prefix = prefix + self._pair = paired + self._parent = parent + self._context = context + self._out = None + self._buffered = 0 + if api and 'handler' in attrs: + # api == None means it's just tagchecker, and parent will always be None + ftag = self.getParent('form') + if not ftag: + raise spyceTagSyntaxException('tags with active handlers must be nested inside form:form') + if 'action' in ftag._attrs: + raise spyceTagSyntaxException('parent form action is incompatible with active handlers') + # delete it before it gets to begin() + # subclass can always do own attr checking before calling spyceTag.__init__ + del attrs['handler'] + self._attrs = self._attrsEval(attrs) + def _attrsEval(self, attrs): + if not self._context: # tagchecker passes None + return attrs + for key, expr in attrs.items(): + if expr and expr[0] == '=': + attrs[key] = self.eval(expr[1:]) + return attrs + def eval(self, expr): + try: + return eval(expr, self._context) + except NameError, e: + if '.' not in expr: + raise + # maybe it's a module reference + modname = expr.split('.')[0] + try: + mod = self._api.getModule(modname) + except ImportError: + raise e + self._context[modname] = mod + return eval(expr, self._context) + except TypeError: + raise 'Expected python code; unable to evaluate %s' % expr + # setup tag environment (output stream) + def setOut(self, out): + "Set output stream" + self._out = out + def setBuffered(self, buffered): + "Set whether tag is running on a buffer wrt. enclosing scope" + self._buffered = buffered + # accessors + def getPrefix(self): + "Return tag prefix" + return self._prefix + def getAttributes(self): + "Get tag attributes." + return self._attrs + def getPaired(self): + "Return whether this is a paired or singleton tag." + return self._pair + def getParent(self, name=None): + "Get parent tag" + parent = self._parent + if name is not None: + while parent is not None: + if parent.name == name: + break + parent = parent._parent + return parent + def getFullId(self): + id = '' + p = self + # tag compiler restricts handlers to be inside form + while p and p.__class__.__name__ != 'form_form': + if p._id: + id = p._id + id + p = p._parent + return id + def getOut(self): + "Return output stream" + return self._out + def getBuffered(self): + "Get whether tag is running on a buffer wrt. enclosing scope" + return self._buffered + # functions and fields to override + "Code chunk to insert into calling class (code, ref)" # for use by [[! code blocks in compiled tags + classcode = None + "Handlers to insert into calling class" + handlers = None + "The name of this tag!" + name = None + "Whether this tag wants to buffer its body processing" + buffer = 0 + "Whether this tag want to conditionally perform body processing" + conditional = 0 + "Whether this tag wants to possibly loop body processing" + loops = 0 + "Whether this tag wants to handle exceptions" + catches = 0 + "Whether end() must (even on exception) get called if begin() completes" + mustend = 0 + "Whether this tag wants to export values to calling context (overrides export())" + exports = 0 + def syntax(self): + "Check tag syntax" + pass + def begin(self, **kwargs): + "Process start tag; return true to process body (if conditional==1)" + return 1 + def export(self): + """ + return dict of any key/value pairs tag wants to propagate into spyceProcess locals. + (Obviously, key must be a string; value may be any object.) + Used e.g. by to send x into parent scope. + _Must set class.exports or export method will not be called._ + """ + return None + def body(self, _contents): + "Process tag body; return true to repeat (if loops==1)" + if _contents: + self.getOut().write(_contents) + return 0 + def end(self): + "Process end tag" + pass + def catch(self, ex): + "Process any exception thrown by tag (if catches==1)" + raise + +class spyceTagPlus(spyceTag): + "An easier spyceTag class to work with..." + def getModule(self, name): + "Return a Spyce module reference" + return self._api.getModule(name) + + def parentRequired(self, parentname): + parent = self.getParent(parentname) + if not parent: + raise '%s tag must be used inside a parent %s active tag' % (self.name, parentname) + return parent + def syntaxNonEmpty(self, *names): + for name in names: + try: value = self._attrs[name] + except KeyError: return + if not value: + raise spyceTagSyntaxException('attribute "%s" should not be empty', name) + def syntaxNonEmpty(self, *names): + for name in names: + try: value = self._attrs[name] + except KeyError: return + if not value: + raise spyceTagSyntaxException('attribute "%s" should not be empty', name) + def syntaxNonEmpty(self, *names): + for name in names: + try: value = self._attrs[name] + except KeyError: return + if not value: + raise spyceTagSyntaxException('attribute "%s" should not be empty', name) + def syntaxNonEmpty(self, *names): + for name in names: + try: value = self._attrs[name] + except KeyError: return + if not value: + raise spyceTagSyntaxException('attribute "%s" should not be empty', name) + def syntaxNonEmpty(self, *names): + for name in names: + try: value = self._attrs[name] + except KeyError: return + if not value: + raise spyceTagSyntaxException('attribute "%s" should not be empty', name) + def syntaxValidSet(self, name, validSet): + try: value = self._attrs[name] + except KeyError: return + if value not in validSet: + raise spyceTagSyntaxException('attribute "%s" should be one of: %s'% (name, ', '.join(validSet))) + def syntaxPairOnly(self): + "Ensure that this tag is paired i.e. open/close" + if not self._pair: + raise spyceTagSyntaxException('singleton tag not allowed') + def syntaxSingleOnly(self): + "Ensure that this tag is single i.e. " + if self._pair: + raise spyceTagSyntaxException('paired tag not allowed') + + +################################################## +# Spyce tag syntax checking +# + +class spyceTagChecker: + def __init__(self, server): + self._server = server + self._taglibs = {} + self._stack = [] + def loadLib(self, libname, libfrom, libas, rel_file, info=None): + if not libas: libas = libname + try: + self._taglibs[(libname, libfrom)] = \ + self._server.loadModule(libname, libfrom, rel_file)(libas) + except (SyntaxError, TypeError): + raise + except: + sys.stdout.write('%s\n' % spyceUtil.exceptionString()) + raise spyceException.spyceSyntaxError( + 'unable to load module: %s (%s)' % (libname, libas), info) + def getTag(self, (libname,libfrom), name, context, attrs, pair, info): + lib = self._taglibs[(libname, libfrom)] + try: + return lib.getTag(None, name, None, None, attrs, pair, None) + except: + spyce.DEBUG(spyceUtil.exceptionString()) + raise spyceException.spyceSyntaxError( + 'unknown tag "%s:%s"'%(libname, name), info) + def getTagClass(self, (libname, libfrom), name, info): + lib = self._taglibs[(libname, libfrom)] + try: + return lib.getTagClass(name) + except: + spyce.DEBUG(spyceUtil.exceptionString()) + s = 'unknown tag "%s:%s" (known tags in %s are: %s)' % ( + libname, + name, + libname, + ','.join([t.name for t in lib.tags])) + raise spyceException.spyceSyntaxError(s, info) + def startTag(self, (libname,libfrom), name, attrs, pair, info): + tag = self.getTag((libname, libfrom), name, None, attrs, pair, info) + try: + # standard signature validation + (args, varargs, varkw, defaults) = inspect.getargspec(tag.begin) + # args w/ defaults don't need to be checked + if defaults: + n_defaults = len(defaults) + else: + n_defaults = 0 + L = args[1:len(args) - n_defaults] # assume self is first + for attr in L: + if attr not in attrs: + raise spyceTagSyntaxException('"%s" tag call missing compulsory "%s" attribute' % (name, attr)) + # extra attrs cause an error, if *args/**kwargs not in signature + for attr in attrs: + # spyceCompile checks to make sure 'handler' is ok; tagChecker + # doesn't have the necessary info to make that decision. + if attr not in args and not varargs and not varkw and attr != 'handler': + raise spyceTagSyntaxException('unexpected attribute "%s"' % attr) + # custom validation + error = tag.syntax() + except spyceTagSyntaxException, e: + spyce.DEBUG(spyceUtil.exceptionString()) + raise spyceException.spyceSyntaxError(str(e), info) + if error: + raise spyceException.spyceSyntaxError(error, info) + if pair: + self._stack.append( (libname, libfrom, name, info) ) + def endTag(self, (libname,libfrom), name, info): + try: + libname1, libfrom1, name1, info1 = self._stack.pop() + except IndexError: + raise spyceException.spyceSyntaxError( + 'unmatched close tag', info) + if (libname1,libfrom1,name1) != (libname,libfrom,name): + raise spyceException.spyceSyntaxError( + 'unmatched close tag, expected <%s:%s>' % (libname1,name1), info) + def finish(self): + if self._stack: + libname, libfrom, name, info = self._stack.pop() + raise spyceException.spyceSyntaxError( + 'unmatched open tag', info) + +################################################## +# Spyce tag syntax exception +# + +class spyceTagSyntaxException: + def __init__(self, str): + self._str = str + def __repr__(self): + return self._str + diff --git a/spyce-2.1/spyceTag.pyc b/spyce-2.1/spyceTag.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c7d4c0ee1d543e28df27511289d7c51412f9dfe Binary files /dev/null and b/spyce-2.1/spyceTag.pyc differ diff --git a/spyce-2.1/spyceUtil.py b/spyce-2.1/spyceUtil.py new file mode 100755 index 0000000000000000000000000000000000000000..b4fb39c053eb849bd582b8c074c9493d9f797518 --- /dev/null +++ b/spyce-2.1/spyceUtil.py @@ -0,0 +1,240 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +################################################## + +import os.path, sys, re, string, imp, pydoc, time, shutil +from cStringIO import StringIO + +__doc__ = '''Spyce utility functions''' + +################################################## +# Misc + +module_mtimes = {} +# allows passing config module directly so you can avoid +# starting a server instance if you like. spyceWWW does this. +def scan_modules(): + modified = [] + for name, module in sys.modules.items(): + try: + filename = os.path.realpath(module.__file__) + except AttributeError: + continue + try: + mtime = os.path.getmtime(filename) + except OSError: + continue + if filename.endswith('.pyc') or filename.endswith('.pyo'): + pyfile = filename[:-1] + if os.path.exists(pyfile): + mtime = max(mtime, os.path.getmtime(pyfile)) + + try: + cachedtime = module_mtimes[filename] + except: + cachedtime = mtime + if cachedtime < mtime: + modified.append(name) + import spyce + spyce.DEBUG('found changed python module %s from %s' % (name, filename)) + module_mtimes[filename] = mtime + return modified + +def tryForAwhile(callable, tries=10, pause=1): + for i in range(tries): + try: return callable() + except: time.sleep(pause) + return None + +def doc(thing, forceload=0): + """Display text documentation, given an object or a path to an object.""" + s = "" + object, name = pydoc.resolve(thing, forceload) + desc = pydoc.describe(object) + module = pydoc.inspect.getmodule(object) + if name and '.' in name: + desc += ' in ' + name[:name.rfind('.')] + elif module and module is not object: + desc += ' in module ' + module.__name__ + s += pydoc.html.document(object, name) + return s + +class attrdict(dict): + def __getattr__(self, name): return self[name] + def __setattr__(self, name, value): self[name] = value + +def isTagCollection(f): + while True: + line = f.readline() + if not line: return False + if re.search(r'\S', line): break + f.seek(0) + return re.match(r'\s*\[\[\.\s*tagcollection', line, re.I) + +def argsForSpawn(args): + if sys.platform == 'win32': + # spawn doesn't escape spaces on win32 + args = ['"' + re.sub('"', r'\"', i) + '"' for i in args] + return args + +def url2file(url, relativeto=None): + """ + Returns the file corresponding to url. + If url is relative to the current file (i.e., url does not start with "/"), + Spyce needs to know the current file to give the correct answer. + """ + if url.startswith('/'): + L = url[1:].split('/') + import spyce + base = spyce.getServer().config.root + else: + if not relativeto: raise Exception('unable to determine relative path inside anonymous Spyce') + L = url.split('/') + base = os.path.dirname(relativeto) + return os.path.realpath(os.path.join(base, *L)) + +################################################## +# Current exception string + +def exceptionString(): + "Generate string out of current exception." + # (basically the equivalent of the python 2.4 traceback.format_exc) + import traceback, string + info = traceback.format_exception(*sys.exc_info()) + return string.join(info, '') + +################################################## +# Return hashtable value, or entire hashtable + +def extractValue(hash, key, default=None): + """Extract value from dictionary, if it exists. + If key is none, return entire dictionary""" + if key is None: + if isinstance(hash, dict): return hash + else: return dict(hash) + # can't use hash.get; some half-assed classes, like the one mod_python + # passes as the env, don't support it + if hash.has_key(key): return hash[key] + return default + +################################################## +# Return hashtable value, or entire hashtable + +RE_SPACE_REDUCE = re.compile('[ \t][ \t]+') +RE_SPACE_NEWLINE_REDUCE = re.compile('\n\s+') +def spaceCompact(text): + text = string.split(text, '\n') + text = map(lambda s: RE_SPACE_REDUCE.sub(' ', s), text) + text = string.join(text, '\n') + text = RE_SPACE_NEWLINE_REDUCE.sub('\n', text) + return text + +################################################## +# Threading helpers + +class ThreadedWriter: + '''Thread-safe writer''' + def __init__(self, o=None): + import threading + self.__dict__['_currentThread'] = threading.currentThread + self.__dict__['_o'] = o + def setObject(self, o=None): + self._currentThread().threadOut = o + self._currentThread().threadWrite = o.write + def getObject(self): + try: return self._currentThread().threadOut + except AttributeError: return self._o + def clearObject(self): + try: del self._currentThread().threadOut + except AttributeError: pass + def write(self, s): + try: self._currentThread().threadWrite(s) + except AttributeError: self._o.write(s) + def close(self): + self.getObject().close() + def flush(self): + self.getObject().flush() + def __getattr__(self, name): + if name=='softspace': # performance + return self.getObject().softspace + return eval('self.getObject().%s'%name) + def __setattr__(self, name, value): + if name=='softspace': # performance + self.getObject().softspace = value + eval('self.getObject().%s=value'%name) + def __delattr__(self, name): + return eval('del self.getObject().%s'%name) + +################################################## +# Output + +class BufferedOutput: + "Buffered output stream." + def __init__(self, out): + self.buf = StringIO() + self.writeBuf = self.buf.write + self.out = out + self.closed = 0 + def write(self, s): + if self.closed: + raise 'output stream closed' + self.writeBuf(s) + def clear(self): + if not self.buf: + raise 'stream is not buffered' + self.buf = StringIO() + self.writeBuf = self.buf.write + def flush(self, stopFlag=0): + if stopFlag: return + if self.buf and self.buf.getvalue(): + self.out.write(self.buf.getvalue()) + self.out.flush() + self.clear() + def close(self): + if self.closed: + raise 'output stream closed' + self.closed = 1 + self.flush() + self.out.close() + def unbuffer(self): + "Turn this into a pass-through." + if self.buf: + self.flush() + self.buf = None + self.writeBuf = self.out.write + def getOut(self): + "Return underlying output stream." + return self.out + + +class NoCloseOut: + def __init__(self, out): + self.out = out + self.write = self.out.write + self.flush = self.out.flush + def close(self): + pass + def getOut(self): + return self.out + + +def panicOutput(response, s): + import cgi + # output to browser, if possible + try: response.clear() + except: pass + try: + response.write('
\n')
+    response.write('Spyce Panic!
\n') + response.write(cgi.escape(s)) + response.write('
\n') + response.returncode = response.RETURN_OK + response.flush() + except: pass + # output to error log + sys.stderr.write(s) + sys.stderr.flush() + sys.exit(1) diff --git a/spyce-2.1/spyceUtil.pyc b/spyce-2.1/spyceUtil.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a16fbe2eedd5c8b2e41d572a6f27dc2d72a0e353 Binary files /dev/null and b/spyce-2.1/spyceUtil.pyc differ diff --git a/spyce-2.1/spyceWWW.py b/spyce-2.1/spyceWWW.py new file mode 100755 index 0000000000000000000000000000000000000000..70daf9b86b09c1493ad22346b635395205152ac9 --- /dev/null +++ b/spyce-2.1/spyceWWW.py @@ -0,0 +1,397 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: spyceWWW.py 1159 2006-08-16 23:33:19Z ellisj $ +################################################## + +import sys, os, string, socket, BaseHTTPServer, SocketServer, cgi, stat, time, threading +import spyce, spyceException, spyceCmd, spyceUtil, spycePreload +import pcqueue + +__doc__ = '''Standalone Spyce web server.''' + +LOG = 1 + +def formatBytes(bytes): + bytes = float(bytes) + if bytes<=9999: return "%6.0f" % bytes + bytes = bytes / float(1024) + if bytes<=999: return "%5.1fK" % bytes + bytes = bytes / float(1024) + return "%5.1fM" % bytes + +################################################## +# Request / response handlers +# + +class spyceHTTPRequest(spyce.spyceRequest): + 'HTTP Spyce request object. (see spyce.spyceRequest)' + def __init__(self, httpdHandler, documentRoot): + spyce.spyceRequest.__init__(self) + self._in = httpdHandler.rfile + self._headers = httpdHandler.headers + self._httpdHandler = httpdHandler + self._documentRoot = documentRoot + self._env = None + def env(self, name=None): + if not self._env: + self._env = { + 'REMOTE_ADDR': self._httpdHandler.client_address[0], + 'REMOTE_PORT': self._httpdHandler.client_address[1], + 'GATEWAY_INTERFACE': "CGI/1.1", + 'REQUEST_METHOD': self._httpdHandler.command, + 'REQUEST_URI': self._httpdHandler.path, + 'PATH_INFO': self._httpdHandler.pathinfo, + 'SERVER_SOFTWARE': 'spyce/%s' % spyce.__version__, + 'SERVER_PROTOCOL': self._httpdHandler.request_version, + 'DOCUMENT_ROOT': self._documentRoot, + 'QUERY_STRING': + string.join(string.split(self._httpdHandler.path, '?')[1:]) or '', + 'CONTENT_LENGTH': self.getHeader('Content-Length'), + 'CONTENT_TYPE': self.getHeader('Content-type'), + 'HTTP_USER_AGENT': self.getHeader('User-Agent'), + 'HTTP_ACCEPT': self.getHeader('Accept'), + 'HTTP_ACCEPT_ENCODING': self.getHeader('Accept-Encoding'), + 'HTTP_ACCEPT_LANGUAGE': self.getHeader('Accept-Language'), + 'HTTP_ACCEPT_CHARSET': self.getHeader('Accept-Charset'), + 'HTTP_COOKIE': self.getHeader('Cookie'), + 'HTTP_REFERER': self.getHeader('Referer'), + 'HTTP_HOST': self.getHeader('Host'), + 'HTTP_CONNECTION': self.getHeader('Connection'), + 'HTTP_KEEP_ALIVE': self.getHeader('Keep-alive'), + # From ASP + # AUTH_TYPE, + # APPL_PHYSICAL_PATH, + # REMOTE_HOST, + # SERVER_PROTOCOL, + # SERVER_SOFWARE + } + return spyceUtil.extractValue(self._env, name) + def getHeader(self, type=None): + if type: type=string.lower(type) + return spyceUtil.extractValue(self._headers.dict, type) + def getServerID(self): + return os.getpid() + +class spyceHTTPResponse(spyceCmd.spyceCmdlineResponse): + 'HTTP Spyce response object. (see spyce.spyceResponse)' + def __init__(self, httpdHandler): + self._httpheader = httpdHandler.request_version!='HTTP/0.9' + spyceCmd.spyceCmdlineResponse.__init__(self, spyceUtil.NoCloseOut(httpdHandler.wfile), sys.stdout, self._httpheader) + self._httpdHandler = httpdHandler + # incidentally, this a rather expensive operation! + if LOG: + httpdHandler.log_request() + def sendHeaders(self): + if self._httpheader and not self.headersSent: + resultText = self.RETURN_CODE.get(self.returncode) + self.origout.write('%s %s %s\n' % (self._httpdHandler.request_version, self.returncode, resultText)) + spyceCmd.spyceCmdlineResponse.sendHeaders(self) + def close(self): + spyceCmd.spyceCmdlineResponse.close(self) + self._httpdHandler.request.close() + + +################################################## +# Spyce web server +# + +class myHTTPhandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + if spyce.getServer().config.check_modules_and_restart: + L = spyceUtil.scan_modules() + if L: + self.send_response(200) + self.send_header("Content-type", "text/plain") + self.end_headers() + self.wfile.write("Detected changed modules %s. Restarting...\n(If you don't want Spyce to do this, turn off check_modules_and_restart in your spyceconf file.)" % L) + self.request.close() + os._exit(3) + + try: + start = time.time() + try: + # parse pathinfo + pathinfo = os.path.normpath(string.split(self.path, '?')[0]) + while pathinfo and (pathinfo[0]==os.sep or pathinfo[0:2]==os.pardir): + if pathinfo[0:len(os.sep)]==os.sep: pathinfo=pathinfo[len(os.sep):] + if pathinfo[0:len(os.pardir)]==os.pardir: pathinfo=pathinfo[len(os.pardir):] + self.pathinfo = "/"+pathinfo + # convert to path + path = os.path.join(self.server.documentRoot, pathinfo) + + if os.path.exists(path): + if os.path.isdir(path): + # directory listing + pathparts = self.path.split('?', 1) + if not pathparts[0].endswith('/'): + self.send_response(spyce.spyceResponse.RETURN_MOVED_PERMANENTLY) + self.send_header('Location', '?'.join([pathparts[0] + '/'] + pathparts[1:])) + self.end_headers() + return + # check for index.spy, index.html, etc. + indexFile = None + for findex in spyce.getServer().config.indexFiles: + p2 = os.path.join(path, findex) + if os.path.exists(p2): indexFile = p2; break + if indexFile is None: handler_type = '/' + else: + path = indexFile + handler_type = os.path.splitext(path)[-1][1:] + else: + # regular files + handler_type = os.path.splitext(path)[-1][1:] + try: handler = self.server.handler[handler_type] + except KeyError: handler = self.server.handler[None] + else: # force 404s to spyce handler so error page can be customized + handler = self.__class__.handler_spyce + if spyce.DEBUG_ERROR: sys.stderr.write('handler is %s\n' % str(handler)) + # process request + return handler(self, path) + except IOError: + self.send_error(404, "Unexpected IOError") + return None + finally: spyce.DEBUG('total for %s was %s' % (self.path, time.time() - start)) + do_POST=do_GET + def handler_spyce(self, path): + # process spyce + request = spyceHTTPRequest(self, self.server.documentRoot) + response = spyceHTTPResponse(self) + result = spyce.spyceFileHandler(request, response, path) + response.close() + def handler_dump(self, path): + # process content to dump (with correct mime type) + f = None + try: + f = open(path, 'rb') + try: + _, ext = os.path.splitext(path) + if ext: ext=ext[1:] + mimetype = self.server.mimeTable[ext] + except: + mimetype = "application/octet-stream" + self.send_response(200) + self.send_header("Content-type", mimetype) + self.end_headers() + self.wfile.write(f.read()) + self.request.close() + finally: + try: + if f: f.close() + except: pass + def handler_directory(self, path): + # process directory + if(self.path[-1:]!='/'): + self.send_response(301) + self.send_header('Location', self.path+'/') + self.end_headers() + return + L = os.listdir(path) + L.sort(lambda a, b: cmp(a.lower(), b.lower())) + def info(name, path=path): + fullname = os.path.join(path, name) + displayname = linkname = name = cgi.escape(name) + # Append / for directories or @ for symbolic links + if os.path.isdir(fullname): + displayname = name + "/" + linkname = name + "/" + elif os.path.islink(fullname): + displayname = name + "@" + statinfo = os.stat(fullname) + mtime = statinfo[stat.ST_MTIME] + size = statinfo[stat.ST_SIZE] + return linkname, displayname, mtime, size + L = map(info, L) + + NAME_WIDTH = 30 + output = ''' + + Index of %(title)s + + +

Index of %(title)s

+
 Name%(filler)s  Date%(filler_date)s  Size
''' % { + 'title' : self.pathinfo.replace(os.path.sep, '/'), + 'filler': ' '*(NAME_WIDTH-len('Name')), + 'filler_date': ' '*(len(time.asctime(time.localtime(0)))-len('Date')), + } + + if L: + for link, display, mtime, size in L: + output = output + ' %(display)s%(filler)s %(mtime)s %(size)s\n' % { + 'link': link, + 'display': display[:NAME_WIDTH], + 'link': link, + 'filler': ' '*(NAME_WIDTH-len(display)), + 'mtime': time.asctime(time.localtime(mtime)), + 'size': formatBytes(size), + } + else: + output = output + 'No files\n' + + output = output[:-1] + '''
+
Spyce-WWW/%(version)s server
+ +''' % { + 'version' : spyce.__version__, + } + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write(output) + +def buildMimeTable(files): + mimetable = {} + for file in files: + try: + f = None + try: + f = open(file, 'r') + line = f.readline() + while line: + if line[0]=='#': + line = f.readline(); continue + line = string.strip(line) + if not line: + line = f.readline(); continue + line = string.replace(line, '\t', ' ') + items = filter(None, map(string.strip, string.split(line, ' '))) + mimetype, extensions = items[0], items[1:] + for ext in extensions: + mimetable[ext] = mimetype + line = f.readline() + except IOError: pass + finally: + try: + if f: f.close() + except: pass + return mimetable + +def buildHandlerTable(handler, server): + for ext in handler.keys(): + handler[ext] = eval('server.handler_'+handler[ext]) + if spyce.DEBUG_ERROR: sys.stderr.write('handlers are ' + str(handler) + '\n') + return handler + +class ThreadPooledTcpServer(SocketServer.TCPServer): + allow_reuse_address = True + def __init__(self, minthreads, maxthreads, qsize, *args, **kwargs): + SocketServer.TCPServer.__init__(self, *args, **kwargs) + self.queue = pcqueue.PCQueue(minthreads, maxthreads, qsize) + self.queue.consume = self.consume + def consume(self, item): + request, client_address = item + try: + self.finish_request(request, client_address) + self.close_request(request) + except: + self.handle_error(request, client_address) + self.close_request(request) + def process_request(self, request, client_address): + self.queue.put((request, client_address)) + +def spyceHTTPserver(configFile, daemon=None): + stdout = sys.stdout + os.environ[spyce.SPYCE_ENTRY] = 'www' + config = spycePreload.getConfigModule(configFile) + if config.minthreads <= 0: + raise ValueError('minthreads must be at least 1') + if config.maxthreads < config.minthreads: + raise ValueError('maxthreads must be at least equal to minthreads') + + # initialize mtime cache + spyceUtil.scan_modules() + + # (_and_restart code inspired by WSGIkit) + if config.check_modules_and_restart: + reloader_environ_key = 'SPYCE_RELOADER_SHOULD_RUN' + if os.environ.get(reloader_environ_key): + stdout.write("# Running reloading file monitor (for check_modules_and_restart option)\n") + def exit_if_modules_changed(): + L = spyceUtil.scan_modules() + if L: + stdout.write("# (Detected changed modules %s. Restarting...\n\n(If you don't want Spyce to do this, turn off check_modules_and_restart in your spyceconf file.)\n" % L) + os._exit(3) + import scheduler + scheduler.schedule(1, exit_if_modules_changed) + else: + args = spyceUtil.argsForSpawn([sys.executable, "-u"] + sys.argv) + while 1: + stdout.write("# Spawning another server (for check_modules_and_restart option)\n") + new_environ = os.environ.copy() + new_environ[reloader_environ_key] = 'true' + + pid = os.spawnve(os.P_NOWAIT, sys.executable, args, new_environ) + # waitpid ignores keyboardinterrupt until the process it's waiting on finishes; + # the threading here is a workaround for that + exit_wrapper = [None] + def run(): exit_wrapper[0] = os.waitpid(pid, 0) + + th = threading.Thread(target=run) + th.setDaemon(True) + th.start() + try: + while th.isAlive(): time.sleep(0.1) + except (SystemExit, KeyboardInterrupt), e: + # try to clean up child process too + try: + import signal + os.kill(pid, signal.SIGTERM) + except (ImportError, AttributeError): + try: import win32api + except ImportError: + print "Unable to terminate child process during shutdown!\nPlease download and install the win32all package so this will work." + else: win32api.TerminateProcess(pid, -1) + raise e + exit_code = exit_wrapper[0][1] + spyce.DEBUG('RAW EXIT CODE %s' % (exit_code,)) + exit_code = exit_code >> 8 + # (3 == "changes detected, restart me") + if exit_code != 3: return exit_code + + print '# Starting Spyce web server. v%s' % spyce.__version__ + try: + print '# Configuration - %s' % configFile + server = spyce.getServer(config) + except (spyceException.spyceForbidden, spyceException.spyceNotFound), e: + print e + return + + try: + # initialize server + try: + httpd = ThreadPooledTcpServer(config.minthreads, config.maxthreads, config.maxqueuesize, + (config.ipaddr, config.port), myHTTPhandler) + print '# Listening on - %s:%d' % (config.ipaddr, config.port) + httpd.documentRoot = os.path.abspath(config.root) + print '# Document root - '+httpd.documentRoot + httpd.mimeTable = buildMimeTable(config.mime) + httpd.handler = buildHandlerTable(config.www_handlers, myHTTPhandler) + if config.adminport: + import spyceConsole + spyceConsole.start_console_thread(config.adminport) + print '# Admin console listening on port %d' % config.adminport + except (SystemExit, KeyboardInterrupt): raise + except: + print 'Unable to start server on port %s' % config.port + print spyceUtil.exceptionString() + return -1 + # daemonize + if daemon: + print '# Daemonizing process.' + try: spyceCmd.daemonize(pidfile=daemon) + except SystemExit: return 0 # expected + global LOG + LOG = 0 + + # process requests + print '# Ready.' + while 1: + try: httpd.handle_request() + except (SystemExit, KeyboardInterrupt): raise + except: stdout.write('Error: %s\n' % spyceUtil.exceptionString()) + except KeyboardInterrupt: print 'Break!' + httpd.queue.join() + return 0 diff --git a/spyce-2.1/spyceWWW.pyc b/spyce-2.1/spyceWWW.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5a5b5df011662f1a4cb68b99c0182b3cccbe862 Binary files /dev/null and b/spyce-2.1/spyceWWW.pyc differ diff --git a/spyce-2.1/spyceconf.py b/spyce-2.1/spyceconf.py new file mode 100755 index 0000000000000000000000000000000000000000..e7d8c04ff4d02a4cdc91e12c0c9697275758a891 --- /dev/null +++ b/spyce-2.1/spyceconf.py @@ -0,0 +1,333 @@ +# NOTE: do note write code that directly imports this module +# (except when you are writing a custom configuration module.) +# This is a recipe for trouble, since spyce allows the user +# to specify the configuration module filename on the commandline. +# Instead, use import spyce; spyce.getServer().config. + +import os, sys +import spycePreload + +# Determine SPYCE_HOME dynamically. +# (you can hardcode SPYCE_HOME if you really want to, but it shouldn't be necessary.) +SPYCE_HOME = spycePreload.guessSpyceHome() + +# The spyce path determines which directories are searched for when +# loading modules (with [[.import]]) and tag libraries (with [[.taglib]] +# and the globaltags configuration later in this file. +# +# By default, the Spyce installation directory is always searched +# first. Any directories in the SPYCE_PATH environment are also +# searched. +# +# If you need to import from .py modules in nonstandard locations +# (i.e., not in your python installation's library directory), +# you will want to add their directories to sys.path as well, as +# done here for the error module. (However, Spyce automagically +# changes sys.path dynamically so you will always be able to import +# modules in the same directory as your currently-processing .spy file.) +# +# path += ['/usr/spyce/inc/myapplication', '/var/myapp/lib'] +path = [os.path.join(SPYCE_HOME, 'modules'), os.path.join(SPYCE_HOME, 'contrib', 'modules')] +path.append(os.path.join(SPYCE_HOME, 'tags')) +path.append(os.path.join(SPYCE_HOME, 'contrib', 'tags')) +if os.environ.has_key('SPYCE_PATH'): + path += os.environ['SPYCE_PATH'].split(os.pathsep) +# provide originalsyspath so if someone wants to maintain a config file via +# "from spyceconf import *" +# he can remove the above modifications if desired. +originalsyspath = list(sys.path) +sys.path.extend(path) + +# The globaltags option specifies a group of tag libraries that will be autoloaded +# as if [[.taglib name=libname from=file as=prefix]] were specified in every .spy +# file. (There is no performance hit if the library is not used on a page; +# the Spyce compiler optimizes it out.) +# +# The format is ('libname', 'file', 'prefix'). +# For a 2.0-style tag library, the libname attribute is ignored. Passing None is fine. +# +# globaltags.append(('mytag', 'mytaglib.py', 'my')) +# globaltags.append((None, 'taglib2.py', 'my2')) +globaltags = [ + ('core', 'core.py', 'spy'), + ('form', 'form.py', 'f'), + (None, 'render.spi', 'render'), + ] + +# The default parent template is the one that is used by +# if no src attribute is given, specified as an absolute url. +defaultparent = "/parent.spi" + +# The errorhandler option sets the server-level error handler. These +# errors include spyce.spyceNotFound, spyce.spyceForbidden, +# spyce.spyceSyntaxError and spyce.pythonSyntaxError. (file-level +# error handling is defined within Spyce scripts using the error module.) +# +# The server will call the error handler as errorhandler(request, response, error). +# +# Please look at the default function if you are considering writing your own +# server error handler. +import error +errorhandler = error.serverHandler + +# The pageerror option sets the default page-level error handler. +# "Page-level" means all runtime errors that occur during the +# processing of a Spyce script (i.e. after the compilation phase has +# completed successfully) +# +# The format of this option is one of: +# ('string', 'MODULE', 'VARIABLE') +# ('file', 'URL') +# (This format is used since the error template must be transformed into +# compiled spyce code, which can't be done before the configuration file +# is completely loaded.) +# +# Please refer to the default template to see how to define your own +# page-level error handlers. +# +# pageerrortemplate = ('file', '/error.spy') +pageerrortemplate = ('string', 'error', 'defaultErrorTemplate') + +# The cache option affects the underlying cache mechanism that the +# server uses to maintain compiled Spyce scripts. Currently, Spyce +# supports two cache handlers: +# +# cache = 'memory' +# OR +# cache = 'file' +# cachedir = '/tmp' # REQUIRED: directory in which to store compiled files +# +# Why store the cache in the filesystem instead of memory? The main +# reason is if you are running under CGI or mod_python. Under +# mod_python, Apache will kick off a number of separate spyce +# processes; each would have its own memory cache; using the +# filesystem avoids wasteful duplication. (Pointing the cachedir to a +# ramdisk makes it almost as fast as a memory cache.) Under CGI of +# course, the python process isn't persistent so file caching is the +# only option to avoid expensive recompilation with each request. +# +# If you are running multiple Spyce instances on the same machine, +# they cannot share the same cachedir. Give each a different cachedir, +# or use the in-memory cache type. +cache = 'memory' + +# The check_mtime option affects the caching of compiled Spyce code. +# When True, Spyce will check file timestamps with each request and +# recompile if they have been modified. Setting this to False can +# speed up a "production server" but you will have to restart the +# server and (if using a file cache) clear out the cachedir +# to have changes made in the spyce code take effect. +check_mtime = True + +# The debug option turns on a LOT of logging to stderr. +debug = False + +# The globals section defines server-wide constants. The hashtable is +# accessible as "pool" within any Spyce file (with the pool +# method loaded), or as self._api.getServerGlobals() within any Spyce +# module. +# +# globals = {'name': "My Website", 'four': 2+2} +globals = {} + +# You may wish to pre-load various Python modules during engine initialization. +# Once imported, they will be in the python module cache. +# +# (You may of course use normal imports at any time in this configuration script; +# however, imports specified here are run after the Spyce server is +# completely initialized, making it safe to access the server internals via +# import spyce; spyce.getServer()...) +# +# imports = ['myModule', 'myModule2'] +imports = [] + +# The root option defines the path from which Spyce requests are processed. +# I.e., when a request for http://yourserver/path/foo.spy arrives, +# Spyce looks for foo.spy in /path/. +# +# root = '/var/www/html' +root = os.path.join(SPYCE_HOME, 'www') +# feel free to comment this next line out if you don't have python modules (.py) in your web root +sys.path.append(root) + +# some parts of spyce may need to create temporary files; usually the default is fine. +# BUT if you do override this, be sure to also override other parts of the config +# that reference it. (currently just session_store) +import tempfile +tmp = tempfile.gettempdir() + +# active tag to render form validation errors +validation_render = 'render:validation' + + +##### +# database connection +##### + +from sqlalchemy.ext.sqlsoup import SqlSoup + +# Examples: +# db = SqlSoup('postgres://user:pass@localhost/dbname') +# db = SqlSoup('sqlite:///my.db') +# db = SqlSoup('mysql://user:pass@localhost/dbname') +# +# SqlSoup takes the same URLs as an SqlAlchemy Engine. See +# http://www.sqlalchemy.org/docs/dbengine.myt#dbengine_establishing +# for more examples. +try: + db = SqlSoup('sqlite:///www/demos/to-do/todo.db') +except: + db = None + +##### +# session options -- see docs/mod_session.html for details +##### + +import session + +session_store = session.DbmStore(tmp) +# session_store = session.MemoryStore() + +session_path = '/' + +session_expire = 24 * 60 * 60 # seconds + +##### +# login options +##### + +# The spyce login system uses the session storage +# defined above; a key called _spy_login will be added to each session. + +# validators must be a function that takes login and password as +# arguments, and returns a pickle-able object (usually int or string) +# representing the ID of the logged in user, or None if login fails. +# +# You'll need to supply your own to hook into your database or other +# validation system; see the pyweboff config.py for an example of doing +# this. +def nevervalidator(login, password): + return None + +def testvalidator(login, password): + if login == 'spyce' and password == 'spyce': + return 2 + return None + +login_defaultvalidator = testvalidator + +# How to store login tokens. FileStorage comes with Spyce, +# but it's easy to create your own Storage class if you want to put them +# in your database, for instance. +from _coreutil import FileStorage +# It's not a good idea to put login-tokens off of www/ in production! +# It's just done this way here to keep the spyce source tree relatively clean. +login_storage = FileStorage(os.path.join(SPYCE_HOME, 'www', 'login-tokens')) + +# tags to render login form; must be visible in the globaltags search space. +# These come from tags/render.spi; to make your own, just create a tag +# that takes the same parameters and add the library to the globaltags list above. +login_render = 'render:login' +loginrequired_render = 'render:login_required' + +###### +# webserver options -- does not affect mod_python or *CGI configurations +###### + +# indexFiles specifies a list of files to look for +# in a directory if directory itself is requested. +# The first matching file will be selected. +# If empty, a directory listing will be served instead. +# +# indexFiles = ['index.spy', 'index.html', 'index.txt'] +indexFiles = ['index.spy', 'index.html'] + +# The Spyce webserver uses a threaded concurrency model. (Historically, +# it also offered "no concurrency" and forking. If for some strange +# reason you really want no concurrency, set minthreads=maxthreads=1. +# Forking was removed entirely because it performed over 10x slower +# than threading on Linux and Windows.) +# +# Do note that because of the Python GIL (global interpeter lock), +# only one CPU of a multi-CPU machine can execute Python code at a time. +# If this is your situation, mod_python may be a better option for you. +minthreads = 5 +maxthreads = 10 +# number of pending requests to accept +maxqueuesize = 50 + +# Restart the webserver if a python module changes. +# Spyce does this by running the "real" server in a subprocess; when that +# server detects changed modules, it exits and Spyce starts another one. +# +# Spyce will check for changes every second, or when a request +# is made, whichever comes first. +# +# It's highly recommended to turn this off for "production" servers, +# since checking each module for each request is a performance hit. +check_modules_and_restart = True + +# The ipaddr option defines which IP addresses the server will listen on. +# empty string for all. +ipaddr = '' + +# The port option defines which TCP port the server will listen on. +port = 8000 +# Port that provides an interactive Python console interface to +# the webserver's guts. Currently no password protection is offered; +# don't expose this to the outside world! +adminport = None + +# The mime option is a list of filenames. The files should +# be definitions of mime-types for common file extensions in the +# standard Apache format. +# +# mime: ['/etc/mime.types'] +mime = [os.path.join(SPYCE_HOME, 'spyce.mime')] + +# The www_handlers option defines the hander used for files of +# arbitrary extensions. (The None key specifies the default.) +# The currently supported handlers are: +# spyce - process the file at the requested path as a spyce script +# directory - display directory listing +# dump - transfer the file at the requested path verbatim, +# providing an appropriate "Content-type" header, if it is known. +# (It's difficult to use the actual instance methods here since we +# don't have a handle to the WWW server object. So, we use strings +# and let spyceWWW eval them later.) +www_handlers = { + 'spy': 'spyce', + '/': 'directory', + None: 'dump' +} + + +###### +# (F)CGI options +###### + +# Forbid direct requests to the cgi script and discard command line arguments. +# +# Reason: http://www.cert.org/advisories/CA-1996-11.html +# +# You may need to disable this for +# 1) non-Apache servers that do not set the REDIRECT_STATUS environment +# variable. +# 2) when using the alternative #! variant of CGI configuration +cgi_allow_only_redirect = False + + +###### +# standard module customization +###### + +# (request module) + +# param filters and file filters are lists of functions that are called +# for all GET and POST parameters or files, respectively. +# Each callable should expect two arguments: a reference to the +# request object/spyce module, and the dictionary being filtered. +# (Thus, each callable in param_filters will be called twice; once for the +# GET dict and once for POST. Each callable in file_filters will only be called once.) +param_filters = [] +file_filters = [] diff --git a/spyce-2.1/spyceconf.pyc b/spyce-2.1/spyceconf.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6530b52bb3713058cdf42754f237f3b96a3e1dd Binary files /dev/null and b/spyce-2.1/spyceconf.pyc differ diff --git a/spyce-2.1/sqlalchemy/__init__.py b/spyce-2.1/sqlalchemy/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..27d3ed667216dc7e558041f65b5204e1579ebaf3 --- /dev/null +++ b/spyce-2.1/sqlalchemy/__init__.py @@ -0,0 +1,17 @@ +# __init__.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from sqlalchemy.types import * +from sqlalchemy.sql import * +from sqlalchemy.schema import * +from sqlalchemy.orm import * + +from sqlalchemy.engine import create_engine +from sqlalchemy.schema import default_metadata + +def global_connect(*args, **kwargs): + default_metadata.connect(*args, **kwargs) + \ No newline at end of file diff --git a/spyce-2.1/sqlalchemy/__init__.pyc b/spyce-2.1/sqlalchemy/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d898e3d3b38ecfb134f45554700d0c81ac87dab Binary files /dev/null and b/spyce-2.1/sqlalchemy/__init__.pyc differ diff --git a/spyce-2.1/sqlalchemy/ansisql.py b/spyce-2.1/sqlalchemy/ansisql.py new file mode 100755 index 0000000000000000000000000000000000000000..a0ce64905de81fceb536f17d195db9433705cba0 --- /dev/null +++ b/spyce-2.1/sqlalchemy/ansisql.py @@ -0,0 +1,903 @@ +# ansisql.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""defines ANSI SQL operations. Contains default implementations for the abstract objects +in the sql module.""" + +from sqlalchemy import schema, sql, engine, util, sql_util +from sqlalchemy.engine import default +import string, re, sets, weakref + +ANSI_FUNCS = sets.ImmutableSet([ +'CURRENT_TIME', +'CURRENT_TIMESTAMP', +'CURRENT_DATE', +'LOCALTIME', +'LOCALTIMESTAMP', +'CURRENT_USER', +'SESSION_USER', +'USER' +]) + + +RESERVED_WORDS = util.Set(['all', 'analyse', 'analyze', 'and', 'any', 'array', 'as', 'asc', 'asymmetric', 'authorization', 'between', 'binary', 'both', 'case', 'cast', 'check', 'collate', 'column', 'constraint', 'create', 'cross', 'current_date', 'current_role', 'current_time', 'current_timestamp', 'current_user', 'default', 'deferrable', 'desc', 'distinct', 'do', 'else', 'end', 'except', 'false', 'for', 'foreign', 'freeze', 'from', 'full', 'grant', 'group', 'having', 'ilike', 'in', 'initially', 'inner', 'intersect', 'into', 'is', 'isnull', 'join', 'leading', 'left', 'like', 'limit', 'localtime', 'localtimestamp', 'natural', 'new', 'not', 'notnull', 'null', 'off', 'offset', 'old', 'on', 'only', 'or', 'order', 'outer', 'overlaps', 'placing', 'primary', 'references', 'right', 'select', 'session_user', 'similar', 'some', 'symmetric', 'table', 'then', 'to', 'trailing', 'true', 'union', 'unique', 'user', 'using', 'verbose', 'when', 'where']) + +LEGAL_CHARACTERS = util.Set(string.ascii_lowercase + string.ascii_uppercase + string.digits + '_$') +ILLEGAL_INITIAL_CHARACTERS = util.Set(string.digits + '$') + +class ANSIDialect(default.DefaultDialect): + def __init__(self, cache_identifiers=True, **kwargs): + super(ANSIDialect,self).__init__(**kwargs) + self.identifier_preparer = self.preparer() + self.cache_identifiers = cache_identifiers + + def create_connect_args(self): + return ([],{}) + + def dbapi(self): + return None + + def schemagenerator(self, *args, **params): + return ANSISchemaGenerator(*args, **params) + + def schemadropper(self, *args, **params): + return ANSISchemaDropper(*args, **params) + + def compiler(self, statement, parameters, **kwargs): + return ANSICompiler(self, statement, parameters, **kwargs) + + def preparer(self): + """return an IdenfifierPreparer. + + This object is used to format table and column names including proper quoting and case conventions.""" + return ANSIIdentifierPreparer(self) + +class ANSICompiler(sql.Compiled): + """default implementation of Compiled, which compiles ClauseElements into ANSI-compliant SQL strings.""" + def __init__(self, dialect, statement, parameters=None, **kwargs): + """constructs a new ANSICompiler object. + + dialect - Dialect to be used + + statement - ClauseElement to be compiled + + parameters - optional dictionary indicating a set of bind parameters + specified with this Compiled object. These parameters are the "default" + key/value pairs when the Compiled is executed, and also may affect the + actual compilation, as in the case of an INSERT where the actual columns + inserted will correspond to the keys present in the parameters.""" + sql.Compiled.__init__(self, dialect, statement, parameters, **kwargs) + self.binds = {} + self.froms = {} + self.wheres = {} + self.strings = {} + self.select_stack = [] + self.typemap = {} + self.isinsert = False + self.isupdate = False + self.bindtemplate = ":%s" + self.paramstyle = dialect.paramstyle + self.positional = dialect.positional + self.positiontup = [] + self.preparer = dialect.identifier_preparer + + def after_compile(self): + # this re will search for params like :param + # it has a negative lookbehind for an extra ':' so that it doesnt match + # postgres '::text' tokens + match = r'(? 0 + + def visit_calculatedclause(self, list): + if list.parens: + self.strings[list] = "(" + string.join([self.get_str(c) for c in list.clauses], ' ') + ")" + else: + self.strings[list] = string.join([self.get_str(c) for c in list.clauses], ' ') + + def visit_cast(self, cast): + if len(self.select_stack): + # not sure if we want to set the typemap here... + self.typemap.setdefault("CAST", cast.type) + self.strings[cast] = "CAST(%s AS %s)" % (self.strings[cast.clause],self.strings[cast.typeclause]) + + def visit_function(self, func): + if len(self.select_stack): + self.typemap.setdefault(func.name, func.type) + if not self.apply_function_parens(func): + self.strings[func] = ".".join(func.packagenames + [func.name]) + self.froms[func] = self.strings[func] + else: + self.strings[func] = ".".join(func.packagenames + [func.name]) + "(" + string.join([self.get_str(c) for c in func.clauses], ', ') + ")" + self.froms[func] = self.strings[func] + + def visit_compound_select(self, cs): + text = string.join([self.get_str(c) for c in cs.selects], " " + cs.keyword + " ") + group_by = self.get_str(cs.group_by_clause) + if group_by: + text += " GROUP BY " + group_by + order_by = self.get_str(cs.order_by_clause) + if order_by: + text += " ORDER BY " + order_by + text += self.visit_select_postclauses(cs) + if cs.parens: + self.strings[cs] = "(" + text + ")" + else: + self.strings[cs] = text + self.froms[cs] = "(" + text + ")" + + def visit_binary(self, binary): + result = self.get_str(binary.left) + if binary.operator is not None: + result += " " + self.binary_operator_string(binary) + result += " " + self.get_str(binary.right) + if binary.parens: + result = "(" + result + ")" + self.strings[binary] = result + + def binary_operator_string(self, binary): + return binary.operator + + def visit_bindparam(self, bindparam): + if bindparam.shortname != bindparam.key: + self.binds.setdefault(bindparam.shortname, bindparam) + count = 1 + key = bindparam.key + + # redefine the generated name of the bind param in the case + # that we have multiple conflicting bind parameters. + while self.binds.setdefault(key, bindparam) is not bindparam: + # insure the name doesn't expand the length of the string + # in case we're at the edge of max identifier length + tag = "_%d" % count + key = bindparam.key[0 : len(bindparam.key) - len(tag)] + tag + count += 1 + bindparam.key = key + self.strings[bindparam] = self.bindparam_string(key) + + def bindparam_string(self, name): + return self.bindtemplate % name + + def visit_alias(self, alias): + self.froms[alias] = self.get_from_text(alias.original) + " AS " + self.preparer.format_alias(alias) + self.strings[alias] = self.get_str(alias.original) + + def visit_select(self, select): + + # the actual list of columns to print in the SELECT column list. + # its an ordered dictionary to insure that the actual labeled column name + # is unique. + inner_columns = util.OrderedDict() + + self.select_stack.append(select) + for c in select._raw_columns: + # TODO: make this polymorphic? + if isinstance(c, sql.Select) and c._scalar: + c.accept_visitor(self) + inner_columns[self.get_str(c)] = c + continue + try: + s = c._selectable() + except AttributeError: + c.accept_visitor(self) + inner_columns[self.get_str(c)] = c + continue + for co in s.columns: + if select.use_labels: + l = co.label(co._label) + l.accept_visitor(self) + inner_columns[co._label] = l + # TODO: figure this out, a ColumnClause with a select as a parent + # is different from any other kind of parent + elif select.issubquery and isinstance(co, sql._ColumnClause) and co.table is not None and not isinstance(co.table, sql.Select): + # SQLite doesnt like selecting from a subquery where the column + # names look like table.colname, so add a label synonomous with + # the column name + l = co.label(co.name) + l.accept_visitor(self) + inner_columns[self.get_str(l.obj)] = l + else: + co.accept_visitor(self) + inner_columns[self.get_str(co)] = co + self.select_stack.pop(-1) + + collist = string.join([self.get_str(v) for v in inner_columns.values()], ', ') + + text = "SELECT " + text += self.visit_select_precolumns(select) + text += collist + + whereclause = select.whereclause + + froms = [] + for f in select.froms: + + if self.parameters is not None: + # look at our own parameters, see if they + # are all present in the form of BindParamClauses. if + # not, then append to the above whereclause column conditions + # matching those keys + for c in f.columns: + if sql.is_column(c) and self.parameters.has_key(c.key) and not self.binds.has_key(c.key): + value = self.parameters[c.key] + else: + continue + clause = c==value + clause.accept_visitor(self) + whereclause = sql.and_(clause, whereclause) + self.visit_compound(whereclause) + + # special thingy used by oracle to redefine a join + w = self.get_whereclause(f) + if w is not None: + # TODO: move this more into the oracle module + whereclause = sql.and_(w, whereclause) + self.visit_compound(whereclause) + + t = self.get_from_text(f) + if t is not None: + froms.append(t) + + if len(froms): + text += " \nFROM " + text += string.join(froms, ', ') + else: + text += self.default_from() + + if whereclause is not None: + t = self.get_str(whereclause) + if t: + text += " \nWHERE " + t + + group_by = self.get_str(select.group_by_clause) + if group_by: + text += " GROUP BY " + group_by + + if select.having is not None: + t = self.get_str(select.having) + if t: + text += " \nHAVING " + t + + order_by = self.get_str(select.order_by_clause) + if order_by: + text += " ORDER BY " + order_by + + text += self.visit_select_postclauses(select) + + text += self.for_update_clause(select) + + if getattr(select, 'parens', False): + self.strings[select] = "(" + text + ")" + else: + self.strings[select] = text + self.froms[select] = "(" + text + ")" + + def visit_select_precolumns(self, select): + """ called when building a SELECT statment, position is just before column list """ + return select.distinct and "DISTINCT " or "" + + def visit_select_postclauses(self, select): + """ called when building a SELECT statement, position is after all other SELECT clauses. Most DB syntaxes put LIMIT/OFFSET here """ + return (select.limit or select.offset) and self.limit_clause(select) or "" + + def for_update_clause(self, select): + if select.for_update: + return " FOR UPDATE" + else: + return "" + + def limit_clause(self, select): + text = "" + if select.limit is not None: + text += " \n LIMIT " + str(select.limit) + if select.offset is not None: + if select.limit is None: + text += " \n LIMIT -1" + text += " OFFSET " + str(select.offset) + return text + + def visit_table(self, table): + self.froms[table] = self.preparer.format_table(table) + self.strings[table] = "" + + def visit_join(self, join): + # TODO: ppl are going to want RIGHT, FULL OUTER and NATURAL joins. + righttext = self.get_from_text(join.right) + if join.right._group_parenthesized(): + righttext = "(" + righttext + ")" + if join.isouter: + self.froms[join] = (self.get_from_text(join.left) + " LEFT OUTER JOIN " + righttext + + " ON " + self.get_str(join.onclause)) + else: + self.froms[join] = (self.get_from_text(join.left) + " JOIN " + righttext + + " ON " + self.get_str(join.onclause)) + self.strings[join] = self.froms[join] + + def visit_insert_column_default(self, column, default, parameters): + """called when visiting an Insert statement, for each column in the table that + contains a ColumnDefault object. adds a blank 'placeholder' parameter so the + Insert gets compiled with this column's name in its column and VALUES clauses.""" + parameters.setdefault(column.key, None) + + def visit_update_column_default(self, column, default, parameters): + """called when visiting an Update statement, for each column in the table that + contains a ColumnDefault object as an onupdate. adds a blank 'placeholder' parameter so the + Update gets compiled with this column's name as one of its SET clauses.""" + parameters.setdefault(column.key, None) + + def visit_insert_sequence(self, column, sequence, parameters): + """called when visiting an Insert statement, for each column in the table that + contains a Sequence object. Overridden by compilers that support sequences to place + a blank 'placeholder' parameter, so the Insert gets compiled with this column's + name in its column and VALUES clauses.""" + pass + + def visit_insert_column(self, column, parameters): + """called when visiting an Insert statement, for each column in the table + that is a NULL insert into the table. Overridden by compilers who disallow + NULL columns being set in an Insert where there is a default value on the column + (i.e. postgres), to remove the column from the parameter list.""" + pass + + def visit_insert(self, insert_stmt): + # scan the table's columns for defaults that have to be pre-set for an INSERT + # add these columns to the parameter list via visit_insert_XXX methods + default_params = {} + class DefaultVisitor(schema.SchemaVisitor): + def visit_column(s, c): + self.visit_insert_column(c, default_params) + def visit_column_default(s, cd): + self.visit_insert_column_default(c, cd, default_params) + def visit_sequence(s, seq): + self.visit_insert_sequence(c, seq, default_params) + vis = DefaultVisitor() + for c in insert_stmt.table.c: + if (isinstance(c, schema.SchemaItem) and (self.parameters is None or self.parameters.get(c.key, None) is None)): + c.accept_schema_visitor(vis) + + self.isinsert = True + colparams = self._get_colparams(insert_stmt, default_params) + + def create_param(p): + if isinstance(p, sql._BindParamClause): + self.binds[p.key] = p + if p.shortname is not None: + self.binds[p.shortname] = p + return self.bindparam_string(p.key) + else: + p.accept_visitor(self) + if isinstance(p, sql.ClauseElement) and not isinstance(p, sql.ColumnElement): + return "(" + self.get_str(p) + ")" + else: + return self.get_str(p) + + text = ("INSERT INTO " + self.preparer.format_table(insert_stmt.table) + " (" + string.join([self.preparer.format_column(c[0]) for c in colparams], ', ') + ")" + + " VALUES (" + string.join([create_param(c[1]) for c in colparams], ', ') + ")") + + self.strings[insert_stmt] = text + + def visit_update(self, update_stmt): + # scan the table's columns for onupdates that have to be pre-set for an UPDATE + # add these columns to the parameter list via visit_update_XXX methods + default_params = {} + class OnUpdateVisitor(schema.SchemaVisitor): + def visit_column_onupdate(s, cd): + self.visit_update_column_default(c, cd, default_params) + vis = OnUpdateVisitor() + for c in update_stmt.table.c: + if (isinstance(c, schema.SchemaItem) and (self.parameters is None or self.parameters.get(c.key, None) is None)): + c.accept_schema_visitor(vis) + + self.isupdate = True + colparams = self._get_colparams(update_stmt, default_params) + def create_param(p): + if isinstance(p, sql._BindParamClause): + self.binds[p.key] = p + self.binds[p.shortname] = p + return self.bindparam_string(p.key) + else: + p.accept_visitor(self) + if isinstance(p, sql.ClauseElement) and not isinstance(p, sql.ColumnElement): + return "(" + self.get_str(p) + ")" + else: + return self.get_str(p) + + text = "UPDATE " + self.preparer.format_table(update_stmt.table) + " SET " + string.join(["%s=%s" % (self.preparer.format_column(c[0]), create_param(c[1])) for c in colparams], ', ') + + if update_stmt.whereclause: + text += " WHERE " + self.get_str(update_stmt.whereclause) + + self.strings[update_stmt] = text + + + def _get_colparams(self, stmt, default_params): + """determines the VALUES or SET clause for an INSERT or UPDATE + clause based on the arguments specified to this ANSICompiler object + (i.e., the execute() or compile() method clause object): + + insert(mytable).execute(col1='foo', col2='bar') + mytable.update().execute(col2='foo', col3='bar') + + in the above examples, the insert() and update() methods have no "values" sent to them + at all, so compiling them with no arguments would yield an insert for all table columns, + or an update with no SET clauses. but the parameters sent indicate a set of per-compilation + arguments that result in a differently compiled INSERT or UPDATE object compared to the + original. The "values" parameter to the insert/update is figured as well if present, + but the incoming "parameters" sent here take precedence. + """ + # case one: no parameters in the statement, no parameters in the + # compiled params - just return binds for all the table columns + if self.parameters is None and stmt.parameters is None: + return [(c, sql.bindparam(c.key, type=c.type)) for c in stmt.table.columns] + + # if we have statement parameters - set defaults in the + # compiled params + if self.parameters is None: + parameters = {} + else: + parameters = self.parameters.copy() + + if stmt.parameters is not None: + for k, v in stmt.parameters.iteritems(): + parameters.setdefault(k, v) + + for k, v in default_params.iteritems(): + parameters.setdefault(k, v) + + # now go thru compiled params, get the Column object for each key + d = {} + for key, value in parameters.iteritems(): + if isinstance(key, sql._ColumnClause): + d[key] = value + else: + try: + d[stmt.table.columns[str(key)]] = value + except KeyError: + pass + + # create a list of column assignment clauses as tuples + values = [] + for c in stmt.table.columns: + if d.has_key(c): + value = d[c] + if sql._is_literal(value): + value = sql.bindparam(c.key, value, type=c.type) + values.append((c, value)) + return values + + def visit_delete(self, delete_stmt): + text = "DELETE FROM " + self.preparer.format_table(delete_stmt.table) + + if delete_stmt.whereclause: + text += " WHERE " + self.get_str(delete_stmt.whereclause) + + self.strings[delete_stmt] = text + + def __str__(self): + return self.get_str(self.statement) + +class ANSISchemaBase(engine.SchemaIterator): + def find_alterables(self, tables): + alterables = [] + class FindAlterables(schema.SchemaVisitor): + def visit_foreign_key_constraint(self, constraint): + if constraint.use_alter and constraint.table in tables: + alterables.append(constraint) + findalterables = FindAlterables() + for table in tables: + for c in table.constraints: + c.accept_schema_visitor(findalterables) + return alterables + +class ANSISchemaGenerator(ANSISchemaBase): + def __init__(self, engine, proxy, connection, checkfirst=False, tables=None, **kwargs): + super(ANSISchemaGenerator, self).__init__(engine, proxy, **kwargs) + self.checkfirst = checkfirst + self.tables = tables and util.Set(tables) or None + self.connection = connection + self.preparer = self.engine.dialect.preparer() + self.dialect = self.engine.dialect + + def get_column_specification(self, column, first_pk=False): + raise NotImplementedError() + + def visit_metadata(self, metadata): + collection = [t for t in metadata.table_iterator(reverse=False, tables=self.tables) if (not self.checkfirst or not self.dialect.has_table(self.connection, t.name))] + for table in collection: + table.accept_schema_visitor(self, traverse=False) + if self.supports_alter(): + for alterable in self.find_alterables(collection): + self.add_foreignkey(alterable) + + def visit_table(self, table): + for column in table.columns: + if column.default is not None: + column.default.accept_schema_visitor(self, traverse=False) + #if column.onupdate is not None: + # column.onupdate.accept_schema_visitor(visitor, traverse=False) + + self.append("\nCREATE TABLE " + self.preparer.format_table(table) + " (") + + separator = "\n" + + # if only one primary key, specify it along with the column + first_pk = False + for column in table.columns: + self.append(separator) + separator = ", \n" + self.append("\t" + self.get_column_specification(column, first_pk=column.primary_key and not first_pk)) + if column.primary_key: + first_pk = True + for constraint in column.constraints: + constraint.accept_schema_visitor(self, traverse=False) + + for constraint in table.constraints: + constraint.accept_schema_visitor(self, traverse=False) + + self.append("\n)%s\n\n" % self.post_create_table(table)) + self.execute() + if hasattr(table, 'indexes'): + for index in table.indexes: + index.accept_schema_visitor(self, traverse=False) + + def post_create_table(self, table): + return '' + + def get_column_default_string(self, column): + if isinstance(column.default, schema.PassiveDefault): + if isinstance(column.default.arg, str): + return repr(column.default.arg) + else: + return str(self._compile(column.default.arg, None)) + else: + return None + + def _compile(self, tocompile, parameters): + """compile the given string/parameters using this SchemaGenerator's dialect.""" + compiler = self.engine.dialect.compiler(tocompile, parameters) + compiler.compile() + return compiler + + def visit_check_constraint(self, constraint): + self.append(", \n\t") + if constraint.name is not None: + self.append("CONSTRAINT %s " % constraint.name) + self.append(" CHECK (%s)" % constraint.sqltext) + + def visit_primary_key_constraint(self, constraint): + if len(constraint) == 0: + return + self.append(", \n\tPRIMARY KEY ") + if constraint.name is not None: + self.append("%s " % constraint.name) + self.append("(%s)" % (string.join([self.preparer.format_column(c) for c in constraint],', '))) + + def supports_alter(self): + return True + + def visit_foreign_key_constraint(self, constraint): + if constraint.use_alter and self.supports_alter(): + return + self.append(", \n\t ") + self.define_foreign_key(constraint) + + def add_foreignkey(self, constraint): + self.append("ALTER TABLE %s ADD " % self.preparer.format_table(constraint.table)) + self.define_foreign_key(constraint) + self.execute() + + def define_foreign_key(self, constraint): + if constraint.name is not None: + self.append("CONSTRAINT %s " % constraint.name) + self.append("FOREIGN KEY(%s) REFERENCES %s (%s)" % ( + string.join([self.preparer.format_column(f.parent) for f in constraint.elements], ', '), + self.preparer.format_table(list(constraint.elements)[0].column.table), + string.join([self.preparer.format_column(f.column) for f in constraint.elements], ', ') + )) + if constraint.ondelete is not None: + self.append(" ON DELETE %s" % constraint.ondelete) + if constraint.onupdate is not None: + self.append(" ON UPDATE %s" % constraint.onupdate) + + def visit_unique_constraint(self, constraint): + self.append(", \n\t") + if constraint.name is not None: + self.append("CONSTRAINT %s " % constraint.name) + self.append(" UNIQUE ") + self.append("(%s)" % (string.join([self.preparer.format_column(c) for c in constraint],', '))) + + def visit_column(self, column): + pass + + def visit_index(self, index): + self.append('CREATE ') + if index.unique: + self.append('UNIQUE ') + self.append('INDEX %s ON %s (%s)' \ + % (index.name, self.preparer.format_table(index.table), + string.join([self.preparer.format_column(c) for c in index.columns], ', '))) + self.execute() + +class ANSISchemaDropper(ANSISchemaBase): + def __init__(self, engine, proxy, connection, checkfirst=False, tables=None, **kwargs): + super(ANSISchemaDropper, self).__init__(engine, proxy, **kwargs) + self.checkfirst = checkfirst + self.tables = tables + self.connection = connection + self.preparer = self.engine.dialect.preparer() + self.dialect = self.engine.dialect + + def visit_metadata(self, metadata): + collection = [t for t in metadata.table_iterator(reverse=True, tables=self.tables) if (not self.checkfirst or self.dialect.has_table(self.connection, t.name))] + if self.supports_alter(): + for alterable in self.find_alterables(collection): + self.drop_foreignkey(alterable) + for table in collection: + table.accept_schema_visitor(self, traverse=False) + + def supports_alter(self): + return True + + def visit_index(self, index): + self.append("\nDROP INDEX " + index.name) + self.execute() + + def drop_foreignkey(self, constraint): + self.append("ALTER TABLE %s DROP CONSTRAINT %s" % (self.preparer.format_table(constraint.table), constraint.name)) + self.execute() + + def visit_table(self, table): + for column in table.columns: + if column.default is not None: + column.default.accept_schema_visitor(self, traverse=False) + + self.append("\nDROP TABLE " + self.preparer.format_table(table)) + self.execute() + +class ANSIDefaultRunner(engine.DefaultRunner): + pass + +class ANSIIdentifierPreparer(object): + """handles quoting and case-folding of identifiers based on options""" + def __init__(self, dialect, initial_quote='"', final_quote=None, omit_schema=False): + """Constructs a new ANSIIdentifierPreparer object. + + initial_quote - Character that begins a delimited identifier + final_quote - Caracter that ends a delimited identifier. defaults to initial_quote. + + omit_schema - prevent prepending schema name. useful for databases that do not support schemae + """ + self.dialect = dialect + self.initial_quote = initial_quote + self.final_quote = final_quote or self.initial_quote + self.omit_schema = omit_schema + self.__strings = {} + def _escape_identifier(self, value): + """escape an identifier. + + subclasses should override this to provide database-dependent escaping behavior.""" + return value.replace('"', '""') + + def _quote_identifier(self, value): + """quote an identifier. + + subclasses should override this to provide database-dependent quoting behavior.""" + return self.initial_quote + self._escape_identifier(value) + self.final_quote + + def _fold_identifier_case(self, value): + """fold the case of an identifier. + + subclassses should override this to provide database-dependent case folding behavior.""" + return value + # ANSI SQL calls for the case of all unquoted identifiers to be folded to UPPER. + # some tests would need to be rewritten if this is done. + #return value.upper() + + def _reserved_words(self): + return RESERVED_WORDS + + def _legal_characters(self): + return LEGAL_CHARACTERS + + def _illegal_initial_characters(self): + return ILLEGAL_INITIAL_CHARACTERS + + def _requires_quotes(self, value, case_sensitive): + """return true if the given identifier requires quoting.""" + return \ + value in self._reserved_words() \ + or (value[0] in self._illegal_initial_characters()) \ + or bool(len([x for x in str(value) if x not in self._legal_characters()])) \ + or (case_sensitive and value.lower() != value) + + def __generic_obj_format(self, obj, ident): + if getattr(obj, 'quote', False): + return self._quote_identifier(ident) + if self.dialect.cache_identifiers: + case_sens = getattr(obj, 'case_sensitive', None) + try: + return self.__strings[(ident, case_sens)] + except KeyError: + if self._requires_quotes(ident, getattr(obj, 'case_sensitive', ident == ident.lower())): + self.__strings[(ident, case_sens)] = self._quote_identifier(ident) + else: + self.__strings[(ident, case_sens)] = ident + return self.__strings[(ident, case_sens)] + else: + if self._requires_quotes(ident, getattr(obj, 'case_sensitive', ident == ident.lower())): + return self._quote_identifier(ident) + else: + return ident + + def should_quote(self, object): + return object.quote or self._requires_quotes(object.name, object.case_sensitive) + + def is_natural_case(self, object): + return object.quote or self._requires_quotes(object.name, object.case_sensitive) + + def format_sequence(self, sequence): + return self.__generic_obj_format(sequence, sequence.name) + + def format_label(self, label): + return self.__generic_obj_format(label, label.name) + + def format_alias(self, alias): + return self.__generic_obj_format(alias, alias.name) + + def format_table(self, table, use_schema=True): + """Prepare a quoted table and schema name""" + result = self.__generic_obj_format(table, table.name) + if use_schema and getattr(table, "schema", None): + result = self.__generic_obj_format(table, table.schema) + "." + result + return result + + def format_column(self, column, use_table=False): + """Prepare a quoted column name """ + # TODO: isinstance alert ! get ColumnClause and Column to better + # differentiate themselves + if isinstance(column, schema.SchemaItem): + if use_table: + return self.format_table(column.table, use_schema=False) + "." + self.__generic_obj_format(column, column.name) + else: + return self.__generic_obj_format(column, column.name) + else: + # literal textual elements get stuck into ColumnClause alot, which shouldnt get quoted + if use_table: + return column.table.name + "." + column.name + else: + return column.name + + def format_column_with_table(self, column): + """Prepare a quoted column name with table name""" + return self.format_column(column, use_table=True) + diff --git a/spyce-2.1/sqlalchemy/ansisql.pyc b/spyce-2.1/sqlalchemy/ansisql.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e91f0cd54ac27c0bea6fe3f02c1a19b15550f4ab Binary files /dev/null and b/spyce-2.1/sqlalchemy/ansisql.pyc differ diff --git a/spyce-2.1/sqlalchemy/databases/__init__.py b/spyce-2.1/sqlalchemy/databases/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..f009034c751b6f5337243da1c042529f640b5800 --- /dev/null +++ b/spyce-2.1/sqlalchemy/databases/__init__.py @@ -0,0 +1,8 @@ +# __init__.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +__all__ = ['oracle', 'postgres', 'sqlite', 'mysql', 'mssql'] diff --git a/spyce-2.1/sqlalchemy/databases/__init__.pyc b/spyce-2.1/sqlalchemy/databases/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1bfd6d4bc554d6769016101e96aa8b772487d201 Binary files /dev/null and b/spyce-2.1/sqlalchemy/databases/__init__.pyc differ diff --git a/spyce-2.1/sqlalchemy/databases/firebird.py b/spyce-2.1/sqlalchemy/databases/firebird.py new file mode 100755 index 0000000000000000000000000000000000000000..3a2129ff30e572bfb7b9536d1f98948a9ddfa8d6 --- /dev/null +++ b/spyce-2.1/sqlalchemy/databases/firebird.py @@ -0,0 +1,423 @@ +# firebird.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +import sys, StringIO, string, types + +from sqlalchemy import util +import sqlalchemy.engine.default as default +import sqlalchemy.sql as sql +import sqlalchemy.schema as schema +import sqlalchemy.ansisql as ansisql +import sqlalchemy.types as sqltypes +import sqlalchemy.exceptions as exceptions +try: + import kinterbasdb +except: + kinterbasdb = None + +dbmodule = kinterbasdb + +_initialized_kb = False + + +class FBNumeric(sqltypes.Numeric): + def get_col_spec(self): + return "NUMERIC(%(precision)s, %(length)s)" % {'precision': self.precision, 'length' : self.length} +class FBInteger(sqltypes.Integer): + def get_col_spec(self): + return "INTEGER" +class FBSmallInteger(sqltypes.Smallinteger): + def get_col_spec(self): + return "SMALLINT" +class FBDateTime(sqltypes.DateTime): + def get_col_spec(self): + return "TIMESTAMP" +class FBDate(sqltypes.DateTime): + def get_col_spec(self): + return "DATE" +class FBText(sqltypes.TEXT): + def get_col_spec(self): + return "BLOB SUB_TYPE 2" +class FBString(sqltypes.String): + def get_col_spec(self): + return "VARCHAR(%(length)s)" % {'length' : self.length} +class FBChar(sqltypes.CHAR): + def get_col_spec(self): + return "CHAR(%(length)s)" % {'length' : self.length} +class FBBinary(sqltypes.Binary): + def get_col_spec(self): + return "BLOB SUB_TYPE 1" +class FBBoolean(sqltypes.Boolean): + def get_col_spec(self): + return "SMALLINT" + +colspecs = { + sqltypes.Integer : FBInteger, + sqltypes.Smallinteger : FBSmallInteger, + sqltypes.Numeric : FBNumeric, + sqltypes.Float : FBNumeric, + sqltypes.DateTime : FBDateTime, + sqltypes.Date : FBDate, + sqltypes.String : FBString, + sqltypes.Binary : FBBinary, + sqltypes.Boolean : FBBoolean, + sqltypes.TEXT : FBText, + sqltypes.CHAR: FBChar, +} + +def descriptor(): + return {'name':'firebird', + 'description':'Firebird', + 'arguments':[ + ('host', 'Host Server Name', None), + ('database', 'Database Name', None), + ('user', 'Username', None), + ('password', 'Password', None) + ]} + + +class FireBirdExecutionContext(default.DefaultExecutionContext): + def supports_sane_rowcount(self): + return True + + +class FireBirdDialect(ansisql.ANSIDialect): + def __init__(self, module = None, **params): + global _initialized_kb + self.module = module or dbmodule + self.opts = {} + + if not _initialized_kb: + _initialized_kb = True + type_conv = params.get('type_conv', 200) or 200 + if isinstance(type_conv, types.StringTypes): + type_conv = int(type_conv) + + concurrency_level = params.get('concurrency_level', 1) or 1 + if isinstance(concurrency_level, types.StringTypes): + concurrency_level = int(concurrency_level) + + if kinterbasdb is not None: + kinterbasdb.init(type_conv=type_conv, concurrency_level=concurrency_level) + ansisql.ANSIDialect.__init__(self, **params) + + def create_connect_args(self, url): +# self.opts = url.translate_connect_args(['host', 'database', 'user', 'password']) + opts = url.translate_connect_args(['host', 'database', 'user', 'password', 'port']) + if opts.get('port'): + opts['host'] = "%s/%s" % (opts['host'], opts['port']) + del opts['port'] + opts.update(url.query) + # pop arguments that we took at the module level + opts.pop('type_conv', None) + opts.pop('concurrency_level', None) + self.opts = opts + + return ([], self.opts) + + def create_execution_context(self): + return FireBirdExecutionContext(self) + + def type_descriptor(self, typeobj): + return sqltypes.adapt_type(typeobj, colspecs) + + def supports_sane_rowcount(self): + return True + + def compiler(self, statement, bindparams, **kwargs): + return FBCompiler(self, statement, bindparams, **kwargs) + + def schemagenerator(self, *args, **kwargs): + return FBSchemaGenerator(*args, **kwargs) + + def schemadropper(self, *args, **kwargs): + return FBSchemaDropper(*args, **kwargs) + + def defaultrunner(self, engine, proxy): + return FBDefaultRunner(engine, proxy) + + def preparer(self): + return FBIdentifierPreparer(self) + + def has_table(self, connection, table_name): + tblqry = """\ + SELECT count(*) + FROM RDB$RELATIONS R + WHERE R.RDB$RELATION_NAME=?""" + + c = connection.execute(tblqry, [table_name.upper()]) + row = c.fetchone() + if row[0] > 0: + return True + else: + return False + + def reflecttable(self, connection, table): + #TODO: map these better + column_func = { + 14 : lambda r: sqltypes.String(r['FLEN']), # TEXT + 7 : lambda r: sqltypes.Integer(), # SHORT + 8 : lambda r: sqltypes.Integer(), # LONG + 9 : lambda r: sqltypes.Float(), # QUAD + 10 : lambda r: sqltypes.Float(), # FLOAT + 27 : lambda r: sqltypes.Float(), # DOUBLE + 35 : lambda r: sqltypes.DateTime(), # TIMESTAMP + 37 : lambda r: sqltypes.String(r['FLEN']), # VARYING + 261: lambda r: sqltypes.TEXT(), # BLOB + 40 : lambda r: sqltypes.Char(r['FLEN']), # CSTRING + 12 : lambda r: sqltypes.Date(), # DATE + 13 : lambda r: sqltypes.Time(), # TIME + 16 : lambda r: sqltypes.Numeric(precision=r['FPREC'], length=r['FSCALE'] * -1) #INT64 + } + tblqry = """\ + SELECT DISTINCT R.RDB$FIELD_NAME AS FNAME, + R.RDB$NULL_FLAG AS NULL_FLAG, + R.RDB$FIELD_POSITION, + F.RDB$FIELD_TYPE AS FTYPE, + F.RDB$FIELD_SUB_TYPE AS STYPE, + F.RDB$FIELD_LENGTH AS FLEN, + F.RDB$FIELD_PRECISION AS FPREC, + F.RDB$FIELD_SCALE AS FSCALE + FROM RDB$RELATION_FIELDS R + JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME + WHERE F.RDB$SYSTEM_FLAG=0 and R.RDB$RELATION_NAME=? + ORDER BY R.RDB$FIELD_POSITION""" + keyqry = """\ + SELECT SE.RDB$FIELD_NAME SENAME + FROM RDB$RELATION_CONSTRAINTS RC + JOIN RDB$INDEX_SEGMENTS SE + ON RC.RDB$INDEX_NAME=SE.RDB$INDEX_NAME + WHERE RC.RDB$CONSTRAINT_TYPE=? AND RC.RDB$RELATION_NAME=?""" + fkqry = """\ + SELECT RC.RDB$CONSTRAINT_NAME CNAME, + CSE.RDB$FIELD_NAME FNAME, + IX2.RDB$RELATION_NAME RNAME, + SE.RDB$FIELD_NAME SENAME + FROM RDB$RELATION_CONSTRAINTS RC + JOIN RDB$INDICES IX1 + ON IX1.RDB$INDEX_NAME=RC.RDB$INDEX_NAME + JOIN RDB$INDICES IX2 + ON IX2.RDB$INDEX_NAME=IX1.RDB$FOREIGN_KEY + JOIN RDB$INDEX_SEGMENTS CSE + ON CSE.RDB$INDEX_NAME=IX1.RDB$INDEX_NAME + JOIN RDB$INDEX_SEGMENTS SE + ON SE.RDB$INDEX_NAME=IX2.RDB$INDEX_NAME + WHERE RC.RDB$CONSTRAINT_TYPE=? AND RC.RDB$RELATION_NAME=? + ORDER BY SE.RDB$INDEX_NAME, SE.RDB$FIELD_POSITION""" + + # get primary key fields + c = connection.execute(keyqry, ["PRIMARY KEY", table.name.upper()]) + pkfields =[r['SENAME'] for r in c.fetchall()] + + # get all of the fields for this table + + def lower_if_possible(name): + # Remove trailing spaces: FB uses a CHAR() type, + # that is padded with spaces + name = name.rstrip() + # If its composed only by upper case chars, use + # the lowered version, otherwise keep the original + # (even if stripped...) + lname = name.lower() + if lname.upper() == name and not ' ' in name: + return lname + return name + + c = connection.execute(tblqry, [table.name.upper()]) + row = c.fetchone() + if not row: + raise exceptions.NoSuchTableError(table.name) + + while row: + name = row['FNAME'] + args = [lower_if_possible(name)] + + kw = {} + # get the data types and lengths + args.append(column_func[row['FTYPE']](row)) + + # is it a primary key? + kw['primary_key'] = name in pkfields + + table.append_column(schema.Column(*args, **kw)) + row = c.fetchone() + + # get the foreign keys + c = connection.execute(fkqry, ["FOREIGN KEY", table.name.upper()]) + fks = {} + while True: + row = c.fetchone() + if not row: break + + cname = lower_if_possible(row['CNAME']) + try: + fk = fks[cname] + except KeyError: + fks[cname] = fk = ([], []) + rname = lower_if_possible(row['RNAME']) + schema.Table(rname, table.metadata, autoload=True, autoload_with=connection) + fname = lower_if_possible(row['FNAME']) + refspec = rname + '.' + lower_if_possible(row['SENAME']) + fk[0].append(fname) + fk[1].append(refspec) + + for name,value in fks.iteritems(): + table.append_constraint(schema.ForeignKeyConstraint(value[0], value[1], name=name)) + + + def last_inserted_ids(self): + return self.context.last_inserted_ids + + + def do_execute(self, cursor, statement, parameters, **kwargs): + cursor.execute(statement, parameters or []) + + def do_rollback(self, connection): + connection.rollback(True) + + def do_commit(self, connection): + connection.commit(True) + + def connection(self): + """returns a managed DBAPI connection from this SQLEngine's connection pool.""" + c = self._pool.connect() + c.supportsTransactions = 0 + return c + + def dbapi(self): + return self.module + + +class FBCompiler(ansisql.ANSICompiler): + """firebird compiler modifies the lexical structure of Select statements to work under + non-ANSI configured Firebird databases, if the use_ansi flag is False.""" + + def __init__(self, dialect, statement, parameters, **kwargs): + self._outertable = None + super(FBCompiler, self).__init__(dialect, statement, parameters, **kwargs) + + + def visit_alias(self, alias): + # Override to not use the AS keyword which FB 1.5 does not like + self.froms[alias] = self.get_from_text(alias.original) + " " + self.preparer.format_alias(alias) + self.strings[alias] = self.get_str(alias.original) + + + def visit_column(self, column): + return ansisql.ANSICompiler.visit_column(self, column) + + + def visit_function(self, func): + if len(func.clauses): + super(FBCompiler, self).visit_function(func) + else: + self.strings[func] = func.name + + def visit_insert(self, insert): + """inserts are required to have the primary keys be explicitly present. + mapper will by default not put them in the insert statement to comply + with autoincrement fields that require they not be present. so, + put them all in for all primary key columns.""" + for c in insert.table.primary_key: + if not self.parameters.has_key(c.key): + self.parameters[c.key] = None + return ansisql.ANSICompiler.visit_insert(self, insert) + + def visit_select_precolumns(self, select): + """ called when building a SELECT statment, position is just before column list + Firebird puts the limit and offset right after the select...thanks for adding the + visit_select_precolumns!!!""" + result = "" + if select.limit: + result += " FIRST %d " % select.limit + if select.offset: + result +=" SKIP %d " % select.offset + if select.distinct: + result += " DISTINCT " + return result + + def limit_clause(self, select): + """Already taken care of in the visit_select_precolumns method.""" + return "" + +class FBSchemaGenerator(ansisql.ANSISchemaGenerator): + def get_column_specification(self, column, **kwargs): + colspec = self.preparer.format_column(column) + colspec += " " + column.type.engine_impl(self.engine).get_col_spec() + default = self.get_column_default_string(column) + if default is not None: + colspec += " DEFAULT " + default + + if not column.nullable: + colspec += " NOT NULL" + return colspec + + def visit_sequence(self, sequence): + self.append("CREATE GENERATOR %s" % sequence.name) + self.execute() + +class FBSchemaDropper(ansisql.ANSISchemaDropper): + def visit_sequence(self, sequence): + self.append("DROP GENERATOR %s" % sequence.name) + self.execute() + +class FBDefaultRunner(ansisql.ANSIDefaultRunner): + def exec_default_sql(self, default): + c = sql.select([default.arg], from_obj=["rdb$database"], engine=self.engine).compile() + return self.proxy(str(c), c.get_params()).fetchone()[0] + + def visit_sequence(self, seq): + return self.proxy("SELECT gen_id(" + seq.name + ", 1) FROM rdb$database").fetchone()[0] + +RESERVED_WORDS = util.Set( + ["action", "active", "add", "admin", "after", "all", "alter", "and", "any", + "as", "asc", "ascending", "at", "auto", "autoddl", "avg", "based", "basename", + "base_name", "before", "begin", "between", "bigint", "blob", "blobedit", "buffer", + "by", "cache", "cascade", "case", "cast", "char", "character", "character_length", + "char_length", "check", "check_point_len", "check_point_length", "close", "collate", + "collation", "column", "commit", "committed", "compiletime", "computed", "conditional", + "connect", "constraint", "containing", "continue", "count", "create", "cstring", + "current", "current_connection", "current_date", "current_role", "current_time", + "current_timestamp", "current_transaction", "current_user", "cursor", "database", + "date", "day", "db_key", "debug", "dec", "decimal", "declare", "default", "delete", + "desc", "descending", "describe", "descriptor", "disconnect", "display", "distinct", + "do", "domain", "double", "drop", "echo", "edit", "else", "end", "entry_point", + "escape", "event", "exception", "execute", "exists", "exit", "extern", "external", + "extract", "fetch", "file", "filter", "float", "for", "foreign", "found", "free_it", + "from", "full", "function", "gdscode", "generator", "gen_id", "global", "goto", + "grant", "group", "group_commit_", "group_commit_wait", "having", "help", "hour", + "if", "immediate", "in", "inactive", "index", "indicator", "init", "inner", "input", + "input_type", "insert", "int", "integer", "into", "is", "isolation", "isql", "join", + "key", "lc_messages", "lc_type", "left", "length", "lev", "level", "like", "logfile", + "log_buffer_size", "log_buf_size", "long", "manual", "max", "maximum", "maximum_segment", + "max_segment", "merge", "message", "min", "minimum", "minute", "module_name", "month", + "names", "national", "natural", "nchar", "no", "noauto", "not", "null", "numeric", + "num_log_buffers", "num_log_bufs", "octet_length", "of", "on", "only", "open", "option", + "or", "order", "outer", "output", "output_type", "overflow", "page", "pagelength", + "pages", "page_size", "parameter", "password", "plan", "position", "post_event", + "precision", "prepare", "primary", "privileges", "procedure", "protected", "public", + "quit", "raw_partitions", "rdb$db_key", "read", "real", "record_version", "recreate", + "references", "release", "release", "reserv", "reserving", "restrict", "retain", + "return", "returning_values", "returns", "revoke", "right", "role", "rollback", + "row_count", "runtime", "savepoint", "schema", "second", "segment", "select", + "set", "shadow", "shared", "shell", "show", "singular", "size", "smallint", + "snapshot", "some", "sort", "sqlcode", "sqlerror", "sqlwarning", "stability", + "starting", "starts", "statement", "static", "statistics", "sub_type", "sum", + "suspend", "table", "terminator", "then", "time", "timestamp", "to", "transaction", + "translate", "translation", "trigger", "trim", "type", "uncommitted", "union", + "unique", "update", "upper", "user", "using", "value", "values", "varchar", + "variable", "varying", "version", "view", "wait", "wait_time", "weekday", "when", + "whenever", "where", "while", "with", "work", "write", "year", "yearday" ]) + +class FBIdentifierPreparer(ansisql.ANSIIdentifierPreparer): + def __init__(self, dialect): + super(FBIdentifierPreparer,self).__init__(dialect, omit_schema=True) + + def _reserved_words(self): + return RESERVED_WORDS + +dialect = FireBirdDialect diff --git a/spyce-2.1/sqlalchemy/databases/information_schema.py b/spyce-2.1/sqlalchemy/databases/information_schema.py new file mode 100755 index 0000000000000000000000000000000000000000..5a7369ccda1701ac3bc2396c692b38f451692513 --- /dev/null +++ b/spyce-2.1/sqlalchemy/databases/information_schema.py @@ -0,0 +1,200 @@ +import sqlalchemy.sql as sql +import sqlalchemy.engine as engine +import sqlalchemy.schema as schema +import sqlalchemy.ansisql as ansisql +import sqlalchemy.types as sqltypes +import sqlalchemy.exceptions as exceptions +from sqlalchemy import * +from sqlalchemy.ansisql import * + +ischema = MetaData() + +schemata = schema.Table("schemata", ischema, + Column("catalog_name", String), + Column("schema_name", String), + Column("schema_owner", String), + schema="information_schema") + +tables = schema.Table("tables", ischema, + Column("table_catalog", String), + Column("table_schema", String), + Column("table_name", String), + Column("table_type", String), + schema="information_schema") + +columns = schema.Table("columns", ischema, + Column("table_schema", String), + Column("table_name", String), + Column("column_name", String), + Column("is_nullable", Integer), + Column("data_type", String), + Column("ordinal_position", Integer), + Column("character_maximum_length", Integer), + Column("numeric_precision", Integer), + Column("numeric_scale", Integer), + Column("column_default", Integer), + schema="information_schema") + +constraints = schema.Table("table_constraints", ischema, + Column("table_schema", String), + Column("table_name", String), + Column("constraint_name", String), + Column("constraint_type", String), + schema="information_schema") + +column_constraints = schema.Table("constraint_column_usage", ischema, + Column("table_schema", String), + Column("table_name", String), + Column("column_name", String), + Column("constraint_name", String), + schema="information_schema") + +pg_key_constraints = schema.Table("key_column_usage", ischema, + Column("table_schema", String), + Column("table_name", String), + Column("column_name", String), + Column("constraint_name", String), + Column("ordinal_position", Integer), + schema="information_schema") + +#mysql_key_constraints = schema.Table("key_column_usage", ischema, +# Column("table_schema", String), +# Column("table_name", String), +# Column("column_name", String), +# Column("constraint_name", String), +# Column("referenced_table_schema", String), +# Column("referenced_table_name", String), +# Column("referenced_column_name", String), +# schema="information_schema") + +key_constraints = pg_key_constraints + +ref_constraints = schema.Table("referential_constraints", ischema, + Column("constraint_catalog", String), + Column("constraint_schema", String), + Column("constraint_name", String), + Column("unique_constraint_catlog", String), + Column("unique_constraint_schema", String), + Column("unique_constraint_name", String), + Column("match_option", String), + Column("update_rule", String), + Column("delete_rule", String), + schema="information_schema") + +class ISchema(object): + def __init__(self, engine): + self.engine = engine + self.cache = {} + def __getattr__(self, name): + if name not in self.cache: + # This is a bit of a hack. + # It would probably be better to have a dict + # with just the information_schema tables at + # the module level, so as to avoid returning + # unrelated objects that happen to be named + # 'gen_*' + try: + gen_tbl = globals()['gen_'+name] + except KeyError: + raise ArgumentError('information_schema table %s not found' % name) + self.cache[name] = gen_tbl.toengine(self.engine) + return self.cache[name] + + +def reflecttable(connection, table, ischema_names): + + key_constraints = pg_key_constraints + + if table.schema is not None: + current_schema = table.schema + else: + current_schema = connection.default_schema_name() + + s = select([columns], + sql.and_(columns.c.table_name==table.name, + columns.c.table_schema==current_schema), + order_by=[columns.c.ordinal_position]) + + c = connection.execute(s) + found_table = False + while True: + row = c.fetchone() + if row is None: + break + #print "row! " + repr(row) + # continue + found_table = True + (name, type, nullable, charlen, numericprec, numericscale, default) = ( + row[columns.c.column_name], + row[columns.c.data_type], + row[columns.c.is_nullable] == 'YES', + row[columns.c.character_maximum_length], + row[columns.c.numeric_precision], + row[columns.c.numeric_scale], + row[columns.c.column_default] + ) + + args = [] + for a in (charlen, numericprec, numericscale): + if a is not None: + args.append(a) + coltype = ischema_names[type] + #print "coltype " + repr(coltype) + " args " + repr(args) + coltype = coltype(*args) + colargs= [] + if default is not None: + colargs.append(PassiveDefault(sql.text(default))) + table.append_column(schema.Column(name, coltype, nullable=nullable, *colargs)) + + if not found_table: + raise exceptions.NoSuchTableError(table.name) + + # we are relying on the natural ordering of the constraint_column_usage table to return the referenced columns + # in an order that corresponds to the ordinal_position in the key_constraints table, otherwise composite foreign keys + # wont reflect properly. dont see a way around this based on whats available from information_schema + s = select([constraints.c.constraint_name, constraints.c.constraint_type, constraints.c.table_name, key_constraints], use_labels=True, from_obj=[constraints.join(column_constraints, column_constraints.c.constraint_name==constraints.c.constraint_name).join(key_constraints, key_constraints.c.constraint_name==column_constraints.c.constraint_name)], order_by=[key_constraints.c.ordinal_position]) + s.append_column(column_constraints) + s.append_whereclause(constraints.c.table_name==table.name) + s.append_whereclause(constraints.c.table_schema==current_schema) + colmap = [constraints.c.constraint_type, key_constraints.c.column_name, column_constraints.c.table_schema, column_constraints.c.table_name, column_constraints.c.column_name, constraints.c.constraint_name, key_constraints.c.ordinal_position] + c = connection.execute(s) + + fks = {} + while True: + row = c.fetchone() + if row is None: + break + (type, constrained_column, referred_schema, referred_table, referred_column, constraint_name, ordinal_position) = ( + row[colmap[0]], + row[colmap[1]], + row[colmap[2]], + row[colmap[3]], + row[colmap[4]], + row[colmap[5]], + row[colmap[6]] + ) + #print "type %s on column %s to remote %s.%s.%s" % (type, constrained_column, referred_schema, referred_table, referred_column) + if type=='PRIMARY KEY': + table.primary_key.add(table.c[constrained_column]) + elif type=='FOREIGN KEY': + try: + fk = fks[constraint_name] + except KeyError: + fk = ([],[]) + fks[constraint_name] = fk + if current_schema == referred_schema: + referred_schema = table.schema + if referred_schema is not None: + schema.Table(referred_table, table.metadata, autoload=True, schema=referred_schema, autoload_with=connection) + refspec = ".".join([referred_schema, referred_table, referred_column]) + else: + schema.Table(referred_table, table.metadata, autoload=True, autoload_with=connection) + refspec = ".".join([referred_table, referred_column]) + if constrained_column not in fk[0]: + fk[0].append(constrained_column) + if refspec not in fk[1]: + fk[1].append(refspec) + + for name, value in fks.iteritems(): + table.append_constraint(ForeignKeyConstraint(value[0], value[1], name=name)) + diff --git a/spyce-2.1/sqlalchemy/databases/mssql.py b/spyce-2.1/sqlalchemy/databases/mssql.py new file mode 100755 index 0000000000000000000000000000000000000000..3cfffd302a1f2a5e1f66f53c9638fe45be94a3e1 --- /dev/null +++ b/spyce-2.1/sqlalchemy/databases/mssql.py @@ -0,0 +1,600 @@ +# mssql.py + +""" +notes: + supports both pymssql and adodbapi interfaces + + IDENTITY columns are supported by using SA schema.Sequence() objects. In other words: + Table('test', mss_engine, + Column('id', Integer, Sequence('blah',100,10), primary_key=True), + Column('name', String(20)) + ).create() + + would yield: + CREATE TABLE test ( + id INTEGER NOT NULL IDENTITY(100,10) PRIMARY KEY, + name VARCHAR(20) + ) + note that the start & increment values for sequences are optional and will default to 1,1 + + support for SET IDENTITY_INSERT ON mode (automagic on / off for INSERTs) + + support for auto-fetching of @@IDENTITY on insert + + select.limit implemented as SELECT TOP n + + +Known issues / TODO: + no support for more than one IDENTITY column per table + no support for table reflection of IDENTITY columns with (seed,increment) values other than (1,1) + no support for GUID type columns (yet) + pymssql has problems with transaction control that this module attempts to work around + pymssql has problems with binary and unicode data that this module does NOT work around + adodbapi fails testtypes.py unit test on unicode data too -- issue with the test? + +""" + +import sys, StringIO, string, types, re, datetime + +import sqlalchemy.sql as sql +import sqlalchemy.engine as engine +import sqlalchemy.engine.default as default +import sqlalchemy.schema as schema +import sqlalchemy.ansisql as ansisql +import sqlalchemy.types as sqltypes +import sqlalchemy.exceptions as exceptions + +try: + import adodbapi as dbmodule + # ADODBAPI has a non-standard Connection method + connect = dbmodule.Connection + make_connect_string = lambda keys: \ + [["Provider=SQLOLEDB;Data Source=%s;User Id=%s;Password=%s;Initial Catalog=%s" % ( + keys.get("host"), keys.get("user"), keys.get("password"), keys.get("database"))], {}] + do_commit = False + sane_rowcount = True +except: + try: + import pymssql as dbmodule + connect = dbmodule.connect + # pymmsql doesn't have a Binary method. we use string + dbmodule.Binary = lambda st: str(st) + def make_connect_string(keys): + if keys.get('port'): + # pymssql expects port as host:port, not a separate arg + keys['host'] = ''.join([keys.get('host', ''), ':', str(keys['port'])]) + del keys['port'] + return [[], keys] + do_commit = True + except: + dbmodule = None + make_connect_string = lambda keys: [[],{}] + sane_rowcount = False + +class MSNumeric(sqltypes.Numeric): + def convert_result_value(self, value, dialect): + return value + + def convert_bind_param(self, value, dialect): + if value is None: + # Not sure that this exception is needed + return value + else: + return str(value) + + def get_col_spec(self): + return "NUMERIC(%(precision)s, %(length)s)" % {'precision': self.precision, 'length' : self.length} + +class MSFloat(sqltypes.Float): + def get_col_spec(self): + return "FLOAT(%(precision)s)" % {'precision': self.precision} + def convert_bind_param(self, value, dialect): + """By converting to string, we can use Decimal types round-trip.""" + return str(value) + +class MSInteger(sqltypes.Integer): + def get_col_spec(self): + return "INTEGER" + +class MSTinyInteger(sqltypes.Integer): + def get_col_spec(self): + return "TINYINT" + +class MSSmallInteger(sqltypes.Smallinteger): + def get_col_spec(self): + return "SMALLINT" + +class MSDateTime(sqltypes.DateTime): + def __init__(self, *a, **kw): + super(MSDateTime, self).__init__(False) + + def get_col_spec(self): + return "DATETIME" + + def convert_bind_param(self, value, dialect): + if hasattr(value, "isoformat"): + #return value.isoformat(' ') + return value.strftime('%Y-%m-%d %H:%M:%S') # isoformat() bings on apodbapi -- reported/suggested by Peter Buschman + else: + return value + + def convert_result_value(self, value, dialect): + # adodbapi will return datetimes with empty time values as datetime.date() objects. Promote them back to full datetime.datetime() + if value and not hasattr(value, 'second'): + return datetime.datetime(value.year, value.month, value.day) + return value + +class MSDate(sqltypes.Date): + def __init__(self, *a, **kw): + super(MSDate, self).__init__(False) + + def get_col_spec(self): + return "SMALLDATETIME" + + def convert_bind_param(self, value, dialect): + if value and hasattr(value, "isoformat"): + return value.isoformat() + return value + + def convert_result_value(self, value, dialect): + # pymssql will return SMALLDATETIME values as datetime.datetime(), truncate it back to datetime.date() + if value and hasattr(value, 'second'): + return value.date() + return value + +class MSText(sqltypes.TEXT): + def get_col_spec(self): + return "TEXT" +class MSString(sqltypes.String): + def get_col_spec(self): + return "VARCHAR(%(length)s)" % {'length' : self.length} +class MSUnicode(sqltypes.Unicode): + def get_col_spec(self): + return "NVARCHAR(%(length)s)" % {'length' : self.length} +class MSChar(sqltypes.CHAR): + def get_col_spec(self): + return "CHAR(%(length)s)" % {'length' : self.length} +class MSNChar(sqltypes.NCHAR): + def get_col_spec(self): + return "NCHAR(%(length)s)" % {'length' : self.length} +class MSBinary(sqltypes.Binary): + def get_col_spec(self): + return "IMAGE" +class MSBoolean(sqltypes.Boolean): + def get_col_spec(self): + return "BIT" + def convert_result_value(self, value, dialect): + if value is None: + return None + return value and True or False + def convert_bind_param(self, value, dialect): + if value is True: + return 1 + elif value is False: + return 0 + elif value is None: + return None + else: + return value and True or False + +colspecs = { + sqltypes.Integer : MSInteger, + sqltypes.Smallinteger: MSSmallInteger, + sqltypes.Numeric : MSNumeric, + sqltypes.Float : MSFloat, + sqltypes.DateTime : MSDateTime, + sqltypes.Date : MSDate, + sqltypes.String : MSString, + sqltypes.Unicode : MSUnicode, + sqltypes.Binary : MSBinary, + sqltypes.Boolean : MSBoolean, + sqltypes.TEXT : MSText, + sqltypes.CHAR: MSChar, + sqltypes.NCHAR: MSNChar, +} + +ischema_names = { + 'int' : MSInteger, + 'smallint' : MSSmallInteger, + 'tinyint' : MSTinyInteger, + 'varchar' : MSString, + 'nvarchar' : MSUnicode, + 'char' : MSChar, + 'nchar' : MSNChar, + 'text' : MSText, + 'ntext' : MSText, + 'decimal' : MSNumeric, + 'numeric' : MSNumeric, + 'float' : MSFloat, + 'datetime' : MSDateTime, + 'smalldatetime' : MSDate, + 'binary' : MSBinary, + 'bit': MSBoolean, + 'real' : MSFloat, + 'image' : MSBinary +} + +def descriptor(): + return {'name':'mssql', + 'description':'MSSQL', + 'arguments':[ + ('user',"Database Username",None), + ('password',"Database Password",None), + ('db',"Database Name",None), + ('host',"Hostname", None), + ]} + +class MSSQLExecutionContext(default.DefaultExecutionContext): + def __init__(self, dialect): + self.IINSERT = self.HASIDENT = False + super(MSSQLExecutionContext, self).__init__(dialect) + + def pre_exec(self, engine, proxy, compiled, parameters, **kwargs): + """ MS-SQL has a special mode for inserting non-NULL values into IDENTITY columns. Activate it if the feature is turned on and needed. """ + if getattr(compiled, "isinsert", False): + self.IINSERT = False + self.HASIDENT = False + for c in compiled.statement.table.c: + if hasattr(c,'sequence'): + self.HASIDENT = True + if engine.dialect.auto_identity_insert: + if isinstance(parameters, list) and parameters[0].has_key(c.name): + self.IINSERT = True + elif parameters.has_key(c.name): + self.IINSERT = True + break + if self.IINSERT: + proxy("SET IDENTITY_INSERT %s ON" % compiled.statement.table.name) + super(MSSQLExecutionContext, self).pre_exec(engine, proxy, compiled, parameters, **kwargs) + + def post_exec(self, engine, proxy, compiled, parameters, **kwargs): + """ Turn off the INDENTITY_INSERT mode if it's been activated, and fetch recently inserted IDENTIFY values (works only for one column) """ + if getattr(compiled, "isinsert", False): + if self.IINSERT: + proxy("SET IDENTITY_INSERT %s OFF" % compiled.statement.table.name) + self.IINSERT = False + elif self.HASIDENT: + cursor = proxy("SELECT @@IDENTITY AS lastrowid") + row = cursor.fetchone() + self._last_inserted_ids = [int(row[0])] + # print "LAST ROW ID", self._last_inserted_ids + self.HASIDENT = False + + + + +class MSSQLDialect(ansisql.ANSIDialect): + def __init__(self, module=None, auto_identity_insert=False, **params): + self.module = module or dbmodule + self.auto_identity_insert = auto_identity_insert + ansisql.ANSIDialect.__init__(self, **params) + + def create_connect_args(self, url): + opts = url.translate_connect_args(['host', 'database', 'user', 'password', 'port']) + opts.update(url.query) + return make_connect_string(opts) + + def create_execution_context(self): + return MSSQLExecutionContext(self) + + def type_descriptor(self, typeobj): + return sqltypes.adapt_type(typeobj, colspecs) + + def last_inserted_ids(self): + return self.context.last_inserted_ids + + def supports_sane_rowcount(self): + return sane_rowcount + + def compiler(self, statement, bindparams, **kwargs): + return MSSQLCompiler(self, statement, bindparams, **kwargs) + + def schemagenerator(self, *args, **kwargs): + return MSSQLSchemaGenerator(*args, **kwargs) + + def schemadropper(self, *args, **kwargs): + return MSSQLSchemaDropper(*args, **kwargs) + + def defaultrunner(self, engine, proxy): + return MSSQLDefaultRunner(engine, proxy) + + def preparer(self): + return MSSQLIdentifierPreparer(self) + + def get_default_schema_name(self): + return "dbo" + + def last_inserted_ids(self): + return self.context.last_inserted_ids + + def do_begin(self, connection): + """implementations might want to put logic here for turning autocommit on/off, etc.""" + if do_commit: + pass + + def _execute(self, c, statement, parameters): + try: + c.execute(statement, parameters) + self.context.rowcount = c.rowcount + c.DBPROP_COMMITPRESERVE = "Y" + except Exception, e: + raise exceptions.SQLError(statement, parameters, e) + + + + def do_rollback(self, connection): + """implementations might want to put logic here for turning autocommit on/off, etc.""" + if do_commit: + try: + # connection.rollback() for pymmsql failed sometimes--the begin tran doesn't show up + # this is a workaround that seems to be handle it. + r = self.raw_connection(connection) + r.query("if @@trancount > 0 rollback tran") + r.fetch_array() + r.query("begin tran") + r.fetch_array() + except: + pass + try: + del connection + except: + raise + + def raw_connection(self, connection): + """Pull the raw pymmsql connection out--sensative to "pool.ConnectionFairy" and pymssql.pymssqlCnx Classes""" + try: + return connection.connection.__dict__['_pymssqlCnx__cnx'] + except: + return connection.connection.adoConn + + def do_commit(self, connection): + """implementations might want to put logic here for turning autocommit on/off, etc. + do_commit is set for pymmsql connections--ADO seems to handle transactions without any issue + """ + # ADO Uses Implicit Transactions. + if do_commit: + # This is very pymssql specific. We use this instead of its commit, because it hangs on failed rollbacks. + # By using the "if" we don't assume an open transaction--much better. + r = self.raw_connection(connection) + r.query("if @@trancount > 0 commit tran") + r.fetch_array() + r.query("begin tran") + r.fetch_array() + else: + pass + #connection.supportsTransactions = 1 + try: + pass + #connection.adoConn.CommitTrans() + except: + pass + #connection.adoConn.execute("begin trans", {}) + #connection.adoConn.BeginTrans() + + def connection(self): + """returns a managed DBAPI connection from this SQLEngine's connection pool.""" + c = self._pool.connect() + c.supportsTransactions = 0 + return c + + + def dbapi(self): + return self.module + + def uppercase_table(self, t): + # convert all names to uppercase -- fixes refs to INFORMATION_SCHEMA for case-senstive DBs, and won't matter for case-insensitive + t.name = t.name.upper() + if t.schema: + t.schema = t.schema.upper() + for c in t.columns: + c.name = c.name.upper() + return t + + def has_table(self, connection, tablename): + import sqlalchemy.databases.information_schema as ischema + + current_schema = self.get_default_schema_name() + columns = self.uppercase_table(ischema.columns) + s = sql.select([columns], + current_schema + and sql.and_(columns.c.table_name==tablename, columns.c.table_schema==current_schema) + or columns.c.table_name==tablename, + ) + + c = connection.execute(s) + row = c.fetchone() + return row is not None + + def reflecttable(self, connection, table): + import sqlalchemy.databases.information_schema as ischema + + # Get base columns + if table.schema is not None: + current_schema = table.schema + else: + current_schema = self.get_default_schema_name() + + columns = self.uppercase_table(ischema.columns) + s = sql.select([columns], + current_schema + and sql.and_(columns.c.table_name==table.name, columns.c.table_schema==current_schema) + or columns.c.table_name==table.name, + order_by=[columns.c.ordinal_position]) + + c = connection.execute(s) + found_table = False + while True: + row = c.fetchone() + if row is None: + break + found_table = True + (name, type, nullable, charlen, numericprec, numericscale, default) = ( + row[columns.c.column_name], + row[columns.c.data_type], + row[columns.c.is_nullable] == 'YES', + row[columns.c.character_maximum_length], + row[columns.c.numeric_precision], + row[columns.c.numeric_scale], + row[columns.c.column_default] + ) + + args = [] + for a in (charlen, numericprec, numericscale): + if a is not None: + args.append(a) + coltype = ischema_names[type] + coltype = coltype(*args) + colargs= [] + if default is not None: + colargs.append(schema.PassiveDefault(sql.text(default))) + + table.append_column(schema.Column(name, coltype, nullable=nullable, *colargs)) + + if not found_table: + raise exceptions.NoSuchTableError(table.name) + + # We also run an sp_columns to check for identity columns: + # FIXME: note that this only fetches the existence of an identity column, not it's properties like (seed, increment) + # also, add a check to make sure we specify the schema name of the table + # cursor = table.engine.execute("sp_columns " + table.name, {}) + cursor = connection.execute("sp_columns " + table.name) + while True: + row = cursor.fetchone() + if row is None: + break + col_name, type_name = row[3], row[5] + if type_name.endswith("identity"): + ic = table.c[col_name] + # setup a psuedo-sequence to represent the identity attribute - we interpret this at table.create() time as the identity attribute + ic.sequence = schema.Sequence(ic.name + '_identity') + + # Add constraints + RR = self.uppercase_table(ischema.ref_constraints) #information_schema.referential_constraints + TC = self.uppercase_table(ischema.constraints) #information_schema.table_constraints + C = self.uppercase_table(ischema.column_constraints).alias('C') #information_schema.constraint_column_usage: the constrained column + R = self.uppercase_table(ischema.column_constraints).alias('R') #information_schema.constraint_column_usage: the referenced column + + # Primary key constraints + s = sql.select([C.c.column_name, TC.c.constraint_type], sql.and_(TC.c.constraint_name == C.c.constraint_name, + C.c.table_name == table.name)) + c = connection.execute(s) + for row in c: + if 'PRIMARY' in row[TC.c.constraint_type.name]: + table.primary_key.add(table.c[row[0]]) + + + # Foreign key constraints + s = sql.select([C.c.column_name, + R.c.table_schema, R.c.table_name, R.c.column_name, + RR.c.constraint_name, RR.c.match_option, RR.c.update_rule, RR.c.delete_rule], + sql.and_(C.c.table_name == table.name, + C.c.constraint_name == RR.c.constraint_name, + R.c.constraint_name == RR.c.unique_constraint_name + ), + order_by = [RR.c.constraint_name]) + rows = connection.execute(s).fetchall() + + # group rows by constraint ID, to handle multi-column FKs + fknm, scols, rcols = (None, [], []) + for r in rows: + scol, rschema, rtbl, rcol, rfknm, fkmatch, fkuprule, fkdelrule = r + if rfknm != fknm: + if fknm: + table.append_constraint(schema.ForeignKeyConstraint(scols, ['%s.%s' % (t,c) for (s,t,c) in rcols], fknm)) + fknm, scols, rcols = (rfknm, [], []) + if (not scol in scols): scols.append(scol) + if (not (rschema, rtbl, rcol) in rcols): rcols.append((rschema, rtbl, rcol)) + + if fknm and scols: + table.append_constraint(schema.ForeignKeyConstraint(scols, ['%s.%s' % (t,c) for (s,t,c) in rcols], fknm)) + + + +class MSSQLCompiler(ansisql.ANSICompiler): + def __init__(self, dialect, statement, parameters, **kwargs): + super(MSSQLCompiler, self).__init__(dialect, statement, parameters, **kwargs) + self.tablealiases = {} + + def visit_select_precolumns(self, select): + """ MS-SQL puts TOP, it's version of LIMIT here """ + s = select.distinct and "DISTINCT " or "" + if (select.limit): + s += "TOP %s " % (select.limit,) + return s + + def limit_clause(self, select): + # Limit in mssql is after the select keyword; MSsql has no support for offset + return "" + + + def visit_table(self, table): + # alias schema-qualified tables + if getattr(table, 'schema', None) is not None and not self.tablealiases.has_key(table): + alias = table.alias() + self.tablealiases[table] = alias + alias.accept_visitor(self) + self.froms[('alias', table)] = self.froms[table] + for c in alias.c: + c.accept_visitor(self) + self.tablealiases[alias] = self.froms[table] + self.froms[table] = self.froms[alias] + else: + super(MSSQLCompiler, self).visit_table(table) + + def visit_alias(self, alias): + # translate for schema-qualified table aliases + if self.froms.has_key(('alias', alias.original)): + self.froms[alias] = self.froms[('alias', alias.original)] + " AS " + alias.name + self.strings[alias] = "" + else: + super(MSSQLCompiler, self).visit_alias(alias) + + def visit_column(self, column): + # translate for schema-qualified table aliases + super(MSSQLCompiler, self).visit_column(column) + if column.table is not None and self.tablealiases.has_key(column.table): + self.strings[column] = \ + self.strings[self.tablealiases[column.table].corresponding_column(column)] + + +class MSSQLSchemaGenerator(ansisql.ANSISchemaGenerator): + def get_column_specification(self, column, **kwargs): + colspec = self.preparer.format_column(column) + " " + column.type.engine_impl(self.engine).get_col_spec() + + # install a IDENTITY Sequence if we have an implicit IDENTITY column + if column.primary_key and column.autoincrement and isinstance(column.type, sqltypes.Integer) and not column.foreign_key: + if column.default is None or (isinstance(column.default, schema.Sequence) and column.default.optional): + column.sequence = schema.Sequence(column.name + '_seq') + + if not column.nullable: + colspec += " NOT NULL" + + if hasattr(column, 'sequence'): + colspec += " IDENTITY(%s,%s)" % (column.sequence.start or 1, column.sequence.increment or 1) + else: + default = self.get_column_default_string(column) + if default is not None: + colspec += " DEFAULT " + default + + return colspec + + +class MSSQLSchemaDropper(ansisql.ANSISchemaDropper): + def visit_index(self, index): + self.append("\nDROP INDEX " + index.table.name + "." + index.name) + self.execute() + +class MSSQLDefaultRunner(ansisql.ANSIDefaultRunner): + pass + +class MSSQLIdentifierPreparer(ansisql.ANSIIdentifierPreparer): + def __init__(self, dialect): + super(MSSQLIdentifierPreparer, self).__init__(dialect, initial_quote='[', final_quote=']') + def _escape_identifier(self, value): + #TODO: determin MSSQL's escapeing rules + return value + def _fold_identifier_case(self, value): + #TODO: determin MSSQL's case folding rules + return value + +dialect = MSSQLDialect diff --git a/spyce-2.1/sqlalchemy/databases/mysql.py b/spyce-2.1/sqlalchemy/databases/mysql.py new file mode 100755 index 0000000000000000000000000000000000000000..3253474643c9301781fe3e272f01f95e99961cd4 --- /dev/null +++ b/spyce-2.1/sqlalchemy/databases/mysql.py @@ -0,0 +1,476 @@ +# mysql.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +import sys, StringIO, string, types, re, datetime + +from sqlalchemy import sql,engine,schema,ansisql +from sqlalchemy.engine import default +import sqlalchemy.types as sqltypes +import sqlalchemy.exceptions as exceptions + +try: + import MySQLdb as mysql +except: + mysql = None + +def kw_colspec(self, spec): + if self.unsigned: + spec += ' UNSIGNED' + if self.zerofill: + spec += ' ZEROFILL' + return spec + +class MSNumeric(sqltypes.Numeric): + def __init__(self, precision = 10, length = 2, **kw): + self.unsigned = 'unsigned' in kw + self.zerofill = 'zerofill' in kw + super(MSNumeric, self).__init__(precision, length) + def get_col_spec(self): + return kw_colspec(self, "NUMERIC(%(precision)s, %(length)s)" % {'precision': self.precision, 'length' : self.length}) +class MSDecimal(MSNumeric): + def get_col_spec(self): + if self.precision is not None and self.length is not None: + return kw_colspec(self, "DECIMAL(%(precision)s, %(length)s)" % {'precision': self.precision, 'length' : self.length}) +class MSDouble(MSNumeric): + def __init__(self, precision=10, length=2, **kw): + if (precision is None and length is not None) or (precision is not None and length is None): + raise exceptions.ArgumentError("You must specify both precision and length or omit both altogether.") + self.unsigned = 'unsigned' in kw + self.zerofill = 'zerofill' in kw + super(MSDouble, self).__init__(precision, length) + def get_col_spec(self): + if self.precision is not None and self.length is not None: + return "DOUBLE(%(precision)s, %(length)s)" % {'precision': self.precision, 'length' : self.length} + else: + return kw_colspec(self, "DOUBLE") +class MSFloat(sqltypes.Float): + def __init__(self, precision=10, length=None, **kw): + if length is not None: + self.length=length + self.unsigned = 'unsigned' in kw + self.zerofill = 'zerofill' in kw + super(MSFloat, self).__init__(precision) + def get_col_spec(self): + if hasattr(self, 'length') and self.length is not None: + return kw_colspec(self, "FLOAT(%(precision)s,%(length)s)" % {'precision': self.precision, 'length' : self.length}) + elif self.precision is not None: + return kw_colspec(self, "FLOAT(%(precision)s)" % {'precision': self.precision}) + else: + return kw_colspec(self, "FLOAT") +class MSInteger(sqltypes.Integer): + def __init__(self, length=None, **kw): + self.length = length + self.unsigned = 'unsigned' in kw + self.zerofill = 'zerofill' in kw + super(MSInteger, self).__init__() + def get_col_spec(self): + if self.length is not None: + return kw_colspec(self, "INTEGER(%(length)s)" % {'length': self.length}) + else: + return kw_colspec(self, "INTEGER") +class MSBigInteger(MSInteger): + def get_col_spec(self): + if self.length is not None: + return kw_colspec(self, "BIGINT(%(length)s)" % {'length': self.length}) + else: + return kw_colspec(self, "BIGINT") +class MSSmallInteger(sqltypes.Smallinteger): + def __init__(self, length=None, **kw): + self.length = length + self.unsigned = 'unsigned' in kw + self.zerofill = 'zerofill' in kw + super(MSSmallInteger, self).__init__() + def get_col_spec(self): + if self.length is not None: + return kw_colspec(self, "SMALLINT(%(length)s)" % {'length': self.length}) + else: + return kw_colspec(self, "SMALLINT") +class MSDateTime(sqltypes.DateTime): + def get_col_spec(self): + return "DATETIME" +class MSDate(sqltypes.Date): + def get_col_spec(self): + return "DATE" +class MSTime(sqltypes.Time): + def get_col_spec(self): + return "TIME" + def convert_result_value(self, value, dialect): + # convert from a timedelta value + if value is not None: + return datetime.time(value.seconds/60/60, value.seconds/60%60, value.seconds - (value.seconds/60*60)) + else: + return None + +class MSText(sqltypes.TEXT): + def __init__(self, **kw): + self.binary = 'binary' in kw + super(MSText, self).__init__() + def get_col_spec(self): + return "TEXT" +class MSTinyText(MSText): + def get_col_spec(self): + if self.binary: + return "TEXT BINARY" + else: + return "TEXT" +class MSMediumText(MSText): + def get_col_spec(self): + if self.binary: + return "MEDIUMTEXT BINARY" + else: + return "MEDIUMTEXT" +class MSLongText(MSText): + def get_col_spec(self): + if self.binary: + return "LONGTEXT BINARY" + else: + return "LONGTEXT" +class MSString(sqltypes.String): + def __init__(self, length=None, *extra): + sqltypes.String.__init__(self, length=length) + def get_col_spec(self): + return "VARCHAR(%(length)s)" % {'length' : self.length} +class MSChar(sqltypes.CHAR): + def get_col_spec(self): + return "CHAR(%(length)s)" % {'length' : self.length} +class MSBinary(sqltypes.Binary): + def get_col_spec(self): + if self.length is not None and self.length <=255: + # the binary2G type seems to return a value that is null-padded + return "BINARY(%d)" % self.length + else: + return "BLOB" + def convert_result_value(self, value, dialect): + if value is None: + return None + else: + return buffer(value) + +class MSMediumBlob(MSBinary): + def get_col_spec(self): + return "MEDIUMBLOB" + +class MSEnum(MSString): + def __init__(self, *enums): + self.__enums_hidden = enums + length = 0 + strip_enums = [] + for a in enums: + if a[0:1] == '"' or a[0:1] == "'": + a = a[1:-1] + if len(a) > length: + length=len(a) + strip_enums.append(a) + self.enums = strip_enums + super(MSEnum, self).__init__(length) + def get_col_spec(self): + return "ENUM(%s)" % ",".join(self.__enums_hidden) + + +class MSBoolean(sqltypes.Boolean): + def get_col_spec(self): + return "BOOLEAN" + def convert_result_value(self, value, dialect): + if value is None: + return None + return value and True or False + def convert_bind_param(self, value, dialect): + if value is True: + return 1 + elif value is False: + return 0 + elif value is None: + return None + else: + return value and True or False + +colspecs = { +# sqltypes.BIGinteger : MSInteger, + sqltypes.Integer : MSInteger, + sqltypes.Smallinteger : MSSmallInteger, + sqltypes.Numeric : MSNumeric, + sqltypes.Float : MSFloat, + sqltypes.DateTime : MSDateTime, + sqltypes.Date : MSDate, + sqltypes.Time : MSTime, + sqltypes.String : MSString, + sqltypes.Binary : MSBinary, + sqltypes.Boolean : MSBoolean, + sqltypes.TEXT : MSText, + sqltypes.CHAR: MSChar, +} + +ischema_names = { + 'bigint' : MSBigInteger, + 'int' : MSInteger, + 'mediumint' : MSInteger, + 'smallint' : MSSmallInteger, + 'tinyint' : MSSmallInteger, + 'varchar' : MSString, + 'char' : MSChar, + 'text' : MSText, + 'tinytext' : MSTinyText, + 'mediumtext': MSMediumText, + 'longtext': MSLongText, + 'decimal' : MSDecimal, + 'numeric' : MSNumeric, + 'float' : MSFloat, + 'double' : MSDouble, + 'timestamp' : MSDateTime, + 'datetime' : MSDateTime, + 'date' : MSDate, + 'time' : MSTime, + 'binary' : MSBinary, + 'blob' : MSBinary, + 'enum': MSEnum, +} + +def descriptor(): + return {'name':'mysql', + 'description':'MySQL', + 'arguments':[ + ('username',"Database Username",None), + ('password',"Database Password",None), + ('database',"Database Name",None), + ('host',"Hostname", None), + ]} + + +class MySQLExecutionContext(default.DefaultExecutionContext): + def post_exec(self, engine, proxy, compiled, parameters, **kwargs): + if getattr(compiled, "isinsert", False): + self._last_inserted_ids = [proxy().lastrowid] + +class MySQLDialect(ansisql.ANSIDialect): + def __init__(self, module = None, **kwargs): + if module is None: + self.module = mysql + else: + self.module = module + ansisql.ANSIDialect.__init__(self, **kwargs) + + def create_connect_args(self, url): + opts = url.translate_connect_args(['host', 'db', 'user', 'passwd', 'port']) + opts.update(url.query) + def coercetype(param, type): + if param in opts and type(param) is not type: + if type is bool: + opts[param] = bool(int(opts[param])) + else: + opts[param] = type(opts[param]) + coercetype('compress', bool) + coercetype('connect_timeout', int) + coercetype('use_unicode', bool) # this could break SA Unicode type + coercetype('charset', str) # this could break SA Unicode type + # TODO: what about options like "ssl", "cursorclass" and "conv" ? + return [[], opts] + + def create_execution_context(self): + return MySQLExecutionContext(self) + + def type_descriptor(self, typeobj): + return sqltypes.adapt_type(typeobj, colspecs) + + def supports_sane_rowcount(self): + return False + + def compiler(self, statement, bindparams, **kwargs): + return MySQLCompiler(self, statement, bindparams, **kwargs) + + def schemagenerator(self, *args, **kwargs): + return MySQLSchemaGenerator(*args, **kwargs) + + def schemadropper(self, *args, **kwargs): + return MySQLSchemaDropper(*args, **kwargs) + + def preparer(self): + return MySQLIdentifierPreparer(self) + + def do_rollback(self, connection): + # some versions of MySQL just dont support rollback() at all.... + try: + connection.rollback() + except: + pass + + def get_default_schema_name(self): + if not hasattr(self, '_default_schema_name'): + self._default_schema_name = text("select database()", self).scalar() + return self._default_schema_name + + def dbapi(self): + return self.module + + def has_table(self, connection, table_name): + cursor = connection.execute("show table status like '" + table_name + "'") + return bool( not not cursor.rowcount ) + + def reflecttable(self, connection, table): + # reference: http://dev.mysql.com/doc/refman/5.0/en/name-case-sensitivity.html + case_sensitive = int(connection.execute("show variables like 'lower_case_table_names'").fetchone()[1]) == 0 + if not case_sensitive: + table.name = table.name.lower() + table.metadata.tables[table.name]= table + try: + c = connection.execute("describe " + table.fullname, {}) + except: + raise exceptions.NoSuchTableError(table.name) + found_table = False + while True: + row = c.fetchone() + if row is None: + break + #print "row! " + repr(row) + if not found_table: + found_table = True + + # these can come back as unicode if use_unicode=1 in the mysql connection + (name, type, nullable, primary_key, default) = (str(row[0]), str(row[1]), row[2] == 'YES', row[3] == 'PRI', row[4]) + + match = re.match(r'(\w+)(\(.*?\))?\s*(\w+)?\s*(\w+)?', type) + col_type = match.group(1) + args = match.group(2) + extra_1 = match.group(3) + extra_2 = match.group(4) + + #print "coltype: " + repr(col_type) + " args: " + repr(args) + "extras:" + repr(extra_1) + ' ' + repr(extra_2) + coltype = ischema_names.get(col_type, MSString) + kw = {} + if extra_1 is not None: + kw[extra_1] = True + if extra_2 is not None: + kw[extra_2] = True + + if args is not None: + if col_type == 'enum': + args= args[1:-1] + argslist = args.split(',') + coltype = coltype(*argslist, **kw) + else: + argslist = re.findall(r'(\d+)', args) + coltype = coltype(*[int(a) for a in argslist], **kw) + + colargs= [] + if default: + colargs.append(schema.PassiveDefault(sql.text(default))) + table.append_column(schema.Column(name, coltype, *colargs, + **dict(primary_key=primary_key, + nullable=nullable, + ))) + + tabletype = self.moretableinfo(connection, table=table) + table.kwargs['mysql_engine'] = tabletype + + if not found_table: + raise exceptions.NoSuchTableError(table.name) + + def moretableinfo(self, connection, table): + """Return (tabletype, {colname:foreignkey,...}) + execute(SHOW CREATE TABLE child) => + CREATE TABLE `child` ( + `id` int(11) default NULL, + `parent_id` int(11) default NULL, + KEY `par_ind` (`parent_id`), + CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`) ON DELETE CASCADE\n) TYPE=InnoDB + """ + c = connection.execute("SHOW CREATE TABLE " + table.fullname, {}) + desc_fetched = c.fetchone()[1] + + # this can come back as unicode if use_unicode=1 in the mysql connection + if type(desc_fetched) is unicode: + desc_fetched = str(desc_fetched) + elif type(desc_fetched) is not str: + # may get array.array object here, depending on version (such as mysql 4.1.14 vs. 4.1.11) + desc_fetched = desc_fetched.tostring() + desc = desc_fetched.strip() + + tabletype = '' + lastparen = re.search(r'\)[^\)]*\Z', desc) + if lastparen: + match = re.search(r'\b(?:TYPE|ENGINE)=(?P.+)\b', desc[lastparen.start():], re.I) + if match: + tabletype = match.group('ttype') + + fkpat = r'CONSTRAINT `(?P.+?)` FOREIGN KEY \((?P.+?)\) REFERENCES `(?P.+?)` \((?P.+?)\)' + for match in re.finditer(fkpat, desc): + columns = re.findall(r'`(.+?)`', match.group('columns')) + refcols = [match.group('reftable') + "." + x for x in re.findall(r'`(.+?)`', match.group('refcols'))] + schema.Table(match.group('reftable'), table.metadata, autoload=True, autoload_with=connection) + constraint = schema.ForeignKeyConstraint(columns, refcols, name=match.group('name')) + table.append_constraint(constraint) + + return tabletype + + +class MySQLCompiler(ansisql.ANSICompiler): + + def visit_cast(self, cast): + """hey ho MySQL supports almost no types at all for CAST""" + if (isinstance(cast.type, sqltypes.Date) or isinstance(cast.type, sqltypes.Time) or isinstance(cast.type, sqltypes.DateTime)): + return super(MySQLCompiler, self).visit_cast(cast) + else: + # so just skip the CAST altogether for now. + # TODO: put whatever MySQL does for CAST here. + self.strings[cast] = self.strings[cast.clause] + + def for_update_clause(self, select): + if select.for_update == 'read': + return ' LOCK IN SHARE MODE' + else: + return super(MySQLCompiler, self).for_update_clause(select) + + def limit_clause(self, select): + text = "" + if select.limit is not None: + text += " \n LIMIT " + str(select.limit) + if select.offset is not None: + if select.limit is None: + # striaght from the MySQL docs, I kid you not + text += " \n LIMIT 18446744073709551615" + text += " OFFSET " + str(select.offset) + return text + +class MySQLSchemaGenerator(ansisql.ANSISchemaGenerator): + def get_column_specification(self, column, override_pk=False, first_pk=False): + t = column.type.engine_impl(self.engine) + colspec = self.preparer.format_column(column) + " " + column.type.engine_impl(self.engine).get_col_spec() + default = self.get_column_default_string(column) + if default is not None: + colspec += " DEFAULT " + default + + if not column.nullable: + colspec += " NOT NULL" + if column.primary_key: + if len(column.foreign_keys)==0 and first_pk and column.autoincrement and isinstance(column.type, sqltypes.Integer): + colspec += " AUTO_INCREMENT" + return colspec + + def post_create_table(self, table): + mysql_engine = table.kwargs.get('mysql_engine', None) + if mysql_engine is not None: + return " TYPE=%s" % mysql_engine + else: + return "" + +class MySQLSchemaDropper(ansisql.ANSISchemaDropper): + def visit_index(self, index): + self.append("\nDROP INDEX " + index.name + " ON " + index.table.name) + self.execute() + def drop_foreignkey(self, constraint): + self.append("ALTER TABLE %s DROP FOREIGN KEY %s" % (self.preparer.format_table(constraint.table), constraint.name)) + self.execute() + +class MySQLIdentifierPreparer(ansisql.ANSIIdentifierPreparer): + def __init__(self, dialect): + super(MySQLIdentifierPreparer, self).__init__(dialect, initial_quote='`') + def _escape_identifier(self, value): + #TODO: determin MySQL's escaping rules + return value + def _fold_identifier_case(self, value): + #TODO: determin MySQL's case folding rules + return value + +dialect = MySQLDialect diff --git a/spyce-2.1/sqlalchemy/databases/oracle.py b/spyce-2.1/sqlalchemy/databases/oracle.py new file mode 100755 index 0000000000000000000000000000000000000000..107dea1634afe23ea858d39c81afe368db85318d --- /dev/null +++ b/spyce-2.1/sqlalchemy/databases/oracle.py @@ -0,0 +1,460 @@ +# oracle.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +import sys, StringIO, string, re + +import sqlalchemy.util as util +import sqlalchemy.sql as sql +import sqlalchemy.engine as engine +import sqlalchemy.engine.default as default +import sqlalchemy.schema as schema +import sqlalchemy.ansisql as ansisql +import sqlalchemy.types as sqltypes +import sqlalchemy.exceptions as exceptions + +try: + import cx_Oracle +except: + cx_Oracle = None + +class OracleNumeric(sqltypes.Numeric): + def get_col_spec(self): + return "NUMERIC(%(precision)s, %(length)s)" % {'precision': self.precision, 'length' : self.length} +class OracleInteger(sqltypes.Integer): + def get_col_spec(self): + return "INTEGER" +class OracleSmallInteger(sqltypes.Smallinteger): + def get_col_spec(self): + return "SMALLINT" +class OracleDateTime(sqltypes.DateTime): + def get_col_spec(self): + return "DATE" +# Note: +# Oracle DATE == DATETIME +# Oracle does not allow milliseconds in DATE +# Oracle does not support TIME columns + +# only if cx_oracle contains TIMESTAMP +class OracleTimestamp(sqltypes.DateTime): + def get_col_spec(self): + return "TIMESTAMP" + def get_dbapi_type(self, dialect): + return dialect.TIMESTAMP + +class OracleText(sqltypes.TEXT): + def get_col_spec(self): + return "CLOB" +class OracleString(sqltypes.String): + def get_col_spec(self): + return "VARCHAR(%(length)s)" % {'length' : self.length} +class OracleChar(sqltypes.CHAR): + def get_col_spec(self): + return "CHAR(%(length)s)" % {'length' : self.length} +class OracleBinary(sqltypes.Binary): + def get_col_spec(self): + return "BLOB" +class OracleBoolean(sqltypes.Boolean): + def get_col_spec(self): + return "SMALLINT" + def convert_result_value(self, value, dialect): + if value is None: + return None + return value and True or False + def convert_bind_param(self, value, dialect): + if value is True: + return 1 + elif value is False: + return 0 + elif value is None: + return None + else: + return value and True or False + + +colspecs = { + sqltypes.Integer : OracleInteger, + sqltypes.Smallinteger : OracleSmallInteger, + sqltypes.Numeric : OracleNumeric, + sqltypes.Float : OracleNumeric, + sqltypes.DateTime : OracleDateTime, + sqltypes.Date : OracleDateTime, + sqltypes.String : OracleString, + sqltypes.Binary : OracleBinary, + sqltypes.Boolean : OracleBoolean, + sqltypes.TEXT : OracleText, + sqltypes.TIMESTAMP : OracleTimestamp, + sqltypes.CHAR: OracleChar, +} + +ischema_names = { + 'VARCHAR2' : OracleString, + 'DATE' : OracleDateTime, + 'DATETIME' : OracleDateTime, + 'NUMBER' : OracleNumeric, + 'BLOB' : OracleBinary, + 'CLOB' : OracleText, + 'TIMESTAMP' : OracleTimestamp +} + +constraintSQL = """SELECT + ac.constraint_name, + ac.constraint_type, + LOWER(loc.column_name) AS local_column, + LOWER(rem.table_name) AS remote_table, + LOWER(rem.column_name) AS remote_column +FROM all_constraints ac, + all_cons_columns loc, + all_cons_columns rem +WHERE ac.table_name = :table_name +AND ac.constraint_type IN ('R','P') +AND ac.owner = :owner +AND ac.owner = loc.owner +AND ac.constraint_name = loc.constraint_name +AND ac.r_owner = rem.owner(+) +AND ac.r_constraint_name = rem.constraint_name(+) +-- order multiple primary keys correctly +ORDER BY ac.constraint_name, loc.position, rem.position""" + + +def descriptor(): + return {'name':'oracle', + 'description':'Oracle', + 'arguments':[ + ('dsn', 'Data Source Name', None), + ('user', 'Username', None), + ('password', 'Password', None) + ]} + +class OracleExecutionContext(default.DefaultExecutionContext): + def pre_exec(self, engine, proxy, compiled, parameters): + super(OracleExecutionContext, self).pre_exec(engine, proxy, compiled, parameters) + if self.dialect.auto_setinputsizes: + self.set_input_sizes(proxy(), parameters) + +class OracleDialect(ansisql.ANSIDialect): + def __init__(self, use_ansi=True, auto_setinputsizes=False, module=None, threaded=True, **kwargs): + self.use_ansi = use_ansi + self.threaded = threaded + if module is None: + self.module = cx_Oracle + else: + self.module = module + self.supports_timestamp = hasattr(self.module, 'TIMESTAMP' ) + self.auto_setinputsizes = auto_setinputsizes + ansisql.ANSIDialect.__init__(self, **kwargs) + + def dbapi(self): + return self.module + + def create_connect_args(self, url): + if url.database: + # if we have a database, then we have a remote host + port = url.port + if port: + port = int(port) + else: + port = 1521 + dsn = self.module.makedsn(url.host,port,url.database) + else: + # we have a local tnsname + dsn = url.host + opts = dict( + user=url.username, + password=url.password, + dsn = dsn, + threaded = self.threaded + ) + opts.update(url.query) + return ([], opts) + + def type_descriptor(self, typeobj): + return sqltypes.adapt_type(typeobj, colspecs) + + def oid_column_name(self): + return "rowid" + + def create_execution_context(self): + return OracleExecutionContext(self) + + def compiler(self, statement, bindparams, **kwargs): + return OracleCompiler(self, statement, bindparams, **kwargs) + def schemagenerator(self, *args, **kwargs): + return OracleSchemaGenerator(*args, **kwargs) + def schemadropper(self, *args, **kwargs): + return OracleSchemaDropper(*args, **kwargs) + def defaultrunner(self, engine, proxy): + return OracleDefaultRunner(engine, proxy) + + + def has_table(self, connection, table_name): + cursor = connection.execute("""select table_name from all_tables where table_name=:name""", {'name':table_name.upper()}) + return bool( cursor.fetchone() is not None ) + + def has_sequence(self, connection, sequence_name): + cursor = connection.execute("""select sequence_name from all_sequences where sequence_name=:name""", {'name':sequence_name.upper()}) + return bool( cursor.fetchone() is not None ) + + def reflecttable(self, connection, table): + preparer = self.identifier_preparer + if not preparer.should_quote(table): + name = table.name.upper() + else: + name = table.name + c = connection.execute ("select distinct OWNER from ALL_TAB_COLUMNS where TABLE_NAME = :table_name", {'table_name':name}) + rows = c.fetchall() + if not rows : + raise exceptions.NoSuchTableError(table.name) + else: + if table.owner is not None: + if table.owner.upper() in [r[0] for r in rows]: + owner = table.owner.upper() + else: + raise exceptions.AssertionError("Specified owner %s does not own table %s"%(table.owner, table.name)) + else: + if len(rows)==1: + owner = rows[0][0] + else: + raise exceptions.AssertionError("There are multiple tables with name %s in the schema, you must specifie owner"%table.name) + + c = connection.execute ("select COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE, NULLABLE, DATA_DEFAULT from ALL_TAB_COLUMNS where TABLE_NAME = :table_name and OWNER = :owner", {'table_name':name, 'owner':owner}) + + while True: + row = c.fetchone() + if row is None: + break + found_table = True + + #print "ROW:" , row + (name, coltype, length, precision, scale, nullable, default) = (row[0], row[1], row[2], row[3], row[4], row[5]=='Y', row[6]) + + # INTEGER if the scale is 0 and precision is null + # NUMBER if the scale and precision are both null + # NUMBER(9,2) if the precision is 9 and the scale is 2 + # NUMBER(3) if the precision is 3 and scale is 0 + #length is ignored except for CHAR and VARCHAR2 + if coltype=='NUMBER' : + if precision is None and scale is None: + coltype = OracleNumeric + elif precision is None and scale == 0 : + coltype = OracleInteger + else : + coltype = OracleNumeric(precision, scale) + elif coltype=='CHAR' or coltype=='VARCHAR2': + coltype = ischema_names.get(coltype, OracleString)(length) + else: + coltype = re.sub(r'\(\d+\)', '', coltype) + try: + coltype = ischema_names[coltype] + except KeyError: + raise exceptions.AssertionError("Cant get coltype for type '%s'" % coltype) + + colargs = [] + if default is not None: + colargs.append(schema.PassiveDefault(sql.text(default))) + + # if name comes back as all upper, assume its case folded + if (name.upper() == name): + name = name.lower() + + table.append_column(schema.Column(name, coltype, nullable=nullable, *colargs)) + + + c = connection.execute(constraintSQL, {'table_name' : table.name.upper(), 'owner' : owner}) + fks = {} + while True: + row = c.fetchone() + if row is None: + break + #print "ROW:" , row + (cons_name, cons_type, local_column, remote_table, remote_column) = row + if cons_type == 'P': + table.primary_key.add(table.c[local_column]) + elif cons_type == 'R': + try: + fk = fks[cons_name] + except KeyError: + fk = ([], []) + fks[cons_name] = fk + refspec = ".".join([remote_table, remote_column]) + schema.Table(remote_table, table.metadata, autoload=True, autoload_with=connection) + if local_column not in fk[0]: + fk[0].append(local_column) + if refspec not in fk[1]: + fk[1].append(refspec) + + for name, value in fks.iteritems(): + table.append_constraint(schema.ForeignKeyConstraint(value[0], value[1], name=name)) + + def do_executemany(self, c, statement, parameters, context=None): + rowcount = 0 + for param in parameters: + c.execute(statement, param) + rowcount += c.rowcount + if context is not None: + context._rowcount = rowcount + +class OracleCompiler(ansisql.ANSICompiler): + """oracle compiler modifies the lexical structure of Select statements to work under + non-ANSI configured Oracle databases, if the use_ansi flag is False.""" + + def default_from(self): + """called when a SELECT statement has no froms, and no FROM clause is to be appended. + gives Oracle a chance to tack on a "FROM DUAL" to the string output. """ + return " FROM DUAL" + + def apply_function_parens(self, func): + return len(func.clauses) > 0 + + def visit_join(self, join): + if self.dialect.use_ansi: + return ansisql.ANSICompiler.visit_join(self, join) + + self.froms[join] = self.get_from_text(join.left) + ", " + self.get_from_text(join.right) + self.wheres[join] = sql.and_(self.wheres.get(join.left, None), join.onclause) + self.strings[join] = self.froms[join] + + if join.isouter: + # if outer join, push on the right side table as the current "outertable" + self._outertable = join.right + + # now re-visit the onclause, which will be used as a where clause + # (the first visit occured via the Join object itself right before it called visit_join()) + join.onclause.accept_visitor(self) + + self._outertable = None + + self.visit_compound(self.wheres[join]) + + def visit_alias(self, alias): + """oracle doesnt like 'FROM table AS alias'. is the AS standard SQL??""" + self.froms[alias] = self.get_from_text(alias.original) + " " + alias.name + self.strings[alias] = self.get_str(alias.original) + + def visit_column(self, column): + ansisql.ANSICompiler.visit_column(self, column) + if not self.dialect.use_ansi and getattr(self, '_outertable', None) is not None and column.table is self._outertable: + self.strings[column] = self.strings[column] + "(+)" + + def visit_insert(self, insert): + """inserts are required to have the primary keys be explicitly present. + mapper will by default not put them in the insert statement to comply + with autoincrement fields that require they not be present. so, + put them all in for all primary key columns.""" + for c in insert.table.primary_key: + if not self.parameters.has_key(c.key): + self.parameters[c.key] = None + return ansisql.ANSICompiler.visit_insert(self, insert) + + def _TODO_visit_compound_select(self, select): + """need to determine how to get LIMIT/OFFSET into a UNION for oracle""" + if getattr(select, '_oracle_visit', False): + # cancel out the compiled order_by on the select + if hasattr(select, "order_by_clause"): + self.strings[select.order_by_clause] = "" + ansisql.ANSICompiler.visit_compound_select(self, select) + return + + if select.limit is not None or select.offset is not None: + select._oracle_visit = True + # to use ROW_NUMBER(), an ORDER BY is required. + orderby = self.strings[select.order_by_clause] + if not orderby: + orderby = select.oid_column + orderby.accept_visitor(self) + orderby = self.strings[orderby] + class SelectVisitor(sql.ClauseVisitor): + def visit_select(self, select): + select.append_column(sql.column("ROW_NUMBER() OVER (ORDER BY %s)" % orderby).label("ora_rn")) + select.accept_visitor(SelectVisitor()) + limitselect = sql.select([c for c in select.c if c.key!='ora_rn']) + if select.offset is not None: + limitselect.append_whereclause("ora_rn>%d" % select.offset) + if select.limit is not None: + limitselect.append_whereclause("ora_rn<=%d" % (select.limit + select.offset)) + else: + limitselect.append_whereclause("ora_rn<=%d" % select.limit) + limitselect.accept_visitor(self) + self.strings[select] = self.strings[limitselect] + self.froms[select] = self.froms[limitselect] + else: + ansisql.ANSICompiler.visit_compound_select(self, select) + + def visit_select(self, select): + """looks for LIMIT and OFFSET in a select statement, and if so tries to wrap it in a + subquery with row_number() criterion.""" + # TODO: put a real copy-container on Select and copy, or somehow make this + # not modify the Select statement + if getattr(select, '_oracle_visit', False): + # cancel out the compiled order_by on the select + if hasattr(select, "order_by_clause"): + self.strings[select.order_by_clause] = "" + ansisql.ANSICompiler.visit_select(self, select) + return + + if select.limit is not None or select.offset is not None: + select._oracle_visit = True + # to use ROW_NUMBER(), an ORDER BY is required. + orderby = self.strings[select.order_by_clause] + if not orderby: + orderby = select.oid_column + orderby.accept_visitor(self) + orderby = self.strings[orderby] + select.append_column(sql.column("ROW_NUMBER() OVER (ORDER BY %s)" % orderby).label("ora_rn")) + limitselect = sql.select([c for c in select.c if c.key!='ora_rn']) + if select.offset is not None: + limitselect.append_whereclause("ora_rn>%d" % select.offset) + if select.limit is not None: + limitselect.append_whereclause("ora_rn<=%d" % (select.limit + select.offset)) + else: + limitselect.append_whereclause("ora_rn<=%d" % select.limit) + limitselect.accept_visitor(self) + self.strings[select] = self.strings[limitselect] + self.froms[select] = self.froms[limitselect] + else: + ansisql.ANSICompiler.visit_select(self, select) + + def limit_clause(self, select): + return "" + + def for_update_clause(self, select): + if select.for_update=="nowait": + return " FOR UPDATE NOWAIT" + else: + return super(OracleCompiler, self).for_update_clause(select) + +class OracleSchemaGenerator(ansisql.ANSISchemaGenerator): + def get_column_specification(self, column, **kwargs): + colspec = self.preparer.format_column(column) + colspec += " " + column.type.engine_impl(self.engine).get_col_spec() + default = self.get_column_default_string(column) + if default is not None: + colspec += " DEFAULT " + default + + if not column.nullable: + colspec += " NOT NULL" + return colspec + + def visit_sequence(self, sequence): + if not self.engine.dialect.has_sequence(self.connection, sequence.name): + self.append("CREATE SEQUENCE %s" % self.preparer.format_sequence(sequence)) + self.execute() + +class OracleSchemaDropper(ansisql.ANSISchemaDropper): + def visit_sequence(self, sequence): + if self.engine.dialect.has_sequence(self.connection, sequence.name): + self.append("DROP SEQUENCE %s" % sequence.name) + self.execute() + +class OracleDefaultRunner(ansisql.ANSIDefaultRunner): + def exec_default_sql(self, default): + c = sql.select([default.arg], from_obj=["DUAL"], engine=self.engine).compile() + return self.proxy(str(c), c.get_params()).fetchone()[0] + + def visit_sequence(self, seq): + return self.proxy("SELECT " + seq.name + ".nextval FROM DUAL").fetchone()[0] + +dialect = OracleDialect diff --git a/spyce-2.1/sqlalchemy/databases/postgres.py b/spyce-2.1/sqlalchemy/databases/postgres.py new file mode 100755 index 0000000000000000000000000000000000000000..5c78e2f5201b957c1a2b79f2150269b9cdf13eba --- /dev/null +++ b/spyce-2.1/sqlalchemy/databases/postgres.py @@ -0,0 +1,558 @@ +# postgres.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +import datetime, sys, StringIO, string, types, re + +import sqlalchemy.util as util +import sqlalchemy.sql as sql +import sqlalchemy.engine as engine +import sqlalchemy.engine.default as default +import sqlalchemy.schema as schema +import sqlalchemy.ansisql as ansisql +import sqlalchemy.types as sqltypes +import sqlalchemy.exceptions as exceptions +from sqlalchemy.databases import information_schema as ischema +from sqlalchemy import * +import re + +try: + import mx.DateTime.DateTime as mxDateTime +except: + mxDateTime = None + +try: + import psycopg2 as psycopg + #import psycopg2.psycopg1 as psycopg +except: + try: + import psycopg + except: + psycopg = None + +class PGNumeric(sqltypes.Numeric): + def get_col_spec(self): + return "NUMERIC(%(precision)s, %(length)s)" % {'precision': self.precision, 'length' : self.length} +class PGFloat(sqltypes.Float): + def get_col_spec(self): + return "FLOAT(%(precision)s)" % {'precision': self.precision} +class PGInteger(sqltypes.Integer): + def get_col_spec(self): + return "INTEGER" +class PGSmallInteger(sqltypes.Smallinteger): + def get_col_spec(self): + return "SMALLINT" +class PGBigInteger(sqltypes.Integer): + def get_col_spec(self): + return "BIGINT" +class PG2DateTime(sqltypes.DateTime): + def get_col_spec(self): + return "TIMESTAMP " + (self.timezone and "WITH" or "WITHOUT") + " TIME ZONE" +class PG1DateTime(sqltypes.DateTime): + def convert_bind_param(self, value, dialect): + if value is not None: + if isinstance(value, datetime.datetime): + seconds = float(str(value.second) + "." + + str(value.microsecond)) + mx_datetime = mxDateTime(value.year, value.month, value.day, + value.hour, value.minute, + seconds) + return psycopg.TimestampFromMx(mx_datetime) + return psycopg.TimestampFromMx(value) + else: + return None + def convert_result_value(self, value, dialect): + if value is None: + return None + second_parts = str(value.second).split(".") + seconds = int(second_parts[0]) + microseconds = int(second_parts[1]) + return datetime.datetime(value.year, value.month, value.day, + value.hour, value.minute, seconds, + microseconds) + def get_col_spec(self): + return "TIMESTAMP " + (self.timezone and "WITH" or "WITHOUT") + " TIME ZONE" +class PG2Date(sqltypes.Date): + def get_col_spec(self): + return "DATE" +class PG1Date(sqltypes.Date): + def convert_bind_param(self, value, dialect): + # TODO: perform appropriate postgres1 conversion between Python DateTime/MXDateTime + # this one doesnt seem to work with the "emulation" mode + if value is not None: + return psycopg.DateFromMx(value) + else: + return None + def convert_result_value(self, value, dialect): + # TODO: perform appropriate postgres1 conversion between Python DateTime/MXDateTime + return value + def get_col_spec(self): + return "DATE" +class PG2Time(sqltypes.Time): + def get_col_spec(self): + return "TIME " + (self.timezone and "WITH" or "WITHOUT") + " TIME ZONE" +class PG1Time(sqltypes.Time): + def convert_bind_param(self, value, dialect): + # TODO: perform appropriate postgres1 conversion between Python DateTime/MXDateTime + # this one doesnt seem to work with the "emulation" mode + if value is not None: + return psycopg.TimeFromMx(value) + else: + return None + def convert_result_value(self, value, dialect): + # TODO: perform appropriate postgres1 conversion between Python DateTime/MXDateTime + return value + def get_col_spec(self): + return "TIME " + (self.timezone and "WITH" or "WITHOUT") + " TIME ZONE" + +class PGText(sqltypes.TEXT): + def get_col_spec(self): + return "TEXT" +class PGString(sqltypes.String): + def get_col_spec(self): + return "VARCHAR(%(length)s)" % {'length' : self.length} +class PGChar(sqltypes.CHAR): + def get_col_spec(self): + return "CHAR(%(length)s)" % {'length' : self.length} +class PGBinary(sqltypes.Binary): + def get_col_spec(self): + return "BYTEA" +class PGBoolean(sqltypes.Boolean): + def get_col_spec(self): + return "BOOLEAN" + + +pg2_colspecs = { + sqltypes.Integer : PGInteger, + sqltypes.Smallinteger : PGSmallInteger, + sqltypes.Numeric : PGNumeric, + sqltypes.Float : PGFloat, + sqltypes.DateTime : PG2DateTime, + sqltypes.Date : PG2Date, + sqltypes.Time : PG2Time, + sqltypes.String : PGString, + sqltypes.Binary : PGBinary, + sqltypes.Boolean : PGBoolean, + sqltypes.TEXT : PGText, + sqltypes.CHAR: PGChar, +} +pg1_colspecs = pg2_colspecs.copy() +pg1_colspecs.update({ + sqltypes.DateTime : PG1DateTime, + sqltypes.Date : PG1Date, + sqltypes.Time : PG1Time + }) + +pg2_ischema_names = { + 'integer' : PGInteger, + 'bigint' : PGBigInteger, + 'smallint' : PGSmallInteger, + 'character varying' : PGString, + 'character' : PGChar, + 'text' : PGText, + 'numeric' : PGNumeric, + 'float' : PGFloat, + 'real' : PGFloat, + 'double precision' : PGFloat, + 'timestamp' : PG2DateTime, + 'timestamp with time zone' : PG2DateTime, + 'timestamp without time zone' : PG2DateTime, + 'time with time zone' : PG2Time, + 'time without time zone' : PG2Time, + 'date' : PG2Date, + 'time': PG2Time, + 'bytea' : PGBinary, + 'boolean' : PGBoolean, +} +pg1_ischema_names = pg2_ischema_names.copy() +pg1_ischema_names.update({ + 'timestamp with time zone' : PG1DateTime, + 'timestamp without time zone' : PG1DateTime, + 'date' : PG1Date, + 'time' : PG1Time + }) + +def descriptor(): + return {'name':'postgres', + 'description':'PostGres', + 'arguments':[ + ('username',"Database Username",None), + ('password',"Database Password",None), + ('database',"Database Name",None), + ('host',"Hostname", None), + ]} + +class PGExecutionContext(default.DefaultExecutionContext): + + def post_exec(self, engine, proxy, compiled, parameters, **kwargs): + if getattr(compiled, "isinsert", False) and self.last_inserted_ids is None: + if not engine.dialect.use_oids: + pass + # will raise invalid error when they go to get them + else: + table = compiled.statement.table + cursor = proxy() + if cursor.lastrowid is not None and table is not None and len(table.primary_key): + s = sql.select(table.primary_key, table.oid_column == cursor.lastrowid) + c = s.compile(engine=engine) + cursor = proxy(str(c), c.get_params()) + row = cursor.fetchone() + self._last_inserted_ids = [v for v in row] + +class PGDialect(ansisql.ANSIDialect): + def __init__(self, module=None, use_oids=False, use_information_schema=False, **params): + self.use_oids = use_oids + if module is None: + #if psycopg is None: + # raise exceptions.ArgumentError("Couldnt locate psycopg1 or psycopg2: specify postgres module argument") + self.module = psycopg + else: + self.module = module + # figure psycopg version 1 or 2 + try: + if self.module.__version__.startswith('2'): + self.version = 2 + else: + self.version = 1 + except: + self.version = 1 + ansisql.ANSIDialect.__init__(self, **params) + self.use_information_schema = use_information_schema + # produce consistent paramstyle even if psycopg2 module not present + if self.module is None: + self.paramstyle = 'pyformat' + + def create_connect_args(self, url): + opts = url.translate_connect_args(['host', 'database', 'user', 'password', 'port']) + if opts.has_key('port'): + if self.version == 2: + opts['port'] = int(opts['port']) + else: + opts['port'] = str(opts['port']) + opts.update(url.query) + return ([], opts) + + def create_execution_context(self): + return PGExecutionContext(self) + + def type_descriptor(self, typeobj): + if self.version == 2: + return sqltypes.adapt_type(typeobj, pg2_colspecs) + else: + return sqltypes.adapt_type(typeobj, pg1_colspecs) + + def compiler(self, statement, bindparams, **kwargs): + return PGCompiler(self, statement, bindparams, **kwargs) + def schemagenerator(self, *args, **kwargs): + return PGSchemaGenerator(*args, **kwargs) + def schemadropper(self, *args, **kwargs): + return PGSchemaDropper(*args, **kwargs) + def defaultrunner(self, engine, proxy): + return PGDefaultRunner(engine, proxy) + def preparer(self): + return PGIdentifierPreparer(self) + + def get_default_schema_name(self, connection): + if not hasattr(self, '_default_schema_name'): + self._default_schema_name = connection.scalar("select current_schema()", None) + return self._default_schema_name + + def last_inserted_ids(self): + if self.context.last_inserted_ids is None: + raise exceptions.InvalidRequestError("no INSERT executed, or cant use cursor.lastrowid without Postgres OIDs enabled") + else: + return self.context.last_inserted_ids + + def oid_column_name(self): + if self.use_oids: + return "oid" + else: + return None + + def do_executemany(self, c, statement, parameters, context=None): + """we need accurate rowcounts for updates, inserts and deletes. psycopg2 is not nice enough + to produce this correctly for an executemany, so we do our own executemany here.""" + rowcount = 0 + for param in parameters: + c.execute(statement, param) + rowcount += c.rowcount + if context is not None: + context._rowcount = rowcount + + def dbapi(self): + return self.module + + def has_table(self, connection, table_name): + # TODO: why are we case folding here ? + cursor = connection.execute("""select relname from pg_class where lower(relname) = %(name)s""", {'name':table_name.lower()}) + return bool( not not cursor.rowcount ) + + def has_sequence(self, connection, sequence_name): + cursor = connection.execute('''SELECT relname FROM pg_class WHERE relkind = 'S' AND relnamespace IN ( SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%%' AND nspname != 'information_schema' AND relname = %(seqname)s);''', {'seqname': sequence_name}) + return bool(not not cursor.rowcount) + + def reflecttable(self, connection, table): + if self.version == 2: + ischema_names = pg2_ischema_names + else: + ischema_names = pg1_ischema_names + + if self.use_information_schema: + ischema.reflecttable(connection, table, ischema_names) + else: + preparer = self.identifier_preparer + if table.schema is not None: + current_schema = table.schema + else: + current_schema = connection.default_schema_name() + + ## information schema in pg suffers from too many permissions' restrictions + ## let us find out at the pg way what is needed... + + SQL_COLS = """ + SELECT a.attname, + pg_catalog.format_type(a.atttypid, a.atttypmod), + (SELECT substring(d.adsrc for 128) FROM pg_catalog.pg_attrdef d + WHERE d.adrelid = a.attrelid AND d.adnum = a.attnum AND a.atthasdef) + AS DEFAULT, + a.attnotnull, a.attnum + FROM pg_catalog.pg_attribute a + WHERE a.attrelid = ( + SELECT c.oid + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE (n.nspname = :schema OR pg_catalog.pg_table_is_visible(c.oid)) + AND c.relname = :table_name AND (c.relkind = 'r' OR c.relkind = 'v') + ) AND a.attnum > 0 AND NOT a.attisdropped + ORDER BY a.attnum + """ + + s = text(SQL_COLS ) + c = connection.execute(s, table_name=table.name, schema=current_schema) + found_table = False + while True: + row = c.fetchone() + if row is None: + break + found_table = True + name = row['attname'] + ## strip (30) from character varying(30) + attype = re.search('([^\(]+)', row['format_type']).group(1) + + nullable = row['attnotnull'] == False + try: + charlen = re.search('\(([\d,]+)\)',row['format_type']).group(1) + except: + charlen = None + + numericprec = None + numericscale = None + default = row['default'] + if attype == 'numeric': + numericprec, numericscale = charlen.split(',') + charlen = None + if attype == 'double precision': + numericprec, numericscale = (53, None) + charlen = None + if attype == 'integer': + numericprec, numericscale = (32, 0) + charlen = None + + args = [] + for a in (charlen, numericprec, numericscale): + if a is not None: + args.append(int(a)) + + kwargs = {} + if attype == 'timestamp with time zone': + kwargs['timezone'] = True + elif attype == 'timestamp without time zone': + kwargs['timezone'] = False + + coltype = ischema_names[attype] + coltype = coltype(*args, **kwargs) + colargs= [] + if default is not None: + colargs.append(PassiveDefault(sql.text(default))) + table.append_column(schema.Column(name, coltype, nullable=nullable, *colargs)) + + + if not found_table: + raise exceptions.NoSuchTableError(table.name) + + # Primary keys + PK_SQL = """ + SELECT attname FROM pg_attribute + WHERE attrelid = ( + SELECT indexrelid FROM pg_index i, pg_class c, pg_namespace n + WHERE n.nspname = :schema AND c.relname = :table_name + AND c.oid = i.indrelid AND n.oid = c.relnamespace + AND i.indisprimary = 't' ) ; + """ + t = text(PK_SQL) + c = connection.execute(t, table_name=table.name, schema=current_schema) + while True: + row = c.fetchone() + if row is None: + break + pk = row[0] + table.primary_key.add(table.c[pk]) + + # Foreign keys + FK_SQL = """ + SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef + FROM pg_catalog.pg_constraint r + WHERE r.conrelid = ( + SELECT c.oid FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n + ON n.oid = c.relnamespace + WHERE c.relname = :table_name + AND pg_catalog.pg_table_is_visible(c.oid)) + AND r.contype = 'f' ORDER BY 1 + + """ + + t = text(FK_SQL) + c = connection.execute(t, table_name=table.name) + while True: + row = c.fetchone() + if row is None: + break + + identifier = '(?:[a-z_][a-z0-9_$]+|"(?:[^"]|"")+")' + identifier_group = '%s(?:, %s)*' % (identifier, identifier) + identifiers = '(%s)(?:, (%s))*' % (identifier, identifier) + f = re.compile(identifiers) + # FOREIGN KEY (mail_user_id,"Mail_User_ID2") REFERENCES "mYschema".euro_user(user_id,"User_ID2") + foreign_key_pattern = 'FOREIGN KEY \((%s)\) REFERENCES (?:(%s)\.)?(%s)\((%s)\)' % (identifier_group, identifier, identifier, identifier_group) + p = re.compile(foreign_key_pattern) + + m = p.search(row['condef']) + (constrained_columns, referred_schema, referred_table, referred_columns) = m.groups() + + constrained_columns = [preparer._unquote_identifier(x) for x in f.search(constrained_columns).groups() if x] + if referred_schema: + referred_schema = preparer._unquote_identifier(referred_schema) + referred_table = preparer._unquote_identifier(referred_table) + referred_columns = [preparer._unquote_identifier(x) for x in f.search(referred_columns).groups() if x] + + refspec = [] + if referred_schema is not None: + schema.Table(referred_table, table.metadata, autoload=True, schema=referred_schema, + autoload_with=connection) + for column in referred_columns: + refspec.append(".".join([referred_schema, referred_table, column])) + else: + schema.Table(referred_table, table.metadata, autoload=True, autoload_with=connection) + for column in referred_columns: + refspec.append(".".join([referred_table, column])) + + table.append_constraint(ForeignKeyConstraint(constrained_columns, refspec, row['conname'])) + +class PGCompiler(ansisql.ANSICompiler): + + def visit_insert_column(self, column, parameters): + # Postgres advises against OID usage and turns it off in 8.1, + # effectively making cursor.lastrowid + # useless, effectively making reliance upon SERIAL useless. + # so all column primary key inserts must be explicitly present + if column.primary_key: + parameters[column.key] = None + + def limit_clause(self, select): + text = "" + if select.limit is not None: + text += " \n LIMIT " + str(select.limit) + if select.offset is not None: + if select.limit is None: + text += " \n LIMIT ALL" + text += " OFFSET " + str(select.offset) + return text + + def visit_select_precolumns(self, select): + if select.distinct: + if type(select.distinct) == bool: + return "DISTINCT " + if type(select.distinct) == list: + dist_set = "DISTINCT ON (" + for col in select.distinct: + dist_set += self.strings[col] + ", " + dist_set = dist_set[:-2] + ") " + return dist_set + return "DISTINCT ON (" + str(select.distinct) + ") " + else: + return "" + + def binary_operator_string(self, binary): + if isinstance(binary.type, sqltypes.String) and binary.operator == '+': + return '||' + else: + return ansisql.ANSICompiler.binary_operator_string(self, binary) + +class PGSchemaGenerator(ansisql.ANSISchemaGenerator): + + def get_column_specification(self, column, **kwargs): + colspec = self.preparer.format_column(column) + if column.primary_key and len(column.foreign_keys)==0 and column.autoincrement and isinstance(column.type, sqltypes.Integer) and not isinstance(column.type, sqltypes.SmallInteger) and (column.default is None or (isinstance(column.default, schema.Sequence) and column.default.optional)): + colspec += " SERIAL" + else: + colspec += " " + column.type.engine_impl(self.engine).get_col_spec() + default = self.get_column_default_string(column) + if default is not None: + colspec += " DEFAULT " + default + + if not column.nullable: + colspec += " NOT NULL" + return colspec + + def visit_sequence(self, sequence): + if not sequence.optional and (not self.dialect.has_sequence(self.connection, sequence.name)): + self.append("CREATE SEQUENCE %s" % self.preparer.format_sequence(sequence)) + self.execute() + +class PGSchemaDropper(ansisql.ANSISchemaDropper): + def visit_sequence(self, sequence): + if not sequence.optional and (self.dialect.has_sequence(self.connection, sequence.name)): + self.append("DROP SEQUENCE %s" % sequence.name) + self.execute() + +class PGDefaultRunner(ansisql.ANSIDefaultRunner): + def get_column_default(self, column, isinsert=True): + if column.primary_key: + # passive defaults on primary keys have to be overridden + if isinstance(column.default, schema.PassiveDefault): + c = self.proxy("select %s" % column.default.arg) + return c.fetchone()[0] + elif (isinstance(column.type, sqltypes.Integer) and column.autoincrement) and (column.default is None or (isinstance(column.default, schema.Sequence) and column.default.optional)): + sch = column.table.schema + # TODO: this has to build into the Sequence object so we can get the quoting + # logic from it + if sch is not None: + exc = "select nextval('\"%s\".\"%s_%s_seq\"')" % (sch, column.table.name, column.name) + else: + exc = "select nextval('\"%s_%s_seq\"')" % (column.table.name, column.name) + c = self.proxy(exc) + return c.fetchone()[0] + else: + return ansisql.ANSIDefaultRunner.get_column_default(self, column) + else: + return ansisql.ANSIDefaultRunner.get_column_default(self, column) + + def visit_sequence(self, seq): + if not seq.optional: + c = self.proxy("select nextval('%s')" % seq.name) #TODO: self.dialect.preparer.format_sequence(seq)) + return c.fetchone()[0] + else: + return None + +class PGIdentifierPreparer(ansisql.ANSIIdentifierPreparer): + def _fold_identifier_case(self, value): + return value.lower() + def _unquote_identifier(self, value): + if value[0] == self.initial_quote: + value = value[1:-1].replace('""','"') + return value + +dialect = PGDialect diff --git a/spyce-2.1/sqlalchemy/databases/sqlite.py b/spyce-2.1/sqlalchemy/databases/sqlite.py new file mode 100755 index 0000000000000000000000000000000000000000..c76149d7bc258fb1bf3f444de7441eb581d91bf9 --- /dev/null +++ b/spyce-2.1/sqlalchemy/databases/sqlite.py @@ -0,0 +1,325 @@ +# sqlite.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +import sys, StringIO, string, types, re + +from sqlalchemy import sql, engine, schema, ansisql, exceptions, pool, PassiveDefault +import sqlalchemy.engine.default as default +import sqlalchemy.types as sqltypes +import datetime,time + +pysqlite2_timesupport = False # Change this if the init.d guys ever get around to supporting time cols + +try: + from pysqlite2 import dbapi2 as sqlite +except ImportError: + try: + from sqlite3 import dbapi2 as sqlite #try the 2.5+ stdlib name. + except ImportError: + try: + sqlite = __import__('sqlite') # skip ourselves + except: + sqlite = None + +class SLNumeric(sqltypes.Numeric): + def get_col_spec(self): + return "NUMERIC(%(precision)s, %(length)s)" % {'precision': self.precision, 'length' : self.length} +class SLInteger(sqltypes.Integer): + def get_col_spec(self): + return "INTEGER" +class SLSmallInteger(sqltypes.Smallinteger): + def get_col_spec(self): + return "SMALLINT" +class DateTimeMixin(object): + def convert_bind_param(self, value, dialect): + if value is not None: + return str(value) + else: + return None + def _cvt(self, value, dialect, fmt): + if value is None: + return None + parts = value.split('.') + try: + (value, microsecond) = value.split('.') + microsecond = int(microsecond) + except ValueError: + (value, microsecond) = (value, 0) + return time.strptime(value, fmt)[0:6] + (microsecond,) + +class SLDateTime(DateTimeMixin,sqltypes.DateTime): + def get_col_spec(self): + return "TIMESTAMP" + def convert_result_value(self, value, dialect): + tup = self._cvt(value, dialect, "%Y-%m-%d %H:%M:%S") + return tup and datetime.datetime(*tup) +class SLDate(DateTimeMixin, sqltypes.Date): + def get_col_spec(self): + return "DATE" + def convert_result_value(self, value, dialect): + tup = self._cvt(value, dialect, "%Y-%m-%d") + return tup and datetime.date(*tup[0:3]) +class SLTime(DateTimeMixin, sqltypes.Time): + def get_col_spec(self): + return "TIME" + def convert_result_value(self, value, dialect): + tup = self._cvt(value, dialect, "%H:%M:%S") + return tup and datetime.time(*tup[3:7]) +class SLText(sqltypes.TEXT): + def get_col_spec(self): + return "TEXT" +class SLString(sqltypes.String): + def get_col_spec(self): + return "VARCHAR(%(length)s)" % {'length' : self.length} +class SLChar(sqltypes.CHAR): + def get_col_spec(self): + return "CHAR(%(length)s)" % {'length' : self.length} +class SLBinary(sqltypes.Binary): + def get_col_spec(self): + return "BLOB" +class SLBoolean(sqltypes.Boolean): + def get_col_spec(self): + return "BOOLEAN" + def convert_bind_param(self, value, dialect): + if value is None: + return None + return value and 1 or 0 + def convert_result_value(self, value, dialect): + if value is None: + return None + return value and True or False + +colspecs = { + sqltypes.Integer : SLInteger, + sqltypes.Smallinteger : SLSmallInteger, + sqltypes.Numeric : SLNumeric, + sqltypes.Float : SLNumeric, + sqltypes.DateTime : SLDateTime, + sqltypes.Date : SLDate, + sqltypes.Time : SLTime, + sqltypes.String : SLString, + sqltypes.Binary : SLBinary, + sqltypes.Boolean : SLBoolean, + sqltypes.TEXT : SLText, + sqltypes.CHAR: SLChar, +} + +pragma_names = { + 'INTEGER' : SLInteger, + 'SMALLINT' : SLSmallInteger, + 'VARCHAR' : SLString, + 'CHAR' : SLChar, + 'TEXT' : SLText, + 'NUMERIC' : SLNumeric, + 'FLOAT' : SLNumeric, + 'TIMESTAMP' : SLDateTime, + 'DATETIME' : SLDateTime, + 'DATE' : SLDate, + 'BLOB' : SLBinary, +} + +if pysqlite2_timesupport: + colspecs.update({sqltypes.Time : SLTime}) + pragma_names.update({'TIME' : SLTime}) + +def descriptor(): + return {'name':'sqlite', + 'description':'SQLite', + 'arguments':[ + ('database', "Database Filename",None) + ]} + + +class SQLiteExecutionContext(default.DefaultExecutionContext): + def post_exec(self, engine, proxy, compiled, parameters, **kwargs): + if getattr(compiled, "isinsert", False): + self._last_inserted_ids = [proxy().lastrowid] + +class SQLiteDialect(ansisql.ANSIDialect): + def __init__(self, **kwargs): + def vers(num): + return tuple([int(x) for x in num.split('.')]) + self.supports_cast = (sqlite is not None and vers(sqlite.sqlite_version) >= vers("3.2.3")) + ansisql.ANSIDialect.__init__(self, **kwargs) + def compiler(self, statement, bindparams, **kwargs): + return SQLiteCompiler(self, statement, bindparams, **kwargs) + def schemagenerator(self, *args, **kwargs): + return SQLiteSchemaGenerator(*args, **kwargs) + def schemadropper(self, *args, **kwargs): + return SQLiteSchemaDropper(*args, **kwargs) + def preparer(self): + return SQLiteIdentifierPreparer(self) + def create_connect_args(self, url): + filename = url.database or ':memory:' + return ([filename], url.query) + def type_descriptor(self, typeobj): + return sqltypes.adapt_type(typeobj, colspecs) + def create_execution_context(self): + return SQLiteExecutionContext(self) + def last_inserted_ids(self): + return self.context.last_inserted_ids + + def oid_column_name(self): + return "oid" + + def dbapi(self): + return sqlite + + def has_table(self, connection, table_name): + cursor = connection.execute("PRAGMA table_info(" + table_name + ")", {}) + row = cursor.fetchone() + + # consume remaining rows, to work around: http://www.sqlite.org/cvstrac/tktview?tn=1884 + while cursor.fetchone() is not None:pass + + return (row is not None) + + def reflecttable(self, connection, table): + c = connection.execute("PRAGMA table_info(" + table.name + ")", {}) + found_table = False + while True: + row = c.fetchone() + if row is None: + break + #print "row! " + repr(row) + found_table = True + (name, type, nullable, has_default, primary_key) = (row[1], row[2].upper(), not row[3], row[4] is not None, row[5]) + name = re.sub(r'^\"|\"$', '', name) + match = re.match(r'(\w+)(\(.*?\))?', type) + coltype = match.group(1) + args = match.group(2) + + #print "coltype: " + repr(coltype) + " args: " + repr(args) + coltype = pragma_names.get(coltype, SLString) + if args is not None: + args = re.findall(r'(\d+)', args) + #print "args! " +repr(args) + coltype = coltype(*[int(a) for a in args]) + + colargs= [] + if has_default: + colargs.append(PassiveDefault('?')) + table.append_column(schema.Column(name, coltype, primary_key = primary_key, nullable = nullable, *colargs)) + + if not found_table: + raise exceptions.NoSuchTableError(table.name) + + c = connection.execute("PRAGMA foreign_key_list(" + table.name + ")", {}) + fks = {} + while True: + row = c.fetchone() + if row is None: + break + (constraint_name, tablename, localcol, remotecol) = (row[0], row[2], row[3], row[4]) + tablename = re.sub(r'^\"|\"$', '', tablename) + localcol = re.sub(r'^\"|\"$', '', localcol) + remotecol = re.sub(r'^\"|\"$', '', remotecol) + try: + fk = fks[constraint_name] + except KeyError: + fk = ([],[]) + fks[constraint_name] = fk + + #print "row! " + repr([key for key in row.keys()]), repr(row) + # look up the table based on the given table's engine, not 'self', + # since it could be a ProxyEngine + remotetable = schema.Table(tablename, table.metadata, autoload=True, autoload_with=connection) + constrained_column = table.c[localcol].name + refspec = ".".join([tablename, remotecol]) + if constrained_column not in fk[0]: + fk[0].append(constrained_column) + if refspec not in fk[1]: + fk[1].append(refspec) + for name, value in fks.iteritems(): + table.append_constraint(schema.ForeignKeyConstraint(value[0], value[1])) + # check for UNIQUE indexes + c = connection.execute("PRAGMA index_list(" + table.name + ")", {}) + unique_indexes = [] + while True: + row = c.fetchone() + if row is None: + break + if (row[2] == 1): + unique_indexes.append(row[1]) + # loop thru unique indexes for one that includes the primary key + for idx in unique_indexes: + c = connection.execute("PRAGMA index_info(" + idx + ")", {}) + cols = [] + while True: + row = c.fetchone() + if row is None: + break + cols.append(row[2]) + col = table.columns[row[2]] + # unique index that includes the pk is considered a multiple primary key + for col in cols: + table.primary_key.add(table.columns[col]) + +class SQLiteCompiler(ansisql.ANSICompiler): + def visit_cast(self, cast): + if self.dialect.supports_cast: + super(SQLiteCompiler, self).visit_cast(cast) + else: + if len(self.select_stack): + # not sure if we want to set the typemap here... + self.typemap.setdefault("CAST", cast.type) + self.strings[cast] = self.strings[cast.clause] + def limit_clause(self, select): + text = "" + if select.limit is not None: + text += " \n LIMIT " + str(select.limit) + if select.offset is not None: + if select.limit is None: + text += " \n LIMIT -1" + text += " OFFSET " + str(select.offset) + else: + text += " OFFSET 0" + return text + def for_update_clause(self, select): + # sqlite has no "FOR UPDATE" AFAICT + return '' + + def binary_operator_string(self, binary): + if isinstance(binary.type, sqltypes.String) and binary.operator == '+': + return '||' + else: + return ansisql.ANSICompiler.binary_operator_string(self, binary) + +class SQLiteSchemaGenerator(ansisql.ANSISchemaGenerator): + def supports_alter(self): + return False + + def get_column_specification(self, column, **kwargs): + colspec = self.preparer.format_column(column) + " " + column.type.engine_impl(self.engine).get_col_spec() + default = self.get_column_default_string(column) + if default is not None: + colspec += " DEFAULT " + default + + if not column.nullable: + colspec += " NOT NULL" + return colspec + + # this doesnt seem to be needed, although i suspect older versions of sqlite might still + # not directly support composite primary keys + #def visit_primary_key_constraint(self, constraint): + # if len(constraint) > 1: + # self.append(", \n") + # # put all PRIMARY KEYS in a UNIQUE index + # self.append("\tUNIQUE (%s)" % string.join([c.name for c in constraint],', ')) + # else: + # super(SQLiteSchemaGenerator, self).visit_primary_key_constraint(constraint) + +class SQLiteSchemaDropper(ansisql.ANSISchemaDropper): + def supports_alter(self): + return False + +class SQLiteIdentifierPreparer(ansisql.ANSIIdentifierPreparer): + def __init__(self, dialect): + super(SQLiteIdentifierPreparer, self).__init__(dialect, omit_schema=True) + +dialect = SQLiteDialect +poolclass = pool.SingletonThreadPool diff --git a/spyce-2.1/sqlalchemy/databases/sqlite.pyc b/spyce-2.1/sqlalchemy/databases/sqlite.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c79692db943f82121f6b14aea382559e899441a0 Binary files /dev/null and b/spyce-2.1/sqlalchemy/databases/sqlite.pyc differ diff --git a/spyce-2.1/sqlalchemy/engine/__init__.py b/spyce-2.1/sqlalchemy/engine/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..a53ea4971c09fa8a32fb93b0f39512be0fb4b6d5 --- /dev/null +++ b/spyce-2.1/sqlalchemy/engine/__init__.py @@ -0,0 +1,91 @@ +# engine/__init__.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from sqlalchemy import databases +from sqlalchemy.engine.base import * +from sqlalchemy.engine import strategies +import re + +def engine_descriptors(): + """provides a listing of all the database implementations supported. this data + is provided as a list of dictionaries, where each dictionary contains the following + key/value pairs: + + name : the name of the engine, suitable for use in the create_engine function + + description: a plain description of the engine. + + arguments : a dictionary describing the name and description of each parameter + used to connect to this engine's underlying DBAPI. + + This function is meant for usage in automated configuration tools that wish to + query the user for database and connection information. + """ + result = [] + #for module in sqlalchemy.databases.__all__: + for module in ['sqlite', 'postgres', 'mysql']: + module = getattr(__import__('sqlalchemy.databases.%s' % module).databases, module) + result.append(module.descriptor()) + return result + +default_strategy = 'plain' +def create_engine(*args, **kwargs): + """creates a new Engine instance. Using the given strategy name, + locates that strategy and invokes its create() method to produce the Engine. + The strategies themselves are instances of EngineStrategy, and the built in + ones are present in the sqlalchemy.engine.strategies module. Current implementations + include "plain" and "threadlocal". The default used by this function is "plain". + + "plain" provides support for a Connection object which can be used to execute SQL queries + with a specific underlying DBAPI connection. + + "threadlocal" is similar to "plain" except that it adds support for a thread-local connection and + transaction context, which allows a group of engine operations to participate using the same + connection and transaction without the need for explicit passing of a Connection object. + + The standard method of specifying the engine is via URL as the first positional + argument, to indicate the appropriate database dialect and connection arguments, with additional + keyword arguments sent as options to the dialect and resulting Engine. + + The URL is in the form ://opt1=val1&opt2=val2. + Where is a name such as "mysql", "oracle", "postgres", and the options indicate + username, password, database, etc. Supported keynames include "username", "user", "password", + "pw", "db", "database", "host", "filename". + + **kwargs represents options to be sent to the Engine itself as well as the components of the Engine, + including the Dialect, the ConnectionProvider, and the Pool. A list of common options is as follows: + + pool=None : an instance of sqlalchemy.pool.DBProxy or sqlalchemy.pool.Pool to be used as the + underlying source for connections (DBProxy/Pool is described in the previous section). If None, + a default DBProxy will be created using the engine's own database module with the given + arguments. + + echo=False : if True, the Engine will log all statements as well as a repr() of their + parameter lists to the engines logger, which defaults to sys.stdout. A Engine instances' + "echo" data member can be modified at any time to turn logging on and off. If set to the string + 'debug', result rows will be printed to the standard output as well. + + logger=None : a file-like object where logging output can be sent, if echo is set to True. + This defaults to sys.stdout. + + encoding='utf-8' : the encoding to be used when encoding/decoding Unicode strings + + convert_unicode=False : True if unicode conversion should be applied to all str types + + module=None : used by Oracle and Postgres, this is a reference to a DBAPI2 module to be used + instead of the engine's default module. For Postgres, the default is psycopg2, or psycopg1 if + 2 cannot be found. For Oracle, its cx_Oracle. For mysql, MySQLdb. + + use_ansi=True : used only by Oracle; when False, the Oracle driver attempts to support a + particular "quirk" of some Oracle databases, that the LEFT OUTER JOIN SQL syntax is not + supported, and the "Oracle join" syntax of using (+)= must be used + in order to achieve a LEFT OUTER JOIN. Its advised that the Oracle database be configured to + have full ANSI support instead of using this feature. + + """ + strategy = kwargs.pop('strategy', default_strategy) + strategy = strategies.strategies[strategy] + return strategy.create(*args, **kwargs) diff --git a/spyce-2.1/sqlalchemy/engine/__init__.pyc b/spyce-2.1/sqlalchemy/engine/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5e82b4a37229f4c408bca1fd4cf4904ee4f6dd1 Binary files /dev/null and b/spyce-2.1/sqlalchemy/engine/__init__.pyc differ diff --git a/spyce-2.1/sqlalchemy/engine/base.py b/spyce-2.1/sqlalchemy/engine/base.py new file mode 100755 index 0000000000000000000000000000000000000000..205e5aa02c78ef9e732aa754d12d517b167b4c81 --- /dev/null +++ b/spyce-2.1/sqlalchemy/engine/base.py @@ -0,0 +1,816 @@ +from sqlalchemy import exceptions, sql, schema, util, types, logging +import StringIO, sys, re + +class ConnectionProvider(object): + """defines an interface that returns raw Connection objects (or compatible).""" + def get_connection(self): + """this method should return a Connection or compatible object from a DBAPI which + also contains a close() method. + It is not defined what context this connection belongs to. It may be newly connected, + returned from a pool, part of some other kind of context such as thread-local, + or can be a fixed member of this object.""" + raise NotImplementedError() + def dispose(self): + """releases all resources corresponding to this ConnectionProvider, such + as any underlying connection pools.""" + raise NotImplementedError() + +class Dialect(sql.AbstractDialect): + """Defines the behavior of a specific database/DBAPI. + + Any aspect of metadata defintion, SQL query generation, execution, result-set handling, + or anything else which varies between databases is defined under the general category of + the Dialect. The Dialect acts as a factory for other database-specific object implementations + including ExecutionContext, Compiled, DefaultGenerator, and TypeEngine. + + All Dialects implement the following attributes: + + positional - True if the paramstyle for this Dialect is positional + + paramstyle - the paramstyle to be used (some DBAPIs support multiple paramstyles) + + supports_autoclose_results - usually True; if False, indicates that rows returned by fetchone() + might not be just plain tuples, and may be "live" proxy objects which still require the cursor + to be open in order to be read (such as pyPgSQL which has active filehandles for BLOBs). in that + case, an auto-closing ResultProxy cannot automatically close itself after results are consumed. + + convert_unicode - True if unicode conversion should be applied to all str types + + encoding - type of encoding to use for unicode, usually defaults to 'utf-8' + """ + def create_connect_args(self, opts): + """given a dictionary of key-valued connect parameters, returns a tuple + consisting of a *args/**kwargs suitable to send directly to the dbapi's connect function. + The connect args will have any number of the following keynames: host, hostname, database, dbanme, + user,username, password, pw, passwd, filename.""" + raise NotImplementedError() + def convert_compiled_params(self, parameters): + """given a sql.ClauseParameters object, returns an array or dictionary suitable to pass + directly to this Dialect's DBAPI's execute method.""" + def type_descriptor(self, typeobj): + """provides a database-specific TypeEngine object, given the generic object + which comes from the types module. Subclasses will usually use the adapt_type() + method in the types module to make this job easy.""" + raise NotImplementedError() + def oid_column_name(self): + """returns the oid column name for this dialect, or None if the dialect cant/wont support OID/ROWID.""" + raise NotImplementedError() + def supports_sane_rowcount(self): + """Provided to indicate when MySQL is being used, which does not have standard behavior + for the "rowcount" function on a statement handle. """ + raise NotImplementedError() + def schemagenerator(self, engine, proxy, **params): + """returns a schema.SchemaVisitor instance that can generate schemas, when it is + invoked to traverse a set of schema objects. + + schemagenerator is called via the create() method on Table, Index, and others. + """ + raise NotImplementedError() + def schemadropper(self, engine, proxy, **params): + """returns a schema.SchemaVisitor instance that can drop schemas, when it is + invoked to traverse a set of schema objects. + + schemagenerator is called via the drop() method on Table, Index, and others. + """ + raise NotImplementedError() + def defaultrunner(self, engine, proxy, **params): + """returns a schema.SchemaVisitor instances that can execute defaults.""" + raise NotImplementedError() + def compiler(self, statement, parameters): + """returns a sql.ClauseVisitor which will produce a string representation of the given + ClauseElement and parameter dictionary. This object is usually a subclass of + ansisql.ANSICompiler. + + compiler is called within the context of the compile() method.""" + raise NotImplementedError() + def reflecttable(self, connection, table): + """given an Connection and a Table object, reflects its columns and properties from the database.""" + raise NotImplementedError() + def has_table(self, connection, table_name): + raise NotImplementedError() + def has_sequence(self, connection, sequence_name): + raise NotImplementedError() + def dbapi(self): + """subclasses override this method to provide the DBAPI module used to establish + connections.""" + raise NotImplementedError() + def get_default_schema_name(self, connection): + """returns the currently selected schema given an connection""" + raise NotImplementedError() + def execution_context(self): + """returns a new ExecutionContext object.""" + raise NotImplementedError() + def do_begin(self, connection): + """provides an implementation of connection.begin()""" + raise NotImplementedError() + def do_rollback(self, connection): + """provides an implementation of connection.rollback()""" + raise NotImplementedError() + def do_commit(self, connection): + """provides an implementation of connection.commit()""" + raise NotImplementedError() + def do_executemany(self, cursor, statement, parameters): + raise NotImplementedError() + def do_execute(self, cursor, statement, parameters): + raise NotImplementedError() + def compile(self, clauseelement, parameters=None): + """compile the given ClauseElement using this Dialect. + + a convenience method which simply flips around the compile() call + on ClauseElement.""" + return clauseelement.compile(dialect=self, parameters=parameters) + +class ExecutionContext(object): + """a messenger object for a Dialect that corresponds to a single execution. The Dialect + should provide an ExecutionContext via the create_execution_context() method. + The pre_exec and post_exec methods will be called for compiled statements, afterwhich + it is expected that the various methods last_inserted_ids, last_inserted_params, etc. + will contain appropriate values, if applicable.""" + def pre_exec(self, engine, proxy, compiled, parameters): + """called before an execution of a compiled statement. proxy is a callable that + takes a string statement and a bind parameter list/dictionary.""" + raise NotImplementedError() + def post_exec(self, engine, proxy, compiled, parameters): + """called after the execution of a compiled statement. proxy is a callable that + takes a string statement and a bind parameter list/dictionary.""" + raise NotImplementedError() + def get_rowcount(self, cursor): + """returns the count of rows updated/deleted for an UPDATE/DELETE statement""" + raise NotImplementedError() + def supports_sane_rowcount(self): + """Indicates if the "rowcount" DBAPI cursor function works properly. + + Currently, MySQLDB does not properly implement this function.""" + raise NotImplementedError() + def last_inserted_ids(self): + """return the list of the primary key values for the last insert statement executed. + + This does not apply to straight textual clauses; only to sql.Insert objects compiled against + a schema.Table object, which are executed via statement.execute(). The order of items in the + list is the same as that of the Table's 'primary_key' attribute. + + In some cases, this method may invoke a query back to the database to retrieve the data, based on + the "lastrowid" value in the cursor.""" + raise NotImplementedError() + def last_inserted_params(self): + """return a dictionary of the full parameter dictionary for the last compiled INSERT statement. + + Includes any ColumnDefaults or Sequences that were pre-executed.""" + raise NotImplementedError() + def last_updated_params(self): + """return a dictionary of the full parameter dictionary for the last compiled UPDATE statement. + + Includes any ColumnDefaults that were pre-executed.""" + raise NotImplementedError() + def lastrow_has_defaults(self): + """return True if the last row INSERTED via a compiled insert statement contained PassiveDefaults. + + The presence of PassiveDefaults indicates that the database inserted data beyond that which we + passed to the query programmatically.""" + raise NotImplementedError() + +class Connectable(object): + """interface for an object that can provide an Engine and a Connection object which correponds to that Engine.""" + def contextual_connect(self): + """returns a Connection object which may be part of an ongoing context.""" + raise NotImplementedError() + def create(self, entity, **kwargs): + """creates a table or index given an appropriate schema object.""" + raise NotImplementedError() + def drop(self, entity, **kwargs): + raise NotImplementedError() + def execute(self, object, *multiparams, **params): + raise NotImplementedError() + def _not_impl(self): + raise NotImplementedError() + engine = property(_not_impl, doc="returns the Engine which this Connectable is associated with.") + +class Connection(Connectable): + """represents a single DBAPI connection returned from the underlying connection pool. Provides + execution support for string-based SQL statements as well as ClauseElement, Compiled and DefaultGenerator objects. + provides a begin method to return Transaction objects. + + The Connection object is **not** threadsafe.""" + def __init__(self, engine, connection=None, close_with_result=False): + self.__engine = engine + self.__connection = connection or engine.raw_connection() + self.__transaction = None + self.__close_with_result = close_with_result + def _get_connection(self): + try: + return self.__connection + except AttributeError: + raise exceptions.InvalidRequestError("This Connection is closed") + engine = property(lambda s:s.__engine, doc="The Engine with which this Connection is associated (read only)") + connection = property(_get_connection, doc="The underlying DBAPI connection managed by this Connection.") + should_close_with_result = property(lambda s:s.__close_with_result, doc="Indicates if this Connection should be closed when a corresponding ResultProxy is closed; this is essentially an auto-release mode.") + def _create_transaction(self, parent): + return Transaction(self, parent) + def connect(self): + """connect() is implemented to return self so that an incoming Engine or Connection object can be treated similarly.""" + return self + def contextual_connect(self, **kwargs): + """contextual_connect() is implemented to return self so that an incoming Engine or Connection object can be treated similarly.""" + return self + def begin(self): + if self.__transaction is None: + self.__transaction = self._create_transaction(None) + return self.__transaction + else: + return self._create_transaction(self.__transaction) + def in_transaction(self): + return self.__transaction is not None + def _begin_impl(self): + self.__engine.logger.info("BEGIN") + self.__engine.dialect.do_begin(self.connection) + def _rollback_impl(self): + self.__engine.logger.info("ROLLBACK") + self.__engine.dialect.do_rollback(self.connection) + self.__connection.close_open_cursors() + self.__transaction = None + def _commit_impl(self): + self.__engine.logger.info("COMMIT") + self.__engine.dialect.do_commit(self.connection) + self.__transaction = None + def _autocommit(self, statement): + """when no Transaction is present, this is called after executions to provide "autocommit" behavior.""" + # TODO: have the dialect determine if autocommit can be set on the connection directly without this + # extra step + if not self.in_transaction() and re.match(r'UPDATE|INSERT|CREATE|DELETE|DROP|ALTER', statement.lstrip().upper()): + self._commit_impl() + def _autorollback(self): + if not self.in_transaction(): + self._rollback_impl() + def close(self): + try: + c = self.__connection + except AttributeError: + return + self.__connection.close() + self.__connection = None + del self.__connection + def scalar(self, object, parameters=None, **kwargs): + return self.execute(object, parameters, **kwargs).scalar() + def execute(self, object, *multiparams, **params): + return Connection.executors[type(object).__mro__[-2]](self, object, *multiparams, **params) + def execute_default(self, default, **kwargs): + return default.accept_schema_visitor(self.__engine.dialect.defaultrunner(self.__engine, self.proxy, **kwargs)) + def execute_text(self, statement, parameters=None): + cursor = self._execute_raw(statement, parameters) + return ResultProxy(self.__engine, self, cursor) + def _params_to_listofdicts(self, *multiparams, **params): + if len(multiparams) == 0: + return [params] + elif len(multiparams) == 1: + if multiparams[0] == None: + return [{}] + elif isinstance (multiparams[0], list) or isinstance (multiparams[0], tuple): + return multiparams[0] + else: + return [multiparams[0]] + else: + return multiparams + def execute_clauseelement(self, elem, *multiparams, **params): + executemany = len(multiparams) > 0 + if executemany: + param = multiparams[0] + else: + param = params + return self.execute_compiled(elem.compile(engine=self.__engine, parameters=param), *multiparams, **params) + def execute_compiled(self, compiled, *multiparams, **params): + """executes a sql.Compiled object.""" + cursor = self.connection.cursor() + parameters = [compiled.get_params(**m) for m in self._params_to_listofdicts(*multiparams, **params)] + if len(parameters) == 1: + parameters = parameters[0] + def proxy(statement=None, parameters=None): + if statement is None: + return cursor + + parameters = self.__engine.dialect.convert_compiled_params(parameters) + self._execute_raw(statement, parameters, cursor=cursor, context=context) + return cursor + context = self.__engine.dialect.create_execution_context() + context.pre_exec(self.__engine, proxy, compiled, parameters) + proxy(str(compiled), parameters) + context.post_exec(self.__engine, proxy, compiled, parameters) + return ResultProxy(self.__engine, self, cursor, context, typemap=compiled.typemap) + + # poor man's multimethod/generic function thingy + executors = { + sql.ClauseElement : execute_clauseelement, + sql.ClauseVisitor : execute_compiled, + schema.SchemaItem:execute_default, + str.__mro__[-2] : execute_text + } + + def create(self, entity, **kwargs): + """creates a table or index given an appropriate schema object.""" + return self.__engine.create(entity, connection=self, **kwargs) + def drop(self, entity, **kwargs): + """drops a table or index given an appropriate schema object.""" + return self.__engine.drop(entity, connection=self, **kwargs) + def reflecttable(self, table, **kwargs): + """reflects the columns in the given table from the database.""" + return self.__engine.reflecttable(table, connection=self, **kwargs) + def default_schema_name(self): + return self.__engine.dialect.get_default_schema_name(self) + def run_callable(self, callable_): + return callable_(self) + def _execute_raw(self, statement, parameters=None, cursor=None, context=None, **kwargs): + if cursor is None: + cursor = self.connection.cursor() + try: + self.__engine.logger.info(statement) + self.__engine.logger.info(repr(parameters)) + if parameters is not None and isinstance(parameters, list) and len(parameters) > 0 and (isinstance(parameters[0], list) or isinstance(parameters[0], dict)): + self._executemany(cursor, statement, parameters, context=context) + else: + self._execute(cursor, statement, parameters, context=context) + self._autocommit(statement) + except: + raise + return cursor + + def _execute(self, c, statement, parameters, context=None): + if parameters is None: + if self.__engine.dialect.positional: + parameters = () + else: + parameters = {} + try: + self.__engine.dialect.do_execute(c, statement, parameters, context=context) + except Exception, e: + self._autorollback() + #self._rollback_impl() + if self.__close_with_result: + self.close() + raise exceptions.SQLError(statement, parameters, e) + def _executemany(self, c, statement, parameters, context=None): + try: + self.__engine.dialect.do_executemany(c, statement, parameters, context=context) + except Exception, e: + self._autorollback() + #self._rollback_impl() + if self.__close_with_result: + self.close() + raise exceptions.SQLError(statement, parameters, e) + def proxy(self, statement=None, parameters=None): + """executes the given statement string and parameter object. + the parameter object is expected to be the result of a call to compiled.get_params(). + This callable is a generic version of a connection/cursor-specific callable that + is produced within the execute_compiled method, and is used for objects that require + this style of proxy when outside of an execute_compiled method, primarily the DefaultRunner.""" + parameters = self.__engine.dialect.convert_compiled_params(parameters) + return self._execute_raw(statement, parameters) + +class Transaction(object): + """represents a Transaction in progress. + + the Transaction object is **not** threadsafe.""" + def __init__(self, connection, parent): + self.__connection = connection + self.__parent = parent or self + self.__is_active = True + if self.__parent is self: + self.__connection._begin_impl() + connection = property(lambda s:s.__connection, doc="The Connection object referenced by this Transaction") + is_active = property(lambda s:s.__is_active) + def rollback(self): + if not self.__parent.__is_active: + return + if self.__parent is self: + self.__connection._rollback_impl() + self.__is_active = False + else: + self.__parent.rollback() + def commit(self): + if not self.__parent.__is_active: + raise exceptions.InvalidRequestError("This transaction is inactive") + if self.__parent is self: + self.__connection._commit_impl() + self.__is_active = False + +class Engine(sql.Executor, Connectable): + """ + Connects a ConnectionProvider, a Dialect and a CompilerFactory together to + provide a default implementation of SchemaEngine. + """ + def __init__(self, connection_provider, dialect, echo=None): + self.connection_provider = connection_provider + self.dialect=dialect + self.echo = echo + self.logger = logging.instance_logger(self) + + name = property(lambda s:sys.modules[s.dialect.__module__].descriptor()['name']) + engine = property(lambda s:s) + echo = logging.echo_property() + + def dispose(self): + self.connection_provider.dispose() + def create(self, entity, connection=None, **kwargs): + """creates a table or index within this engine's database connection given a schema.Table object.""" + self._run_visitor(self.dialect.schemagenerator, entity, connection=connection, **kwargs) + def drop(self, entity, connection=None, **kwargs): + """drops a table or index within this engine's database connection given a schema.Table object.""" + self._run_visitor(self.dialect.schemadropper, entity, connection=connection, **kwargs) + def execute_default(self, default, **kwargs): + connection = self.contextual_connect() + try: + return connection.execute_default(default, **kwargs) + finally: + connection.close() + + def _func(self): + return sql._FunctionGenerator(self) + func = property(_func) + def text(self, text, *args, **kwargs): + """returns a sql.text() object for performing literal queries.""" + return sql.text(text, engine=self, *args, **kwargs) + + def _run_visitor(self, visitorcallable, element, connection=None, **kwargs): + if connection is None: + conn = self.contextual_connect() + else: + conn = connection + try: + element.accept_schema_visitor(visitorcallable(self, conn.proxy, connection=conn, **kwargs), traverse=False) + finally: + if connection is None: + conn.close() + + def transaction(self, callable_, connection=None, *args, **kwargs): + """executes the given function within a transaction boundary. this is a shortcut for + explicitly calling begin() and commit() and optionally rollback() when execptions are raised. + The given *args and **kwargs will be passed to the function, as well as the Connection used + in the transaction.""" + if connection is None: + conn = self.contextual_connect() + else: + conn = connection + try: + trans = conn.begin() + try: + ret = callable_(conn, *args, **kwargs) + trans.commit() + return ret + except: + trans.rollback() + raise + finally: + if connection is None: + conn.close() + + def run_callable(self, callable_, connection=None, *args, **kwargs): + if connection is None: + conn = self.contextual_connect() + else: + conn = connection + try: + return callable_(conn, *args, **kwargs) + finally: + if connection is None: + conn.close() + + def execute(self, statement, *multiparams, **params): + connection = self.contextual_connect(close_with_result=True) + return connection.execute(statement, *multiparams, **params) + + def scalar(self, statement, *multiparams, **params): + return self.execute(statement, *multiparams, **params).scalar() + + def execute_compiled(self, compiled, *multiparams, **params): + connection = self.contextual_connect(close_with_result=True) + return connection.execute_compiled(compiled, *multiparams, **params) + + def compiler(self, statement, parameters, **kwargs): + return self.dialect.compiler(statement, parameters, engine=self, **kwargs) + + def connect(self, **kwargs): + """returns a newly allocated Connection object.""" + return Connection(self, **kwargs) + + def contextual_connect(self, close_with_result=False, **kwargs): + """returns a Connection object which may be newly allocated, or may be part of some + ongoing context. This Connection is meant to be used by the various "auto-connecting" operations.""" + return Connection(self, close_with_result=close_with_result, **kwargs) + + def reflecttable(self, table, connection=None): + """given a Table object, reflects its columns and properties from the database.""" + if connection is None: + conn = self.contextual_connect() + else: + conn = connection + try: + self.dialect.reflecttable(conn, table) + finally: + if connection is None: + conn.close() + def has_table(self, table_name): + return self.run_callable(lambda c: self.dialect.has_table(c, table_name)) + + def raw_connection(self): + """returns a DBAPI connection.""" + return self.connection_provider.get_connection() + + def log(self, msg): + """logs a message using this SQLEngine's logger stream.""" + self.logger.info(msg) + +class ResultProxy(object): + """wraps a DBAPI cursor object to provide access to row columns based on integer + position, case-insensitive column name, or by schema.Column object. e.g.: + + row = fetchone() + + col1 = row[0] # access via integer position + + col2 = row['col2'] # access via name + + col3 = row[mytable.c.mycol] # access via Column object. + + ResultProxy also contains a map of TypeEngine objects and will invoke the appropriate + convert_result_value() method before returning columns, as well as the ExecutionContext + corresponding to the statement execution. It provides several methods for which + to obtain information from the underlying ExecutionContext. + """ + class AmbiguousColumn(object): + def __init__(self, key): + self.key = key + def dialect_impl(self, dialect): + return self + def convert_result_value(self, arg, engine): + raise exceptions.InvalidRequestError("Ambiguous column name '%s' in result set! try 'use_labels' option on select statement." % (self.key)) + + def __init__(self, engine, connection, cursor, executioncontext=None, typemap=None): + """ResultProxy objects are constructed via the execute() method on SQLEngine.""" + self.connection = connection + self.dialect = engine.dialect + self.cursor = cursor + self.engine = engine + self.closed = False + if executioncontext is not None: + self.__executioncontext = executioncontext + self.rowcount = executioncontext.get_rowcount(cursor) + else: + self.rowcount = cursor.rowcount + self.__key_cache = {} + self.__echo = engine.echo == 'debug' + metadata = cursor.description + self.props = {} + self.keys = [] + i = 0 + if metadata is not None: + for item in metadata: + # sqlite possibly prepending table name to colnames so strip + colname = item[0].split('.')[-1].lower() + if typemap is not None: + rec = (typemap.get(colname, types.NULLTYPE), i) + else: + rec = (types.NULLTYPE, i) + if rec[0] is None: + raise DBAPIError("None for metadata " + colname) + if self.props.setdefault(colname, rec) is not rec: + self.props[colname] = (ResultProxy.AmbiguousColumn(colname), 0) + self.keys.append(colname) + self.props[i] = rec + i+=1 + def _executioncontext(self): + try: + return self.__executioncontext + except AttributeError: + raise exceptions.InvalidRequestError("This ResultProxy does not have an execution context with which to complete this operation. Execution contexts are not generated for literal SQL execution.") + executioncontext = property(_executioncontext) + + def close(self): + """close this ResultProxy, and the underlying DBAPI cursor corresponding to the execution. + + If this ResultProxy was generated from an implicit execution, the underlying Connection will + also be closed (returns the underlying DBAPI connection to the connection pool.) + + This method is also called automatically when all result rows are exhausted.""" + if not self.closed: + self.closed = True + self.cursor.close() + if self.connection.should_close_with_result and self.dialect.supports_autoclose_results: + self.connection.close() + + def _convert_key(self, key): + """given a key, which could be a ColumnElement, string, etc., matches it to the + appropriate key we got from the result set's metadata; then cache it locally for quick re-access.""" + try: + return self.__key_cache[key] + except KeyError: + if isinstance(key, sql.ColumnElement): + try: + rec = self.props[key._label.lower()] + except KeyError: + try: + rec = self.props[key.key.lower()] + except KeyError: +# rec = self.props[key.name.lower()] + try: + rec = self.props[key.name.lower()] + except KeyError: + raise exceptions.NoSuchColumnError("Could not locate column in row for column '%s'" % str(key)) + elif isinstance(key, str): + rec = self.props[key.lower()] + else: + rec = self.props[key] + self.__key_cache[key] = rec + return rec + + def _has_key(self, row, key): + try: + self._convert_key(key) + return True + except exceptions.NoSuchColumnError: + return False + + def _get_col(self, row, key): + rec = self._convert_key(key) + return rec[0].dialect_impl(self.dialect).convert_result_value(row[rec[1]], self.dialect) + + def __iter__(self): + while True: + row = self.fetchone() + if row is None: + raise StopIteration + else: + yield row + + def last_inserted_ids(self): + """return last_inserted_ids() from the underlying ExecutionContext. + + See ExecutionContext for details.""" + return self.executioncontext.last_inserted_ids() + def last_updated_params(self): + """return last_updated_params() from the underlying ExecutionContext. + + See ExecutionContext for details.""" + return self.executioncontext.last_updated_params() + def last_inserted_params(self): + """return last_inserted_params() from the underlying ExecutionContext. + + See ExecutionContext for details.""" + return self.executioncontext.last_inserted_params() + def lastrow_has_defaults(self): + """return lastrow_has_defaults() from the underlying ExecutionContext. + + See ExecutionContext for details.""" + return self.executioncontext.lastrow_has_defaults() + def supports_sane_rowcount(self): + """return supports_sane_rowcount() from the underlying ExecutionContext. + + See ExecutionContext for details.""" + return self.executioncontext.supports_sane_rowcount() + + def fetchall(self): + """fetch all rows, just like DBAPI cursor.fetchall().""" + l = [] + for row in self.cursor.fetchall(): + l.append(RowProxy(self, row)) + self.close() + return l + + def fetchone(self): + """fetch one row, just like DBAPI cursor.fetchone().""" + row = self.cursor.fetchone() + if row is not None: + return RowProxy(self, row) + else: + # controversy! can we auto-close the cursor after results are consumed ? + # what if the returned rows are still hanging around, and are "live" objects + # and not just plain tuples ? + self.close() + return None + + def scalar(self): + """fetch the first column of the first row, and close the result set.""" + row = self.cursor.fetchone() + try: + if row is not None: + return RowProxy(self, row)[0] + else: + return None + finally: + self.close() + +class RowProxy(object): + """proxies a single cursor row for a parent ResultProxy. Mostly follows + "ordered dictionary" behavior, mapping result values to the string-based column name, + the integer position of the result in the row, as well as Column instances which + can be mapped to the original Columns that produced this result set (for results + that correspond to constructed SQL expressions).""" + def __init__(self, parent, row): + """RowProxy objects are constructed by ResultProxy objects.""" + self.__parent = parent + self.__row = row + if self.__parent._ResultProxy__echo: + self.__parent.engine.logger.debug("Row " + repr(row)) + def close(self): + """close the parent ResultProxy.""" + self.__parent.close() + def __iter__(self): + for i in range(0, len(self.__row)): + yield self.__parent._get_col(self.__row, i) + def __eq__(self, other): + return (other is self) or (other == tuple([self.__parent._get_col(self.__row, key) for key in range(0, len(self.__row))])) + def __repr__(self): + return repr(tuple([self.__parent._get_col(self.__row, key) for key in range(0, len(self.__row))])) + def has_key(self, key): + """return True if this RowProxy contains the given key.""" + return self.__parent._has_key(self.__row, key) + def __getitem__(self, key): + return self.__parent._get_col(self.__row, key) + def __getattr__(self, name): + try: + return self.__parent._get_col(self.__row, name) + except KeyError, e: + raise AttributeError(e.args[0]) + def items(self): + """return a list of tuples, each tuple containing a key/value pair.""" + return [(key, getattr(self, key)) for key in self.keys()] + def keys(self): + """return the list of keys as strings represented by this RowProxy.""" + return self.__parent.keys + def values(self): + """return the values represented by this RowProxy as a list.""" + return list(self) + def __len__(self): + return len(self.__row) + +class SchemaIterator(schema.SchemaVisitor): + """a visitor that can gather text into a buffer and execute the contents of the buffer.""" + def __init__(self, engine, proxy, **params): + """construct a new SchemaIterator. + + engine - the Engine used by this SchemaIterator + + proxy - a callable which takes a statement and bind parameters and executes it, returning + the cursor (the actual DBAPI cursor). The callable should use the same cursor repeatedly.""" + self.proxy = proxy + self.engine = engine + self.buffer = StringIO.StringIO() + + def append(self, s): + """append content to the SchemaIterator's query buffer.""" + self.buffer.write(s) + + def execute(self): + """execute the contents of the SchemaIterator's buffer.""" + try: + return self.proxy(self.buffer.getvalue(), None) + finally: + self.buffer.truncate(0) + +class DefaultRunner(schema.SchemaVisitor): + """a visitor which accepts ColumnDefault objects, produces the dialect-specific SQL corresponding + to their execution, and executes the SQL, returning the result value. + + DefaultRunners are used internally by Engines and Dialects. Specific database modules should provide + their own subclasses of DefaultRunner to allow database-specific behavior.""" + def __init__(self, engine, proxy): + self.proxy = proxy + self.engine = engine + + def get_column_default(self, column): + if column.default is not None: + return column.default.accept_schema_visitor(self) + else: + return None + + def get_column_onupdate(self, column): + if column.onupdate is not None: + return column.onupdate.accept_schema_visitor(self) + else: + return None + + def visit_passive_default(self, default): + """passive defaults by definition return None on the app side, + and are post-fetched to get the DB-side value""" + return None + + def visit_sequence(self, seq): + """sequences are not supported by default""" + return None + + def exec_default_sql(self, default): + c = sql.select([default.arg], engine=self.engine).compile() + return self.proxy(str(c), c.get_params()).fetchone()[0] + + def visit_column_onupdate(self, onupdate): + if isinstance(onupdate.arg, sql.ClauseElement): + return self.exec_default_sql(onupdate) + elif callable(onupdate.arg): + return onupdate.arg() + else: + return onupdate.arg + + def visit_column_default(self, default): + if isinstance(default.arg, sql.ClauseElement): + return self.exec_default_sql(default) + elif callable(default.arg): + return default.arg() + else: + return default.arg diff --git a/spyce-2.1/sqlalchemy/engine/base.pyc b/spyce-2.1/sqlalchemy/engine/base.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c19c9b1bed66cdeaa352b4d3bd2038a98bf9b820 Binary files /dev/null and b/spyce-2.1/sqlalchemy/engine/base.pyc differ diff --git a/spyce-2.1/sqlalchemy/engine/default.py b/spyce-2.1/sqlalchemy/engine/default.py new file mode 100755 index 0000000000000000000000000000000000000000..4af539e784021a44352d0b87bfc10d4b5d246e7d --- /dev/null +++ b/spyce-2.1/sqlalchemy/engine/default.py @@ -0,0 +1,222 @@ +# engine/default.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +from sqlalchemy import schema, exceptions, util, sql, types +import StringIO, sys, re +from sqlalchemy.engine import base + +"""provides default implementations of the engine interfaces""" + + +class PoolConnectionProvider(base.ConnectionProvider): + def __init__(self, pool): + self._pool = pool + def get_connection(self): + return self._pool.connect() + def dispose(self): + self._pool.dispose() + if hasattr(self, '_dbproxy'): + self._dbproxy.dispose() + +class DefaultDialect(base.Dialect): + """default implementation of Dialect""" + def __init__(self, convert_unicode=False, encoding='utf-8', **kwargs): + self.convert_unicode = convert_unicode + self.supports_autoclose_results = True + self.encoding = encoding + self.positional = False + self.paramstyle = 'named' + self._ischema = None + self._figure_paramstyle() + def create_execution_context(self): + return DefaultExecutionContext(self) + def type_descriptor(self, typeobj): + """provides a database-specific TypeEngine object, given the generic object + which comes from the types module. Subclasses will usually use the adapt_type() + method in the types module to make this job easy.""" + if type(typeobj) is type: + typeobj = typeobj() + return typeobj + def oid_column_name(self): + return None + def supports_sane_rowcount(self): + return True + def do_begin(self, connection): + """implementations might want to put logic here for turning autocommit on/off, + etc.""" + pass + def do_rollback(self, connection): + """implementations might want to put logic here for turning autocommit on/off, + etc.""" + #print "ENGINE ROLLBACK ON ", connection.connection + connection.rollback() + def do_commit(self, connection): + """implementations might want to put logic here for turning autocommit on/off, etc.""" + #print "ENGINE COMMIT ON ", connection.connection + connection.commit() + def do_executemany(self, cursor, statement, parameters, **kwargs): + cursor.executemany(statement, parameters) + def do_execute(self, cursor, statement, parameters, **kwargs): + cursor.execute(statement, parameters) + def defaultrunner(self, engine, proxy): + return base.DefaultRunner(engine, proxy) + + def _set_paramstyle(self, style): + self._paramstyle = style + self._figure_paramstyle(style) + paramstyle = property(lambda s:s._paramstyle, _set_paramstyle) + + def convert_compiled_params(self, parameters): + executemany = parameters is not None and isinstance(parameters, list) + # the bind params are a CompiledParams object. but all the DBAPI's hate + # that object (or similar). so convert it to a clean + # dictionary/list/tuple of dictionary/tuple of list + if parameters is not None: + if self.positional: + if executemany: + parameters = [p.get_raw_list() for p in parameters] + else: + parameters = parameters.get_raw_list() + else: + if executemany: + parameters = [p.get_raw_dict() for p in parameters] + else: + parameters = parameters.get_raw_dict() + return parameters + + def _figure_paramstyle(self, paramstyle=None): + db = self.dbapi() + if paramstyle is not None: + self._paramstyle = paramstyle + elif db is not None: + self._paramstyle = db.paramstyle + else: + self._paramstyle = 'named' + + if self._paramstyle == 'named': + self.positional=False + elif self._paramstyle == 'pyformat': + self.positional=False + elif self._paramstyle == 'qmark' or self._paramstyle == 'format' or self._paramstyle == 'numeric': + # for positional, use pyformat internally, ANSICompiler will convert + # to appropriate character upon compilation + self.positional = True + else: + raise DBAPIError("Unsupported paramstyle '%s'" % self._paramstyle) + + def _get_ischema(self): + # We use a property for ischema so that the accessor + # creation only happens as needed, since otherwise we + # have a circularity problem with the generic + # ansisql.engine() + if self._ischema is None: + import sqlalchemy.databases.information_schema as ischema + self._ischema = ischema.ISchema(self) + return self._ischema + ischema = property(_get_ischema, doc="""returns an ISchema object for this engine, which allows access to information_schema tables (if supported)""") + +class DefaultExecutionContext(base.ExecutionContext): + def __init__(self, dialect): + self.dialect = dialect + def pre_exec(self, engine, proxy, compiled, parameters): + self._process_defaults(engine, proxy, compiled, parameters) + def post_exec(self, engine, proxy, compiled, parameters): + pass + def get_rowcount(self, cursor): + if hasattr(self, '_rowcount'): + return self._rowcount + else: + return cursor.rowcount + def supports_sane_rowcount(self): + return self.dialect.supports_sane_rowcount() + def last_inserted_ids(self): + return self._last_inserted_ids + def last_inserted_params(self): + return self._last_inserted_params + def last_updated_params(self): + return self._last_updated_params + def lastrow_has_defaults(self): + return self._lastrow_has_defaults + def set_input_sizes(self, cursor, parameters): + """given a cursor and ClauseParameters, call the appropriate style of + setinputsizes() on the cursor, using DBAPI types from the bind parameter's + TypeEngine objects.""" + if isinstance(parameters, list): + plist = parameters + else: + plist = [parameters] + if self.dialect.positional: + inputsizes = [] + for params in plist[0:1]: + for key in params.positional: + typeengine = params.binds[key].type + inputsizes.append(typeengine.get_dbapi_type(self.dialect.module)) + cursor.setinputsizes(*inputsizes) + else: + inputsizes = {} + for params in plist[0:1]: + for key in params.keys(): + typeengine = params.binds[key].type + inputsizes[key] = typeengine.get_dbapi_type(self.dialect.module) + cursor.setinputsizes(**inputsizes) + + def _process_defaults(self, engine, proxy, compiled, parameters): + """INSERT and UPDATE statements, when compiled, may have additional columns added to their + VALUES and SET lists corresponding to column defaults/onupdates that are present on the + Table object (i.e. ColumnDefault, Sequence, PassiveDefault). This method pre-execs those + DefaultGenerator objects that require pre-execution and sets their values within the + parameter list, and flags the thread-local state about + PassiveDefault objects that may require post-fetching the row after it is inserted/updated. + This method relies upon logic within the ANSISQLCompiler in its visit_insert and + visit_update methods that add the appropriate column clauses to the statement when its + being compiled, so that these parameters can be bound to the statement.""" + if compiled is None: return + if getattr(compiled, "isinsert", False): + if isinstance(parameters, list): + plist = parameters + else: + plist = [parameters] + drunner = self.dialect.defaultrunner(engine, proxy) + self._lastrow_has_defaults = False + for param in plist: + last_inserted_ids = [] + need_lastrowid=False + for c in compiled.statement.table.c: + if not param.has_key(c.key) or param[c.key] is None: + if isinstance(c.default, schema.PassiveDefault): + self._lastrow_has_defaults = True + newid = drunner.get_column_default(c) + if newid is not None: + param[c.key] = newid + if c.primary_key: + last_inserted_ids.append(param[c.key]) + elif c.primary_key: + need_lastrowid = True + elif c.primary_key: + last_inserted_ids.append(param[c.key]) + if need_lastrowid: + self._last_inserted_ids = None + else: + self._last_inserted_ids = last_inserted_ids + #print "LAST INSERTED PARAMS", param + self._last_inserted_params = param + elif getattr(compiled, 'isupdate', False): + if isinstance(parameters, list): + plist = parameters + else: + plist = [parameters] + drunner = self.dialect.defaultrunner(engine, proxy) + self._lastrow_has_defaults = False + for param in plist: + for c in compiled.statement.table.c: + if c.onupdate is not None and (not param.has_key(c.key) or param[c.key] is None): + value = drunner.get_column_onupdate(c) + if value is not None: + param[c.key] = value + self._last_updated_params = param + + diff --git a/spyce-2.1/sqlalchemy/engine/default.pyc b/spyce-2.1/sqlalchemy/engine/default.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e012072ed6d4d43ccdbfad219208d5cd29be6e7 Binary files /dev/null and b/spyce-2.1/sqlalchemy/engine/default.pyc differ diff --git a/spyce-2.1/sqlalchemy/engine/strategies.py b/spyce-2.1/sqlalchemy/engine/strategies.py new file mode 100755 index 0000000000000000000000000000000000000000..d48412160d62f67c65e6fabc07a21b26b6a94eea --- /dev/null +++ b/spyce-2.1/sqlalchemy/engine/strategies.py @@ -0,0 +1,120 @@ +"""defines different strategies for creating new instances of sql.Engine. +by default there are two, one which is the "thread-local" strategy, one which is the "plain" strategy. +new strategies can be added via constructing a new EngineStrategy object which will add itself to the +list of available strategies here, or replace one of the existing name. +this can be accomplished via a mod; see the sqlalchemy/mods package for details.""" + + +from sqlalchemy.engine import base, default, threadlocal, url +from sqlalchemy import util, exceptions +from sqlalchemy import pool as poollib + +strategies = {} + +class EngineStrategy(object): + """defines a function that receives input arguments and produces an instance of sql.Engine, typically + an instance sqlalchemy.engine.base.Engine or a subclass.""" + def __init__(self, name): + """construct a new EngineStrategy object and sets it in the list of available strategies + under this name.""" + self.name = name + strategies[self.name] = self + def create(self, *args, **kwargs): + """given arguments, returns a new sql.Engine instance.""" + raise NotImplementedError() + +class DefaultEngineStrategy(EngineStrategy): + def create(self, name_or_url, **kwargs): + # create url.URL object + u = url.make_url(name_or_url) + + # get module from sqlalchemy.databases + module = u.get_module() + + dialect_args = {} + # consume dialect arguments from kwargs + for k in util.get_cls_kwargs(module.dialect): + if k in kwargs: + dialect_args[k] = kwargs.pop(k) + + # create dialect + dialect = module.dialect(**dialect_args) + + # assemble connection arguments + (cargs, cparams) = dialect.create_connect_args(u) + cparams.update(kwargs.pop('connect_args', {})) + + # look for existing pool or create + pool = kwargs.pop('pool', None) + if pool is None: + dbapi = kwargs.pop('module', dialect.dbapi()) + if dbapi is None: + raise exceptions.InvalidRequestError("Cant get DBAPI module for dialect '%s'" % dialect) + def connect(): + try: + return dbapi.connect(*cargs, **cparams) + except Exception, e: + raise exceptions.DBAPIError("Connection failed", e) + creator = kwargs.pop('creator', connect) + + poolclass = kwargs.pop('poolclass', getattr(module, 'poolclass', poollib.QueuePool)) + pool_args = {} + # consume pool arguments from kwargs, translating a few of the arguments + for k in util.get_cls_kwargs(poolclass): + tk = {'echo':'echo_pool', 'timeout':'pool_timeout', 'recycle':'pool_recycle'}.get(k, k) + if tk in kwargs: + pool_args[k] = kwargs.pop(tk) + pool_args['use_threadlocal'] = self.pool_threadlocal() + pool = poolclass(creator, **pool_args) + else: + if isinstance(pool, poollib.DBProxy): + pool = pool.get_pool(*cargs, **cparams) + else: + pool = pool + + provider = self.get_pool_provider(pool) + + # create engine. + engineclass = self.get_engine_cls() + engine_args = {} + for k in util.get_cls_kwargs(engineclass): + if k in kwargs: + engine_args[k] = kwargs.pop(k) + + # all kwargs should be consumed + if len(kwargs): + raise TypeError("Invalid argument(s) %s sent to create_engine(), using configuration %s/%s/%s. Please check that the keyword arguments are appropriate for this combination of components." % (','.join(["'%s'" % k for k in kwargs]), dialect.__class__.__name__, pool.__class__.__name__, engineclass.__name__)) + + return engineclass(provider, dialect, **engine_args) + + def pool_threadlocal(self): + raise NotImplementedError() + def get_pool_provider(self, pool): + raise NotImplementedError() + def get_engine_cls(self): + raise NotImplementedError() + +class PlainEngineStrategy(DefaultEngineStrategy): + def __init__(self): + DefaultEngineStrategy.__init__(self, 'plain') + def pool_threadlocal(self): + return False + def get_pool_provider(self, pool): + return default.PoolConnectionProvider(pool) + def get_engine_cls(self): + return base.Engine +PlainEngineStrategy() + +class ThreadLocalEngineStrategy(DefaultEngineStrategy): + def __init__(self): + DefaultEngineStrategy.__init__(self, 'threadlocal') + def pool_threadlocal(self): + return True + def get_pool_provider(self, pool): + return threadlocal.TLocalConnectionProvider(pool) + def get_engine_cls(self): + return threadlocal.TLEngine +ThreadLocalEngineStrategy() + + + diff --git a/spyce-2.1/sqlalchemy/engine/strategies.pyc b/spyce-2.1/sqlalchemy/engine/strategies.pyc new file mode 100644 index 0000000000000000000000000000000000000000..314191d3a6d8716fec0a257edc6a5059bbdef9fb Binary files /dev/null and b/spyce-2.1/sqlalchemy/engine/strategies.pyc differ diff --git a/spyce-2.1/sqlalchemy/engine/threadlocal.py b/spyce-2.1/sqlalchemy/engine/threadlocal.py new file mode 100755 index 0000000000000000000000000000000000000000..beac3ee3f9278e60b5e8563f1eef218c97e60172 --- /dev/null +++ b/spyce-2.1/sqlalchemy/engine/threadlocal.py @@ -0,0 +1,122 @@ +from sqlalchemy import schema, exceptions, util, sql, types +import StringIO, sys, re +from sqlalchemy.engine import base, default + +"""provides a thread-local transactional wrapper around the basic ComposedSQLEngine. multiple calls to engine.connect() +will return the same connection for the same thread. also provides begin/commit methods on the engine itself +which correspond to a thread-local transaction.""" + +class TLSession(object): + def __init__(self, engine): + self.engine = engine + self.__tcount = 0 + def get_connection(self, close_with_result=False): + try: + return self.__transaction._increment_connect() + except AttributeError: + return TLConnection(self, close_with_result=close_with_result) + def reset(self): + try: + self.__transaction._force_close() + del self.__transaction + del self.__trans + except AttributeError: + pass + self.__tcount = 0 + def in_transaction(self): + return self.__tcount > 0 + def begin(self): + if self.__tcount == 0: + self.__transaction = self.get_connection() + self.__trans = self.__transaction._begin() + self.__tcount += 1 + return self.__trans + def rollback(self): + if self.__tcount > 0: + try: + self.__trans._rollback_impl() + finally: + self.reset() + def commit(self): + if self.__tcount == 1: + try: + self.__trans._commit_impl() + finally: + self.reset() + elif self.__tcount > 1: + self.__tcount -= 1 + def is_begun(self): + return self.__tcount > 0 + +class TLConnection(base.Connection): + def __init__(self, session, close_with_result): + base.Connection.__init__(self, session.engine, close_with_result=close_with_result) + self.__session = session + self.__opencount = 1 + session = property(lambda s:s.__session) + def _increment_connect(self): + self.__opencount += 1 + return self + def _create_transaction(self, parent): + return TLTransaction(self, parent) + def _begin(self): + return base.Connection.begin(self) + def in_transaction(self): + return self.session.in_transaction() + def begin(self): + return self.session.begin() + def close(self): + if self.__opencount == 1: + base.Connection.close(self) + self.__opencount -= 1 + def _force_close(self): + self.__opencount = 0 + base.Connection.close(self) + +class TLTransaction(base.Transaction): + def _commit_impl(self): + base.Transaction.commit(self) + def _rollback_impl(self): + base.Transaction.rollback(self) + def commit(self): + self.connection.session.commit() + def rollback(self): + self.connection.session.rollback() + +class TLEngine(base.Engine): + """an Engine that includes support for thread-local managed transactions. This engine + is better suited to be used with threadlocal Pool object.""" + def __init__(self, *args, **kwargs): + """the TLEngine relies upon the ConnectionProvider having "threadlocal" behavior, + so that once a connection is checked out for the current thread, you get that same connection + repeatedly.""" + super(TLEngine, self).__init__(*args, **kwargs) + self.context = util.ThreadLocal() + def raw_connection(self): + """returns a DBAPI connection.""" + return self.connection_provider.get_connection() + def connect(self, **kwargs): + """returns a Connection that is not thread-locally scoped. this is the equilvalent to calling + "connect()" on a ComposedSQLEngine.""" + return base.Connection(self, self.connection_provider.unique_connection()) + + def _session(self): + if not hasattr(self.context, 'session'): + self.context.session = TLSession(self) + return self.context.session + session = property(_session, doc="returns the current thread's TLSession") + + def contextual_connect(self, **kwargs): + """returns a TLConnection which is thread-locally scoped.""" + return self.session.get_connection(**kwargs) + + def begin(self): + return self.session.begin() + def commit(self): + self.session.commit() + def rollback(self): + self.session.rollback() + +class TLocalConnectionProvider(default.PoolConnectionProvider): + def unique_connection(self): + return self._pool.unique_connection() diff --git a/spyce-2.1/sqlalchemy/engine/threadlocal.pyc b/spyce-2.1/sqlalchemy/engine/threadlocal.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa9de4686ab5c2a1139c7d2a270e99dcd3f22c1a Binary files /dev/null and b/spyce-2.1/sqlalchemy/engine/threadlocal.pyc differ diff --git a/spyce-2.1/sqlalchemy/engine/url.py b/spyce-2.1/sqlalchemy/engine/url.py new file mode 100755 index 0000000000000000000000000000000000000000..f492ce425c842f47348a0cd29255fba5d1e006db --- /dev/null +++ b/spyce-2.1/sqlalchemy/engine/url.py @@ -0,0 +1,127 @@ +import re +import cgi +import urllib +from sqlalchemy import exceptions + +"""provides the URL object as well as the make_url parsing function.""" + +class URL(object): + """represents the components of a URL used to connect to a database. + + This object is suitable to be passed directly to a create_engine() call. + The fields of the URL are parsed from a string by the module-level make_url() function. + the string format of the URL is an RFC-1738-style string. + + Attributes on URL include: + + drivername + + username + + password + + host + + port + + database + + query - a dictionary containing key/value pairs representing the URL's query string.""" + def __init__(self, drivername, username=None, password=None, host=None, port=None, database=None, query=None): + self.drivername = drivername + self.username = username + self.password = password + self.host = host + if port is not None: + self.port = int(port) + else: + self.port = None + self.database= database + self.query = query or {} + def __str__(self): + s = self.drivername + "://" + if self.username is not None: + s += self.username + if self.password is not None: + s += ':' + urllib.quote_plus(self.password) + s += "@" + if self.host is not None: + s += self.host + if self.port is not None: + s += ':' + str(self.port) + if self.database is not None: + s += '/' + self.database + if len(self.query): + keys = self.query.keys() + keys.sort() + s += '?' + "&".join(["%s=%s" % (k, self.query[k]) for k in keys]) + return s + def get_module(self): + """return the SQLAlchemy database module corresponding to this URL's driver name.""" + return getattr(__import__('sqlalchemy.databases.%s' % self.drivername).databases, self.drivername) + def translate_connect_args(self, names): + """translate this URL's attributes into a dictionary of connection arguments. + + given a list of argument names corresponding to the URL attributes ('host', 'database', 'username', 'password', 'port'), + will assemble the attribute values of this URL into the dictionary using the given names.""" + a = {} + attribute_names = ['host', 'database', 'username', 'password', 'port'] + for n in names: + sname = attribute_names.pop(0) + if n is None: + continue + if getattr(self, sname, None): + a[n] = getattr(self, sname) + return a + + +def make_url(name_or_url): + """given a string or unicode instance, produces a new URL instance. + + the given string is parsed according to the rfc1738 spec. + if an existing URL object is passed, just returns the object.""" + if isinstance(name_or_url, str) or isinstance(name_or_url, unicode): + return _parse_rfc1738_args(name_or_url) + else: + return name_or_url + +def _parse_rfc1738_args(name): + pattern = re.compile(r''' + (\w+):// + (?: + ([^:/]*) + (?::([^/]*))? + @)? + (?: + ([^/:]*) + (?::([^/]*))? + )? + (?:/(.*))? + ''' + , re.X) + + m = pattern.match(name) + if m is not None: + (name, username, password, host, port, database) = m.group(1, 2, 3, 4, 5, 6) + if database is not None: + tokens = database.split(r"?", 2) + database = tokens[0] + query = (len(tokens) > 1 and dict( cgi.parse_qsl(tokens[1]) ) or None) + else: + query = None + opts = {'username':username,'password':password,'host':host,'port':port,'database':database, 'query':query} + if opts['password'] is not None: + opts['password'] = urllib.unquote_plus(opts['password']) + return URL(name, **opts) + else: + raise exceptions.ArgumentError("Could not parse rfc1738 URL from string '%s'" % name) + +def _parse_keyvalue_args(name): + m = re.match( r'(\w+)://(.*)', name) + if m is not None: + (name, args) = m.group(1, 2) + opts = dict( cgi.parse_qsl( args ) ) + return URL(name, *opts) + else: + return None + diff --git a/spyce-2.1/sqlalchemy/engine/url.pyc b/spyce-2.1/sqlalchemy/engine/url.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea47cf7d5caae542b0457105c20a109da27ee8ca Binary files /dev/null and b/spyce-2.1/sqlalchemy/engine/url.pyc differ diff --git a/spyce-2.1/sqlalchemy/exceptions.py b/spyce-2.1/sqlalchemy/exceptions.py new file mode 100755 index 0000000000000000000000000000000000000000..16df317e2a0cef4c25c98a54e53034b83b4f10f4 --- /dev/null +++ b/spyce-2.1/sqlalchemy/exceptions.py @@ -0,0 +1,62 @@ +# exceptions.py - exceptions for SQLAlchemy +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +class SQLAlchemyError(Exception): + """generic error class""" + pass + +class SQLError(SQLAlchemyError): + """raised when the execution of a SQL statement fails. includes accessors + for the underlying exception, as well as the SQL and bind parameters""" + def __init__(self, statement, params, orig): + SQLAlchemyError.__init__(self, "(%s) %s"% (orig.__class__.__name__, str(orig))) + self.statement = statement + self.params = params + self.orig = orig + def __str__(self): + return SQLAlchemyError.__str__(self) + " " + repr(self.statement) + " " + repr(self.params) + +class ArgumentError(SQLAlchemyError): + """raised for all those conditions where invalid arguments are sent to constructed + objects. This error generally corresponds to construction time state errors.""" + pass + +class TimeoutError(SQLAlchemyError): + """raised when a connection pool times out on getting a connection""" + pass + +class ConcurrentModificationError(SQLAlchemyError): + """raised when a concurrent modification condition is detected""" + pass + +class FlushError(SQLAlchemyError): + """raised when an invalid condition is detected upon a flush()""" + pass + +class InvalidRequestError(SQLAlchemyError): + """sqlalchemy was asked to do something it cant do, return nonexistent data, etc. + This error generally corresponds to runtime state errors.""" + pass + +class NoSuchTableError(InvalidRequestError): + """sqlalchemy was asked to load a table's definition from the database, + but the table doesn't exist.""" + pass + +class AssertionError(SQLAlchemyError): + """corresponds to internal state being detected in an invalid state""" + pass + +class NoSuchColumnError(KeyError, SQLAlchemyError): + """raised by RowProxy when a nonexistent column is requested from a row""" + pass + +class DBAPIError(SQLAlchemyError): + """something weird happened with a particular DBAPI version""" + def __init__(self, message, orig): + SQLAlchemyError.__init__(self, "(%s) (%s) %s"% (message, orig.__class__.__name__, str(orig))) + self.orig = orig diff --git a/spyce-2.1/sqlalchemy/exceptions.pyc b/spyce-2.1/sqlalchemy/exceptions.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6a789d4fae3fb922efbc21c5c187f11067f1ce8 Binary files /dev/null and b/spyce-2.1/sqlalchemy/exceptions.pyc differ diff --git a/spyce-2.1/sqlalchemy/ext/__init__.py b/spyce-2.1/sqlalchemy/ext/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/spyce-2.1/sqlalchemy/ext/__init__.py @@ -0,0 +1 @@ + diff --git a/spyce-2.1/sqlalchemy/ext/__init__.pyc b/spyce-2.1/sqlalchemy/ext/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c078e885ca21b8d077dbe613687af72dab9f3008 Binary files /dev/null and b/spyce-2.1/sqlalchemy/ext/__init__.pyc differ diff --git a/spyce-2.1/sqlalchemy/ext/activemapper.py b/spyce-2.1/sqlalchemy/ext/activemapper.py new file mode 100755 index 0000000000000000000000000000000000000000..769c70b836de8bb3030060c1860d01155eadde45 --- /dev/null +++ b/spyce-2.1/sqlalchemy/ext/activemapper.py @@ -0,0 +1,308 @@ +from sqlalchemy import create_session, relation, mapper, \ + join, DynamicMetaData, class_mapper, \ + util, Integer +from sqlalchemy import and_, or_ +from sqlalchemy import Table, Column, ForeignKey +from sqlalchemy.ext.sessioncontext import SessionContext +from sqlalchemy.ext.assignmapper import assign_mapper +from sqlalchemy import backref as create_backref +import sqlalchemy + +import inspect +import sys + +# +# the "proxy" to the database engine... this can be swapped out at runtime +# +metadata = DynamicMetaData("activemapper") + +try: + objectstore = sqlalchemy.objectstore +except AttributeError: + # thread local SessionContext + class Objectstore(object): + def __init__(self, *args, **kwargs): + self.context = SessionContext(*args, **kwargs) + def __getattr__(self, name): + return getattr(self.context.current, name) + session = property(lambda s:s.context.current) + + objectstore = Objectstore(create_session) + + +# +# declarative column declaration - this is so that we can infer the colname +# +class column(object): + def __init__(self, coltype, colname=None, foreign_key=None, + primary_key=False, *args, **kwargs): + if isinstance(foreign_key, basestring): + foreign_key = ForeignKey(foreign_key) + + self.coltype = coltype + self.colname = colname + self.foreign_key = foreign_key + self.primary_key = primary_key + self.kwargs = kwargs + self.args = args + +# +# declarative relationship declaration +# +class relationship(object): + def __init__(self, classname, colname=None, backref=None, private=False, + lazy=True, uselist=True, secondary=None, order_by=False): + self.classname = classname + self.colname = colname + self.backref = backref + self.private = private + self.lazy = lazy + self.uselist = uselist + self.secondary = secondary + self.order_by = order_by + + def process(self, klass, propname, relations): + relclass = ActiveMapperMeta.classes[self.classname] + + if isinstance(self.order_by, str): + self.order_by = [ self.order_by ] + + if isinstance(self.order_by, list): + for itemno in range(len(self.order_by)): + if isinstance(self.order_by[itemno], str): + self.order_by[itemno] = \ + getattr(relclass.c, self.order_by[itemno]) + + backref = self.create_backref(klass) + relations[propname] = relation(relclass.mapper, + secondary=self.secondary, + backref=backref, + private=self.private, + lazy=self.lazy, + uselist=self.uselist, + order_by=self.order_by) + + def create_backref(self, klass): + if self.backref is None: + return None + + relclass = ActiveMapperMeta.classes[self.classname] + + if klass.__name__ == self.classname: + br_fkey = getattr(relclass.c, self.colname) + else: + br_fkey = None + + return create_backref(self.backref, foreignkey=br_fkey) + + +class one_to_many(relationship): + def __init__(self, classname, colname=None, backref=None, private=False, + lazy=True, order_by=False): + relationship.__init__(self, classname, colname, backref, private, + lazy, uselist=True, order_by=order_by) + + +class one_to_one(relationship): + def __init__(self, classname, colname=None, backref=None, private=False, + lazy=True, order_by=False): + relationship.__init__(self, classname, colname, backref, private, + lazy, uselist=False, order_by=order_by) + + def create_backref(self, klass): + if self.backref is None: + return None + + relclass = ActiveMapperMeta.classes[self.classname] + + if klass.__name__ == self.classname: + br_fkey = getattr(relclass.c, self.colname) + else: + br_fkey = None + + return create_backref(self.backref, foreignkey=br_fkey, uselist=False) + + +class many_to_many(relationship): + def __init__(self, classname, secondary, backref=None, lazy=True, + order_by=False): + relationship.__init__(self, classname, None, backref, False, lazy, + uselist=True, secondary=secondary, + order_by=order_by) + + +# +# SQLAlchemy metaclass and superclass that can be used to do SQLAlchemy +# mapping in a declarative way, along with a function to process the +# relationships between dependent objects as they come in, without blowing +# up if the classes aren't specified in a proper order +# + +__deferred_classes__ = {} +__processed_classes__ = {} +def process_relationships(klass, was_deferred=False): + # first, we loop through all of the relationships defined on the + # class, and make sure that the related class already has been + # completely processed and defer processing if it has not + defer = False + for propname, reldesc in klass.relations.items(): + found = (reldesc.classname == klass.__name__ or reldesc.classname in __processed_classes__) + if not found: + defer = True + break + + # next, we loop through all the columns looking for foreign keys + # and make sure that we can find the related tables (they do not + # have to be processed yet, just defined), and we defer if we are + # not able to find any of the related tables + if not defer: + for col in klass.columns: + if col.foreign_key is not None: + found = False + table_name = col.foreign_key._colspec.rsplit('.', 1)[0] + for other_klass in ActiveMapperMeta.classes.values(): + if other_klass.table.fullname.lower() == table_name.lower(): + found = True + + if not found: + defer = True + break + + if defer and not was_deferred: + __deferred_classes__[klass.__name__] = klass + + # if we are able to find all related and referred to tables, then + # we can go ahead and assign the relationships to the class + if not defer: + relations = {} + for propname, reldesc in klass.relations.items(): + reldesc.process(klass, propname, relations) + + class_mapper(klass).add_properties(relations) + if klass.__name__ in __deferred_classes__: + del __deferred_classes__[klass.__name__] + __processed_classes__[klass.__name__] = klass + + # finally, loop through the deferred classes and attempt to process + # relationships for them + if not was_deferred: + # loop through the list of deferred classes, processing the + # relationships, until we can make no more progress + last_count = len(__deferred_classes__) + 1 + while last_count > len(__deferred_classes__): + last_count = len(__deferred_classes__) + deferred = __deferred_classes__.copy() + for deferred_class in deferred.values(): + process_relationships(deferred_class, was_deferred=True) + + +class ActiveMapperMeta(type): + classes = {} + metadatas = util.Set() + def __init__(cls, clsname, bases, dict): + table_name = clsname.lower() + columns = [] + relations = {} + autoload = False + _metadata = getattr(sys.modules[cls.__module__], + "__metadata__", metadata) + version_id_col = None + version_id_col_object = None + + if 'mapping' in dict: + found_pk = False + + members = inspect.getmembers(dict.get('mapping')) + for name, value in members: + if name == '__table__': + table_name = value + continue + + if '__metadata__' == name: + _metadata= value + continue + + if '__autoload__' == name: + autoload = True + continue + + if '__version_id_col__' == name: + version_id_col = value + + if name.startswith('__'): continue + + if isinstance(value, column): + if value.primary_key == True: found_pk = True + + if value.foreign_key: + col = Column(value.colname or name, + value.coltype, + value.foreign_key, + primary_key=value.primary_key, + *value.args, **value.kwargs) + else: + col = Column(value.colname or name, + value.coltype, + primary_key=value.primary_key, + *value.args, **value.kwargs) + columns.append(col) + continue + + if isinstance(value, relationship): + relations[name] = value + + if not found_pk and not autoload: + col = Column('id', Integer, primary_key=True) + cls.mapping.id = col + columns.append(col) + + assert _metadata is not None, "No MetaData specified" + + ActiveMapperMeta.metadatas.add(_metadata) + + if not autoload: + cls.table = Table(table_name, _metadata, *columns) + cls.columns = columns + else: + cls.table = Table(table_name, _metadata, autoload=True) + cls.columns = cls.table._columns + + # check for inheritence + if version_id_col is not None: + version_id_col_object = getattr(cls.table.c, version_id_col, None) + assert(version_id_col_object is not None, "version_id_col (%s) does not exist." % version_id_col) + + if hasattr(bases[0], "mapping"): + cls._base_mapper= bases[0].mapper + assign_mapper(objectstore.context, cls, cls.table, + inherits=cls._base_mapper, version_id_col=version_id_col_object) + else: + assign_mapper(objectstore.context, cls, cls.table, version_id_col=version_id_col_object) + cls.relations = relations + ActiveMapperMeta.classes[clsname] = cls + + process_relationships(cls) + + super(ActiveMapperMeta, cls).__init__(clsname, bases, dict) + + + +class ActiveMapper(object): + __metaclass__ = ActiveMapperMeta + + def set(self, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, value) + + +# +# a utility function to create all tables for all ActiveMapper classes +# + +def create_tables(): + for metadata in ActiveMapperMeta.metadatas: + metadata.create_all() + +def drop_tables(): + for metadata in ActiveMapperMeta.metadatas: + metadata.drop_all() diff --git a/spyce-2.1/sqlalchemy/ext/assignmapper.py b/spyce-2.1/sqlalchemy/ext/assignmapper.py new file mode 100755 index 0000000000000000000000000000000000000000..e672d835e50378bcfb8e2b5c4eb7a65457d3a0f9 --- /dev/null +++ b/spyce-2.1/sqlalchemy/ext/assignmapper.py @@ -0,0 +1,36 @@ +from sqlalchemy import mapper, util, Query +import types + +def monkeypatch_query_method(ctx, class_, name): + def do(self, *args, **kwargs): + query = Query(class_, session=ctx.current) + return getattr(query, name)(*args, **kwargs) + setattr(class_, name, classmethod(do)) + +def monkeypatch_objectstore_method(ctx, class_, name): + def do(self, *args, **kwargs): + session = ctx.current + if name == "flush": + # flush expects a list of objects + self = [self] + return getattr(session, name)(self, *args, **kwargs) + setattr(class_, name, do) + +def assign_mapper(ctx, class_, *args, **kwargs): + if not isinstance(getattr(class_, '__init__'), types.MethodType): + def __init__(self, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, value) + class_.__init__ = __init__ + extension = kwargs.pop('extension', None) + if extension is not None: + extension = util.to_list(extension) + extension.append(ctx.mapper_extension) + else: + extension = ctx.mapper_extension + m = mapper(class_, extension=extension, *args, **kwargs) + class_.mapper = m + for name in ['get', 'select', 'select_by', 'selectone', 'get_by', 'join_to', 'join_via', 'count', 'count_by']: + monkeypatch_query_method(ctx, class_, name) + for name in ['flush', 'delete', 'expire', 'refresh', 'expunge', 'merge', 'save', 'update', 'save_or_update']: + monkeypatch_objectstore_method(ctx, class_, name) diff --git a/spyce-2.1/sqlalchemy/ext/assignmapper.pyc b/spyce-2.1/sqlalchemy/ext/assignmapper.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d4886c0b6c2249c7467bc3b583695998f8baea7 Binary files /dev/null and b/spyce-2.1/sqlalchemy/ext/assignmapper.pyc differ diff --git a/spyce-2.1/sqlalchemy/ext/associationproxy.py b/spyce-2.1/sqlalchemy/ext/associationproxy.py new file mode 100755 index 0000000000000000000000000000000000000000..c9160ded4672e7e6c4aa90b836bf67523b7c3452 --- /dev/null +++ b/spyce-2.1/sqlalchemy/ext/associationproxy.py @@ -0,0 +1,96 @@ +"""contains the AssociationProxy class, a Python property object which +provides transparent proxied access to the endpoint of an association object. + +See the example examples/association/proxied_association.py. +""" + +from sqlalchemy.orm import class_mapper + +class AssociationProxy(object): + """a property object that automatically sets up AssociationLists on a parent object.""" + def __init__(self, targetcollection, attr, creator=None): + """create a new association property. + + targetcollection - the attribute name which stores the collection of Associations + + attr - name of the attribute on the Association in which to get/set target values + + creator - optional callable which is used to create a new association object. this + callable is given a single argument which is an instance of the "proxied" object. + if creator is not given, the association object is created using the class associated + with the targetcollection attribute, using its __init__() constructor and setting + the proxied attribute. + """ + self.targetcollection = targetcollection + self.attr = attr + self.creator = creator + def __init_deferred(self): + prop = class_mapper(self._owner_class).props[self.targetcollection] + self._cls = prop.mapper.class_ + self._uselist = prop.uselist + def _get_class(self): + try: + return self._cls + except AttributeError: + self.__init_deferred() + return self._cls + def _get_uselist(self): + try: + return self._uselist + except AttributeError: + self.__init_deferred() + return self._uselist + cls = property(_get_class) + uselist = property(_get_uselist) + def create(self, target, **kw): + if self.creator is not None: + return self.creator(target, **kw) + else: + assoc = self.cls(**kw) + setattr(assoc, self.attr, target) + return assoc + def __get__(self, obj, owner): + self._owner_class = owner + if obj is None: + return self + storage_key = '_AssociationProxy_%s' % self.targetcollection + if self.uselist: + try: + return getattr(obj, storage_key) + except AttributeError: + a = _AssociationList(self, obj) + setattr(obj, storage_key, a) + return a + else: + return getattr(getattr(obj, self.targetcollection), self.attr) + def __set__(self, obj, value): + if self.uselist: + setattr(obj, self.targetcollection, [self.create(x) for x in value]) + else: + setattr(obj, self.targetcollection, self.create(value)) + def __del__(self, obj): + delattr(obj, self.targetcollection) + +class _AssociationList(object): + """generic proxying list which proxies list operations to a different + list-holding attribute of the parent object, converting Association objects + to and from a target attribute on each Association object.""" + def __init__(self, proxy, parent): + """create a new AssociationList.""" + self.proxy = proxy + self.parent = parent + def append(self, item, **kw): + a = self.proxy.create(item, **kw) + getattr(self.parent, self.proxy.targetcollection).append(a) + def __iter__(self): + return iter([getattr(x, self.proxy.attr) for x in getattr(self.parent, self.proxy.targetcollection)]) + def __repr__(self): + return repr([getattr(x, self.proxy.attr) for x in getattr(self.parent, self.proxy.targetcollection)]) + def __len__(self): + return len(getattr(self.parent, self.proxy.targetcollection)) + def __getitem__(self, index): + return getattr(getattr(self.parent, self.proxy.targetcollection)[index], self.proxy.attr) + def __setitem__(self, index, value): + a = self.proxy.create(item) + getattr(self.parent, self.proxy.targetcollection)[index] = a + diff --git a/spyce-2.1/sqlalchemy/ext/proxy.py b/spyce-2.1/sqlalchemy/ext/proxy.py new file mode 100755 index 0000000000000000000000000000000000000000..c7e707f8d8bb767b8919d7d2e533aa3e7539e3ca --- /dev/null +++ b/spyce-2.1/sqlalchemy/ext/proxy.py @@ -0,0 +1,99 @@ +try: + from threading import local +except ImportError: + from sqlalchemy.util import ThreadLocal as local + +from sqlalchemy import sql +from sqlalchemy.engine import create_engine, Engine + +__all__ = ['BaseProxyEngine', 'AutoConnectEngine', 'ProxyEngine'] + +class BaseProxyEngine(sql.Executor): + """Basis for all proxy engines.""" + + def get_engine(self): + raise NotImplementedError + + def set_engine(self, engine): + raise NotImplementedError + + engine = property(lambda s:s.get_engine(), lambda s,e:s.set_engine(e)) + + def execute_compiled(self, *args, **kwargs): + """this method is required to be present as it overrides the execute_compiled present in sql.Engine""" + return self.get_engine().execute_compiled(*args, **kwargs) + def compiler(self, *args, **kwargs): + """this method is required to be present as it overrides the compiler method present in sql.Engine""" + return self.get_engine().compiler(*args, **kwargs) + + def __getattr__(self, attr): + """provides proxying for methods that are not otherwise present on this BaseProxyEngine. Note + that methods which are present on the base class sql.Engine will *not* be proxied through this, + and must be explicit on this class.""" + # call get_engine() to give subclasses a chance to change + # connection establishment behavior + e = self.get_engine() + if e is not None: + return getattr(e, attr) + raise AttributeError("No connection established in ProxyEngine: " + " no access to %s" % attr) + + +class AutoConnectEngine(BaseProxyEngine): + """An SQLEngine proxy that automatically connects when necessary.""" + + def __init__(self, dburi, **kwargs): + BaseProxyEngine.__init__(self) + self.dburi = dburi + self.kwargs = kwargs + self._engine = None + + def get_engine(self): + if self._engine is None: + if callable(self.dburi): + dburi = self.dburi() + else: + dburi = self.dburi + self._engine = create_engine(dburi, **self.kwargs) + return self._engine + + +class ProxyEngine(BaseProxyEngine): + """Engine proxy for lazy and late initialization. + + This engine will delegate access to a real engine set with connect(). + """ + + def __init__(self, **kwargs): + BaseProxyEngine.__init__(self) + # create the local storage for uri->engine map and current engine + self.storage = local() + self.kwargs = kwargs + + def connect(self, *args, **kwargs): + """Establish connection to a real engine.""" + + kwargs.update(self.kwargs) + if not kwargs: + key = repr(args) + else: + key = "%s, %s" % (repr(args), repr(sorted(kwargs.items()))) + try: + map = self.storage.connection + except AttributeError: + self.storage.connection = {} + self.storage.engine = None + map = self.storage.connection + try: + self.storage.engine = map[key] + except KeyError: + map[key] = create_engine(*args, **kwargs) + self.storage.engine = map[key] + + def get_engine(self): + if not hasattr(self.storage, 'engine') or self.storage.engine is None: + raise AttributeError("No connection established") + return self.storage.engine + + def set_engine(self, engine): + self.storage.engine = engine diff --git a/spyce-2.1/sqlalchemy/ext/selectresults.py b/spyce-2.1/sqlalchemy/ext/selectresults.py new file mode 100755 index 0000000000000000000000000000000000000000..c2ad4091791dfedcff841dd34d2749bfa5a8fe91 --- /dev/null +++ b/spyce-2.1/sqlalchemy/ext/selectresults.py @@ -0,0 +1,166 @@ +import sqlalchemy.sql as sql +import sqlalchemy.orm as orm + +class SelectResultsExt(orm.MapperExtension): + """a MapperExtension that provides SelectResults functionality for the + results of query.select_by() and query.select()""" + def select_by(self, query, *args, **params): + return SelectResults(query, query.join_by(*args, **params)) + def select(self, query, arg=None, **kwargs): + if hasattr(arg, '_selectable'): + return orm.EXT_PASS + else: + return SelectResults(query, arg, ops=kwargs) + +class SelectResults(object): + """Builds a query one component at a time via separate method calls, + each call transforming the previous SelectResults instance into a new SelectResults + instance with further limiting criterion added. When interpreted + in an iterator context (such as via calling list(selectresults)), executes the query.""" + + def __init__(self, query, clause=None, ops={}, joinpoint=None): + """constructs a new SelectResults using the given Query object and optional WHERE + clause. ops is an optional dictionary of bind parameter values.""" + self._query = query + self._clause = clause + self._ops = {} + self._ops.update(ops) + self._joinpoint = joinpoint or (self._query.table, self._query.mapper) + + def count(self): + """executes the SQL count() function against the SelectResults criterion.""" + return self._query.count(self._clause, **self._ops) + + def _col_aggregate(self, col, func): + """executes func() function against the given column + + For performance, only use subselect if order_by attribute is set. + + """ + if self._ops.get('order_by'): + s1 = sql.select([col], self._clause, **self._ops).alias('u') + return sql.select([func(s1.corresponding_column(col))]).scalar() + else: + return sql.select([func(col)], self._clause, **self._ops).scalar() + + def min(self, col): + """executes the SQL min() function against the given column""" + return self._col_aggregate(col, sql.func.min) + + def max(self, col): + """executes the SQL max() function against the given column""" + return self._col_aggregate(col, sql.func.max) + + def sum(self, col): + """executes the SQL sum() function against the given column""" + return self._col_aggregate(col, sql.func.sum) + + def avg(self, col): + """executes the SQL avg() function against the given column""" + return self._col_aggregate(col, sql.func.avg) + + def clone(self): + """creates a copy of this SelectResults.""" + return SelectResults(self._query, self._clause, self._ops.copy(), self._joinpoint) + + def filter(self, clause): + """applies an additional WHERE clause against the query.""" + new = self.clone() + new._clause = sql.and_(self._clause, clause) + return new + + def select(self, clause): + return self.filter(clause) + + def order_by(self, order_by): + """apply an ORDER BY to the query.""" + new = self.clone() + new._ops['order_by'] = order_by + return new + + def limit(self, limit): + """apply a LIMIT to the query.""" + return self[:limit] + + def offset(self, offset): + """apply an OFFSET to the query.""" + return self[offset:] + + def list(self): + """return the results represented by this SelectResults as a list. + + this results in an execution of the underlying query.""" + return list(self) + + def select_from(self, from_obj): + """set the from_obj parameter of the query to a specific table or set of tables. + + from_obj is a list.""" + new = self.clone() + new._ops['from_obj'] = from_obj + return new + + def join_to(self, prop): + """join the table of this SelectResults to the table located against the given property name. + + subsequent calls to join_to or outerjoin_to will join against the rightmost table located from the + previous join_to or outerjoin_to call, searching for the property starting with the rightmost mapper + last located.""" + new = self.clone() + (clause, mapper) = self._join_to(prop, outerjoin=False) + new._ops['from_obj'] = [clause] + new._joinpoint = (clause, mapper) + return new + + def outerjoin_to(self, prop): + """outer join the table of this SelectResults to the table located against the given property name. + + subsequent calls to join_to or outerjoin_to will join against the rightmost table located from the + previous join_to or outerjoin_to call, searching for the property starting with the rightmost mapper + last located.""" + new = self.clone() + (clause, mapper) = self._join_to(prop, outerjoin=True) + new._ops['from_obj'] = [clause] + new._joinpoint = (clause, mapper) + return new + + def _join_to(self, prop, outerjoin=False): + [keys,p] = self._query._locate_prop(prop, start=self._joinpoint[1]) + clause = self._joinpoint[0] + mapper = self._joinpoint[1] + for key in keys: + prop = mapper.props[key] + if outerjoin: + clause = clause.outerjoin(prop.mapper.mapped_table, prop.get_join()) + else: + clause = clause.join(prop.mapper.mapped_table, prop.get_join()) + mapper = prop.mapper + return (clause, mapper) + + def compile(self): + return self._query.compile(self._clause, **self._ops) + + def __getitem__(self, item): + if isinstance(item, slice): + start = item.start + stop = item.stop + if (isinstance(start, int) and start < 0) or \ + (isinstance(stop, int) and stop < 0): + return list(self)[item] + else: + res = self.clone() + if start is not None and stop is not None: + res._ops.update(dict(offset=self._ops.get('offset', 0)+start, limit=stop-start)) + elif start is None and stop is not None: + res._ops.update(dict(limit=stop)) + elif start is not None and stop is None: + res._ops.update(dict(offset=self._ops.get('offset', 0)+start)) + if item.step is not None: + return list(res)[None:None:item.step] + else: + return res + else: + return list(self[item:item+1])[0] + + def __iter__(self): + return iter(self._query.select_whereclause(self._clause, **self._ops)) diff --git a/spyce-2.1/sqlalchemy/ext/sessioncontext.py b/spyce-2.1/sqlalchemy/ext/sessioncontext.py new file mode 100755 index 0000000000000000000000000000000000000000..f431f87c7fc2d5ab4e716b31e99d262ddc6409dc --- /dev/null +++ b/spyce-2.1/sqlalchemy/ext/sessioncontext.py @@ -0,0 +1,55 @@ +from sqlalchemy.util import ScopedRegistry +from sqlalchemy.orm.mapper import MapperExtension + +__all__ = ['SessionContext', 'SessionContextExt'] + +class SessionContext(object): + """A simple wrapper for ScopedRegistry that provides a "current" property + which can be used to get, set, or remove the session in the current scope. + + By default this object provides thread-local scoping, which is the default + scope provided by sqlalchemy.util.ScopedRegistry. + + Usage: + engine = create_engine(...) + def session_factory(): + return Session(bind_to=engine) + context = SessionContext(session_factory) + + s = context.current # get thread-local session + context.current = Session(bind_to=other_engine) # set current session + del context.current # discard the thread-local session (a new one will + # be created on the next call to context.current) + """ + def __init__(self, session_factory, scopefunc=None): + self.registry = ScopedRegistry(session_factory, scopefunc) + super(SessionContext, self).__init__() + + def get_current(self): + return self.registry() + def set_current(self, session): + self.registry.set(session) + def del_current(self): + self.registry.clear() + current = property(get_current, set_current, del_current, + """Property used to get/set/del the session in the current scope""") + + def _get_mapper_extension(self): + try: + return self._extension + except AttributeError: + self._extension = ext = SessionContextExt(self) + return ext + mapper_extension = property(_get_mapper_extension, + doc="""get a mapper extension that implements get_session using this context""") + + +class SessionContextExt(MapperExtension): + """a mapper extionsion that provides sessions to a mapper using SessionContext""" + + def __init__(self, context): + MapperExtension.__init__(self) + self.context = context + + def get_session(self): + return self.context.current diff --git a/spyce-2.1/sqlalchemy/ext/sessioncontext.pyc b/spyce-2.1/sqlalchemy/ext/sessioncontext.pyc new file mode 100644 index 0000000000000000000000000000000000000000..678b258d677a18a4be2a9dbb56ca2af6ceee5c78 Binary files /dev/null and b/spyce-2.1/sqlalchemy/ext/sessioncontext.pyc differ diff --git a/spyce-2.1/sqlalchemy/ext/sqlsoup.py b/spyce-2.1/sqlalchemy/ext/sqlsoup.py new file mode 100755 index 0000000000000000000000000000000000000000..d83ecfb590192ea0c89789fe2f227f91cc1fef8e --- /dev/null +++ b/spyce-2.1/sqlalchemy/ext/sqlsoup.py @@ -0,0 +1,429 @@ +""" +Introduction +============ + +SqlSoup provides a convenient way to access database tables without having +to declare table or mapper classes ahead of time. + +Suppose we have a database with users, books, and loans tables +(corresponding to the PyWebOff dataset, if you're curious). +For testing purposes, we'll create this db as follows: + + >>> from sqlalchemy import create_engine + >>> e = create_engine('sqlite:///:memory:') + >>> for sql in _testsql: e.execute(sql) #doctest: +ELLIPSIS + <... + +Creating a SqlSoup gateway is just like creating an SqlAlchemy engine: + + >>> from sqlalchemy.ext.sqlsoup import SqlSoup + >>> db = SqlSoup('sqlite:///:memory:') + +or, you can re-use an existing metadata: + + >>> db = SqlSoup(BoundMetaData(e)) + +You can optionally specify a schema within the database for your SqlSoup: + + # >>> db.schema = myschemaname + + +Loading objects +=============== + +Loading objects is as easy as this: + + >>> users = db.users.select() + >>> users.sort() + >>> users + [MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0), MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)] + +Of course, letting the database do the sort is better (".c" is short for ".columns"): + + >>> db.users.select(order_by=[db.users.c.name]) + [MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1), MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0)] + +Field access is intuitive: + + >>> users[0].email + u'student@example.edu' + +Of course, you don't want to load all users very often. Let's add a WHERE clause. +Let's also switch the order_by to DESC while we're at it. + + >>> from sqlalchemy import or_, and_, desc + >>> where = or_(db.users.c.name=='Bhargan Basepair', db.users.c.email=='student@example.edu') + >>> db.users.select(where, order_by=[desc(db.users.c.name)]) + [MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0), MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)] + +You can also use the select...by methods if you're querying on a single column. +This allows using keyword arguments as column names: + + >>> db.users.selectone_by(name='Bhargan Basepair') + MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1) + + +Select variants +--------------- + +All the SqlAlchemy Query select variants are available. +Here's a quick summary of these methods: + +- get(PK): load a single object identified by its primary key (either a scalar, or a tuple) +- select(Clause, \*\*kwargs): perform a select restricted by the Clause argument; returns a list of objects. The most common clause argument takes the form "db.tablename.c.columname == value." The most common optional argument is order_by. +- select_by(\*\*params): select methods ending with _by allow using bare column names. (columname=value) This feels more natural to most Python programmers; the downside is you can't specify order_by or other select options. +- selectfirst, selectfirst_by: returns only the first object found; equivalent to select(...)[0] or select_by(...)[0], except None is returned if no rows are selected. +- selectone, selectone_by: like selectfirst or selectfirst_by, but raises if less or more than one object is selected. +- count, count_by: returns an integer count of the rows selected. + +See the SqlAlchemy documentation for details: + +- http://www.sqlalchemy.org/docs/datamapping.myt#datamapping_query for general info and examples, +- http://www.sqlalchemy.org/docs/sqlconstruction.myt for details on constructing WHERE clauses. + + +Modifying objects +================= + +Modifying objects is intuitive: + + >>> user = _ + >>> user.email = 'basepair+nospam@example.edu' + >>> db.flush() + +(SqlSoup leverages the sophisticated SqlAlchemy unit-of-work code, so +multiple updates to a single object will be turned into a single UPDATE +statement when you flush.) + +To finish covering the basics, let's insert a new loan, then delete it: + + >>> book_id = db.books.selectfirst(db.books.c.title=='Regional Variation in Moss').id + >>> db.loans.insert(book_id=book_id, user_name=user.name) + MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None) + >>> db.flush() + + >>> loan = db.loans.selectone_by(book_id=2, user_name='Bhargan Basepair') + >>> db.delete(loan) + >>> db.flush() + +You can also delete rows that have not been loaded as objects. Let's do our insert/delete cycle once more, +this time using the loans table's delete method. (For SQLAlchemy experts: +note that no flush() call is required since this +delete acts at the SQL level, not at the Mapper level.) The same where-clause construction rules +apply here as to the select methods. + + >>> db.loans.insert(book_id=book_id, user_name=user.name) + MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None) + >>> db.flush() + >>> db.loans.delete(db.loans.c.book_id==2) + +You can similarly update multiple rows at once. This will change the book_id to 1 in all loans whose book_id is 2: + + >>> db.loans.update(db.loans.c.book_id==2, book_id=1) + >>> db.loans.select_by(db.loans.c.book_id==1) + [MappedLoans(book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))] + + +Joins +===== + +Occasionally, you will want to pull out a lot of data from related tables all at +once. In this situation, it is far +more efficient to have the database perform the necessary join. (Here +we do not have "a lot of data," but hopefully the concept is still clear.) +SQLAlchemy is smart enough to recognize that loans has a foreign key +to users, and uses that as the join condition automatically. + + >>> join1 = db.join(db.users, db.loans, isouter=True) + >>> join1.select_by(name='Joe Student') + [MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))] + +If you're unfortunate enough to be using MySQL with the default MyISAM +storage engine, you'll have to specify the join condition manually, +since MyISAM does not store foreign keys. Here's the same join again, +with the join condition explicitly specified: + + >>> db.join(db.users, db.loans, db.users.c.name==db.loans.c.user_name, isouter=True) + + +You can compose arbitrarily complex joins by combining Join objects with +tables or other joins. Here we combine our first join with the books table: + + >>> join2 = db.join(join1, db.books) + >>> join2.select() + [MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0),id=1,title='Mustards I Have Known',published_year='1989',authors='Jones')] + +If you join tables that have an identical column name, wrap your join with "with_labels", +to disambiguate columns with their table name: + + >>> db.with_labels(join1).c.keys() + ['users_name', 'users_email', 'users_password', 'users_classname', 'users_admin', 'loans_book_id', 'loans_user_name', 'loans_loan_date'] + + +Advanced Use +============ + +Mapping arbitrary Selectables +----------------------------- + +SqlSoup can map any SQLAlchemy Selectable with the map method. Let's map a Select object that uses an aggregate function; we'll use the SQLAlchemy Table that SqlSoup introspected as the basis. (Since we're not mapping to a simple table or join, we need to tell SQLAlchemy how to find the "primary key," which just needs to be unique within the select, and not necessarily correspond to a "real" PK in the database.) + + >>> from sqlalchemy import select, func + >>> b = db.books._table + >>> s = select([b.c.published_year, func.count('*').label('n')], from_obj=[b], group_by=[b.c.published_year]) + >>> s = s.alias('years_with_count') + >>> years_with_count = db.map(s, primary_key=[s.c.published_year]) + >>> years_with_count.select_by(published_year='1989') + [MappedBooks(published_year='1989',n=1)] + +Obviously if we just wanted to get a list of counts associated with book years once, raw SQL is going to be less work. The advantage of mapping a Select is reusability, both standalone and in Joins. (And if you go to full SQLAlchemy, you can perform mappings like this directly to your object models.) + + +Raw SQL +------- + +You can access the SqlSoup's ``engine`` attribute to compose SQL directly. +The engine's ``execute`` method corresponds +to the one of a DBAPI cursor, and returns a ``ResultProxy`` that has ``fetch`` methods +you would also see on a cursor. + + >>> rp = db.engine.execute('select name, email from users order by name') + >>> for name, email in rp.fetchall(): print name, email + Bhargan Basepair basepair+nospam@example.edu + Joe Student student@example.edu + +You can also pass this engine object to other SQLAlchemy constructs. + + +Extra tests +=========== + +Boring tests here. Nothing of real expository value. + + >>> db.users.select(db.users.c.classname==None, order_by=[db.users.c.name]) + [MappedUsers(name='Bhargan Basepair',email='basepair+nospam@example.edu',password='basepair',classname=None,admin=1), MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0)] + + >>> db.nopk + Traceback (most recent call last): + ... + PKNotFoundError: table 'nopk' does not have a primary key defined + + >>> db.nosuchtable + Traceback (most recent call last): + ... + NoSuchTableError: nosuchtable + + >>> years_with_count.insert(published_year='2007', n=1) + Traceback (most recent call last): + ... + InvalidRequestError: SQLSoup can only modify mapped Tables (found: Alias) +""" + +from sqlalchemy import * +from sqlalchemy.ext.sessioncontext import SessionContext +from sqlalchemy.ext.assignmapper import assign_mapper +from sqlalchemy.exceptions import * + + +_testsql = """ +CREATE TABLE books ( + id integer PRIMARY KEY, -- auto-SERIAL in sqlite + title text NOT NULL, + published_year char(4) NOT NULL, + authors text NOT NULL +); + +CREATE TABLE users ( + name varchar(32) PRIMARY KEY, + email varchar(128) NOT NULL, + password varchar(128) NOT NULL, + classname text, + admin int NOT NULL -- 0 = false +); + +CREATE TABLE loans ( + book_id int PRIMARY KEY REFERENCES books(id), + user_name varchar(32) references users(name) + ON DELETE SET NULL ON UPDATE CASCADE, + loan_date datetime DEFAULT current_timestamp +); + +insert into users(name, email, password, admin) +values('Bhargan Basepair', 'basepair@example.edu', 'basepair', 1); +insert into users(name, email, password, admin) +values('Joe Student', 'student@example.edu', 'student', 0); + +insert into books(title, published_year, authors) +values('Mustards I Have Known', '1989', 'Jones'); +insert into books(title, published_year, authors) +values('Regional Variation in Moss', '1971', 'Flim and Flam'); + +insert into loans(book_id, user_name, loan_date) +values ( + (select min(id) from books), + (select name from users where name like 'Joe%'), + '2006-07-12 0:0:0') +; + +CREATE TABLE nopk ( + i int +); +""".split(';') + +__all__ = ['PKNotFoundError', 'SqlSoup'] + +# +# thread local SessionContext +# +class Objectstore(SessionContext): + def __getattr__(self, key): + return getattr(self.current, key) + def get_session(self): + return self.current + +objectstore = Objectstore(create_session) + +class PKNotFoundError(SQLAlchemyError): pass + +# metaclass is necessary to expose class methods with getattr, e.g. +# we want to pass db.users.select through to users._mapper.select +def _ddl_check(cls): + if not isinstance(cls._table, Table): + msg = 'SQLSoup can only modify mapped Tables (found: %s)' \ + % cls._table.__class__.__name__ + raise InvalidRequestError(msg) +class TableClassType(type): + def insert(cls, **kwargs): + _ddl_check(cls) + o = cls() + o.__dict__.update(kwargs) + return o + def delete(cls, *args, **kwargs): + _ddl_check(cls) + cls._table.delete(*args, **kwargs).execute() + def update(cls, whereclause=None, values=None, **kwargs): + _ddl_check(cls) + cls._table.update(whereclause, values).execute(**kwargs) + def _selectable(cls): + return cls._table + def __getattr__(cls, attr): + if attr == '_query': + # called during mapper init + raise AttributeError() + return getattr(cls._query, attr) + + +def _is_outer_join(selectable): + if not isinstance(selectable, sql.Join): + return False + if selectable.isouter: + return True + return _is_outer_join(selectable.left) or _is_outer_join(selectable.right) + +def _selectable_name(selectable): + if isinstance(selectable, sql.Alias): + return _selectable_name(selectable.selectable) + elif isinstance(selectable, sql.Select): + # sometimes a Select has itself in _froms + nonrecursive_froms = [s for s in selectable._froms if s is not selectable] + return ''.join([_selectable_name(s) for s in nonrecursive_froms]) + elif isinstance(selectable, schema.Table): + return selectable.name.capitalize() + else: + x = selectable.__class__.__name__ + if x[0] == '_': + x = x[1:] + return x + +def class_for_table(selectable, **mapper_kwargs): + if not hasattr(selectable, '_selectable') \ + or selectable._selectable() != selectable: + raise 'class_for_table requires a selectable as its argument' + mapname = 'Mapped' + _selectable_name(selectable) + klass = TableClassType(mapname, (object,), {}) + def __cmp__(self, o): + L = self.__class__.c.keys() + L.sort() + t1 = [getattr(self, k) for k in L] + try: + t2 = [getattr(o, k) for k in L] + except AttributeError: + raise TypeError('unable to compare with %s' % o.__class__) + return cmp(t1, t2) + def __repr__(self): + import locale + encoding = locale.getdefaultlocale()[1] + L = [] + for k in self.__class__.c.keys(): + value = getattr(self, k, '') + if isinstance(value, unicode): + value = value.encode(encoding) + L.append("%s=%r" % (k, value)) + return '%s(%s)' % (self.__class__.__name__, ','.join(L)) + for m in ['__cmp__', '__repr__']: + setattr(klass, m, eval(m)) + klass._table = selectable + klass._mapper = mapper(klass, + selectable, + extension=objectstore.mapper_extension, + allow_null_pks=_is_outer_join(selectable), + **mapper_kwargs) + klass._query = Query(klass._mapper) + return klass + +class SqlSoup: + def __init__(self, *args, **kwargs): + """ + args may either be an SQLEngine or a set of arguments suitable + for passing to create_engine + """ + # meh, sometimes having method overloading instead of kwargs would be easier + if isinstance(args[0], MetaData): + args = list(args) + metadata = args.pop(0) + if args or kwargs: + raise ArgumentError('Extra arguments not allowed when metadata is given') + else: + metadata = BoundMetaData(*args, **kwargs) + self._metadata = metadata + self._cache = {} + self.schema = None + def engine(self): + return self._metadata._engine + engine = property(engine) + def delete(self, *args, **kwargs): + objectstore.delete(*args, **kwargs) + def flush(self): + objectstore.get_session().flush() + def rollback(self): + objectstore.clear() + def map(self, selectable, **kwargs): + try: + t = self._cache[selectable] + except KeyError: + t = class_for_table(selectable, **kwargs) + self._cache[selectable] = t + return t + def with_labels(self, item): + # TODO give meaningful aliases + return self.map(item._selectable().select(use_labels=True).alias('foo')) + def join(self, *args, **kwargs): + j = join(*args, **kwargs) + return self.map(j) + def __getattr__(self, attr): + try: + t = self._cache[attr] + except KeyError: + table = Table(attr, self._metadata, autoload=True, schema=self.schema) + if not table.primary_key.columns: + raise PKNotFoundError('table %r does not have a primary key defined' % attr) + if table.columns: + t = class_for_table(table) + else: + t = None + self._cache[attr] = t + return t + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/spyce-2.1/sqlalchemy/ext/sqlsoup.pyc b/spyce-2.1/sqlalchemy/ext/sqlsoup.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09e3f3eff876786f9ba895b50e50bfa3408b5036 Binary files /dev/null and b/spyce-2.1/sqlalchemy/ext/sqlsoup.pyc differ diff --git a/spyce-2.1/sqlalchemy/logging.py b/spyce-2.1/sqlalchemy/logging.py new file mode 100755 index 0000000000000000000000000000000000000000..b3960ba861c4396086c4cc2b75bfed261f4c274a --- /dev/null +++ b/spyce-2.1/sqlalchemy/logging.py @@ -0,0 +1,76 @@ +# logging.py - adapt python logging module to SQLAlchemy +# Copyright (C) 2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""provides a few functions used by instances to turn on/off their logging, including support +for the usual "echo" parameter. Control of logging for SA can be performed from the regular +python logging module. The regular dotted module namespace is used, starting at 'sqlalchemy'. +For class-level logging, the class name is appended, and for instance-level logging, the hex +id of the instance is appended. + +The "echo" keyword parameter which is available on some SA objects corresponds to an instance-level +logger for that instance. + +E.g.: + + engine.echo = True + +is equivalent to: + + import logging + logging.getLogger('sqlalchemy.engine.Engine.%s' % hex(id(engine))).setLevel(logging.DEBUG) + +""" + +import sys + +# py2.5 absolute imports will fix.... +logging = __import__('logging') + +# turn off logging at the root sqlalchemy level +logging.getLogger('sqlalchemy').setLevel(logging.ERROR) + +default_enabled = False +def default_logging(name): + global default_enabled + if logging.getLogger(name).getEffectiveLevel() < logging.WARN: + default_enabled=True + if not default_enabled: + default_enabled = True + rootlogger = logging.getLogger('sqlalchemy') + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(name)s %(message)s')) + rootlogger.addHandler(handler) + +def _get_instance_name(instance): + # since getLogger() does not have any way of removing logger objects from memory, + # instance logging displays the instance id as a modulus of 16 to prevent endless memory growth + # also speeds performance as logger initialization is apparently slow + return instance.__class__.__module__ + "." + instance.__class__.__name__ + ".0x.." + hex(id(instance))[-2:] + +def instance_logger(instance): + return logging.getLogger(_get_instance_name(instance)) + +def class_logger(cls): + return logging.getLogger(cls.__module__ + "." + cls.__name__) + +def is_debug_enabled(logger): + return logger.isEnabledFor(logging.DEBUG) +def is_info_enabled(logger): + return logger.isEnabledFor(logging.INFO) + +class echo_property(object): + level_map={logging.DEBUG : "debug", logging.INFO:True} + def __get__(self, instance, owner): + level = logging.getLogger(_get_instance_name(instance)).getEffectiveLevel() + return echo_property.level_map.get(level, False) + def __set__(self, instance, value): + if value: + default_logging(_get_instance_name(instance)) + logging.getLogger(_get_instance_name(instance)).setLevel(value == 'debug' and logging.DEBUG or logging.INFO) + else: + logging.getLogger(_get_instance_name(instance)).setLevel(logging.NOTSET) + + diff --git a/spyce-2.1/sqlalchemy/logging.pyc b/spyce-2.1/sqlalchemy/logging.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae8bb8e65a50208634da1887731b17f9f1e69d06 Binary files /dev/null and b/spyce-2.1/sqlalchemy/logging.pyc differ diff --git a/spyce-2.1/sqlalchemy/mods/__init__.py b/spyce-2.1/sqlalchemy/mods/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/spyce-2.1/sqlalchemy/mods/legacy_session.py b/spyce-2.1/sqlalchemy/mods/legacy_session.py new file mode 100755 index 0000000000000000000000000000000000000000..a28cd3dac7d68e667ff65f430aa073a2857f655f --- /dev/null +++ b/spyce-2.1/sqlalchemy/mods/legacy_session.py @@ -0,0 +1,140 @@ +"""a plugin that emulates 0.1 Session behavior.""" + +import sqlalchemy.orm.objectstore as objectstore +import sqlalchemy.orm.unitofwork as unitofwork +import sqlalchemy.util as util +import sqlalchemy + +import sqlalchemy.mods.threadlocal + +class LegacySession(objectstore.Session): + def __init__(self, nest_on=None, hash_key=None, **kwargs): + super(LegacySession, self).__init__(**kwargs) + self.parent_uow = None + self.begin_count = 0 + self.nest_on = util.to_list(nest_on) + self.__pushed_count = 0 + def was_pushed(self): + if self.nest_on is None: + return + self.__pushed_count += 1 + if self.__pushed_count == 1: + for n in self.nest_on: + n.push_session() + def was_popped(self): + if self.nest_on is None or self.__pushed_count == 0: + return + self.__pushed_count -= 1 + if self.__pushed_count == 0: + for n in self.nest_on: + n.pop_session() + class SessionTrans(object): + """returned by Session.begin(), denotes a transactionalized UnitOfWork instance. + call commit() on this to commit the transaction.""" + def __init__(self, parent, uow, isactive): + self.__parent = parent + self.__isactive = isactive + self.__uow = uow + isactive = property(lambda s:s.__isactive, doc="True if this SessionTrans is the 'active' transaction marker, else its a no-op.") + parent = property(lambda s:s.__parent, doc="returns the parent Session of this SessionTrans object.") + uow = property(lambda s:s.__uow, doc="returns the parent UnitOfWork corresponding to this transaction.") + def begin(self): + """calls begin() on the underlying Session object, returning a new no-op SessionTrans object.""" + if self.parent.uow is not self.uow: + raise InvalidRequestError("This SessionTrans is no longer valid") + return self.parent.begin() + def commit(self): + """commits the transaction noted by this SessionTrans object.""" + self.__parent._trans_commit(self) + self.__isactive = False + def rollback(self): + """rolls back the current UnitOfWork transaction, in the case that begin() + has been called. The changes logged since the begin() call are discarded.""" + self.__parent._trans_rollback(self) + self.__isactive = False + def begin(self): + """begins a new UnitOfWork transaction and returns a tranasaction-holding + object. commit() or rollback() should be called on the returned object. + commit() on the Session will do nothing while a transaction is pending, and further + calls to begin() will return no-op transactional objects.""" + if self.parent_uow is not None: + return LegacySession.SessionTrans(self, self.uow, False) + self.parent_uow = self.uow + self.uow = unitofwork.UnitOfWork(identity_map = self.uow.identity_map) + return LegacySession.SessionTrans(self, self.uow, True) + def commit(self, *objects): + """commits the current UnitOfWork transaction. called with + no arguments, this is only used + for "implicit" transactions when there was no begin(). + if individual objects are submitted, then only those objects are committed, and the + begin/commit cycle is not affected.""" + # if an object list is given, commit just those but dont + # change begin/commit status + if len(objects): + self._commit_uow(*objects) + self.uow.flush(self, *objects) + return + if self.parent_uow is None: + self._commit_uow() + def _trans_commit(self, trans): + if trans.uow is self.uow and trans.isactive: + try: + self._commit_uow() + finally: + self.uow = self.parent_uow + self.parent_uow = None + def _trans_rollback(self, trans): + if trans.uow is self.uow: + self.uow = self.parent_uow + self.parent_uow = None + def _commit_uow(self, *obj): + self.was_pushed() + try: + self.uow.flush(self, *obj) + finally: + self.was_popped() + +def begin(): + """deprecated. use s = Session(new_imap=False).""" + return objectstore.get_session().begin() + +def commit(*obj): + """deprecated; use flush(*obj)""" + objectstore.get_session().flush(*obj) + +def uow(): + return objectstore.get_session() + +def push_session(sess): + old = get_session() + if getattr(sess, '_previous', None) is not None: + raise InvalidRequestError("Given Session is already pushed onto some thread's stack") + sess._previous = old + session_registry.set(sess) + sess.was_pushed() + +def pop_session(): + sess = get_session() + old = sess._previous + sess._previous = None + session_registry.set(old) + sess.was_popped() + return old + +def using_session(sess, func): + push_session(sess) + try: + return func() + finally: + pop_session() + +def install_plugin(): + objectstore.Session = LegacySession + objectstore.session_registry = util.ScopedRegistry(objectstore.Session) + objectstore.begin = begin + objectstore.commit = commit + objectstore.uow = uow + objectstore.push_session = push_session + objectstore.pop_session = pop_session + objectstore.using_session = using_session +install_plugin() diff --git a/spyce-2.1/sqlalchemy/mods/selectresults.py b/spyce-2.1/sqlalchemy/mods/selectresults.py new file mode 100755 index 0000000000000000000000000000000000000000..51ed6e4a578563bd505f6a0977e50d02b552be89 --- /dev/null +++ b/spyce-2.1/sqlalchemy/mods/selectresults.py @@ -0,0 +1,7 @@ +from sqlalchemy.ext.selectresults import * +from sqlalchemy.orm.mapper import global_extensions + + +def install_plugin(): + global_extensions.append(SelectResultsExt) +install_plugin() diff --git a/spyce-2.1/sqlalchemy/mods/threadlocal.py b/spyce-2.1/sqlalchemy/mods/threadlocal.py new file mode 100755 index 0000000000000000000000000000000000000000..6fce85954331a9a82687b04e5f4040d4cf8fe001 --- /dev/null +++ b/spyce-2.1/sqlalchemy/mods/threadlocal.py @@ -0,0 +1,47 @@ +"""this plugin installs thread-local behavior at the Engine and Session level. + +The default Engine strategy will be "threadlocal", producing TLocalEngine instances for create_engine by default. +With this engine, connect() method will return the same connection on the same thread, if it is already checked out +from the pool. this greatly helps functions that call multiple statements to be able to easily use just one connection +without explicit "close" statements on result handles. + +on the Session side, module-level methods will be installed within the objectstore module, such as flush(), delete(), etc. +which call this method on the thread-local session. + +Note: this mod creates a global, thread-local session context named sqlalchemy.objectstore. All mappers created +while this mod is installed will reference this global context when creating new mapped object instances. +""" + +from sqlalchemy import util, engine, mapper +from sqlalchemy.ext.sessioncontext import SessionContext +import sqlalchemy.ext.assignmapper as assignmapper +from sqlalchemy.orm.mapper import global_extensions +from sqlalchemy.orm.session import Session +import sqlalchemy +import sys, types + + +__all__ = ['Objectstore', 'assign_mapper'] + +class Objectstore(object): + def __init__(self, *args, **kwargs): + self.context = SessionContext(*args, **kwargs) + def __getattr__(self, name): + return getattr(self.context.current, name) + session = property(lambda s:s.context.current) + +def assign_mapper(class_, *args, **kwargs): + assignmapper.assign_mapper(objectstore.context, class_, *args, **kwargs) + +objectstore = Objectstore(Session) +def install_plugin(): + sqlalchemy.objectstore = objectstore + global_extensions.append(objectstore.context.mapper_extension) + engine.default_strategy = 'threadlocal' + sqlalchemy.assign_mapper = assign_mapper + +def uninstall_plugin(): + engine.default_strategy = 'plain' + global_extensions.remove(objectstore.context.mapper_extension) + +install_plugin() diff --git a/spyce-2.1/sqlalchemy/orm/__init__.py b/spyce-2.1/sqlalchemy/orm/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..2ae9bd3aa5ff17117f2261bed7b5e30d0cde48f3 --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/__init__.py @@ -0,0 +1,179 @@ +# mapper/__init__.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +""" +the mapper package provides object-relational functionality, building upon the schema and sql +packages and tying operations to class properties and constructors. +""" +from sqlalchemy import exceptions +from sqlalchemy.orm.mapper import * +from sqlalchemy.orm import mapper as mapperlib +from sqlalchemy.orm.query import Query +from sqlalchemy.orm.util import polymorphic_union +from sqlalchemy.orm import properties, strategies +from sqlalchemy.orm.session import Session as create_session + +__all__ = ['relation', 'backref', 'eagerload', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', 'extension', + 'mapper', 'clear_mappers', 'clear_mapper', 'class_mapper', 'object_mapper', 'MapperExtension', 'Query', + 'cascade_mappers', 'polymorphic_union', 'create_session', 'synonym', 'contains_eager', 'EXT_PASS' + ] + +def relation(*args, **kwargs): + """provide a relationship of a primary Mapper to a secondary Mapper. + + This corresponds to a parent-child or associative table relationship.""" + if len(args) > 1 and isinstance(args[0], type): + raise exceptions.ArgumentError("relation(class, table, **kwargs) is deprecated. Please use relation(class, **kwargs) or relation(mapper, **kwargs).") + return _relation_loader(*args, **kwargs) + +def _relation_loader(mapper, secondary=None, primaryjoin=None, secondaryjoin=None, lazy=True, **kwargs): + return properties.PropertyLoader(mapper, secondary, primaryjoin, secondaryjoin, lazy=lazy, **kwargs) + +def backref(name, **kwargs): + """create a BackRef object with explicit arguments, which are the same arguments one + can send to relation(). + + used with the "backref" keyword argument to relation() in place + of a string argument. """ + return properties.BackRef(name, **kwargs) + +def deferred(*columns, **kwargs): + """return a DeferredColumnProperty, which indicates this object attributes should only be loaded + from its corresponding table column when first accessed. + + used with the 'properties' dictionary sent to mapper().""" + return properties.ColumnProperty(deferred=True, *columns, **kwargs) + +def mapper(class_, table=None, *args, **params): + """return a new Mapper object. + + See the Mapper class for a description of arguments.""" + return Mapper(class_, table, *args, **params) + +def synonym(name, proxy=False): + """set up 'name' as a synonym to another MapperProperty. + + Used with the 'properties' dictionary sent to mapper().""" + return properties.SynonymProperty(name, proxy=proxy) + +def clear_mappers(): + """remove all mappers that have been created thus far. + + when new mappers are created, they will be assigned to their classes as their primary mapper.""" + mapper_registry.clear() + +def clear_mapper(m): + """remove the given mapper from the storage of mappers. + + when a new mapper is created for the previous mapper's class, it will be used as that classes' + new primary mapper.""" + del mapper_registry[m.class_key] + +def extension(ext): + """return a MapperOption that will insert the given MapperExtension to the + beginning of the list of extensions that will be called in the context of the Query. + + used with query.options().""" + return ExtensionOption(ext) + +def eagerload(name): + """return a MapperOption that will convert the property of the given name + into an eager load. + + used with query.options().""" + return strategies.EagerLazyOption(name, lazy=False) + +def lazyload(name): + """return a MapperOption that will convert the property of the given name + into a lazy load. + + used with query.options().""" + return strategies.EagerLazyOption(name, lazy=True) + +def noload(name): + """return a MapperOption that will convert the property of the given name + into a non-load. + + used with query.options().""" + return strategies.EagerLazyOption(name, lazy=None) + +def contains_eager(key, decorator=None): + """return a MapperOption that will indicate to the query that the given + attribute will be eagerly loaded without any row decoration, or using + a custom row decorator. + + used when feeding SQL result sets directly into + query.instances().""" + return strategies.RowDecorateOption(key, decorator=decorator) + +def defer(name): + """return a MapperOption that will convert the column property of the given + name into a deferred load. + + used with query.options()""" + return strategies.DeferredOption(name, defer=True) +def undefer(name): + """return a MapperOption that will convert the column property of the given + name into a non-deferred (regular column) load. + + used with query.options().""" + return strategies.DeferredOption(name, defer=False) + + +def cascade_mappers(*classes_or_mappers): + """attempt to create a series of relations() between mappers automatically, via + introspecting the foreign key relationships of the underlying tables. + + given a list of classes and/or mappers, identifies the foreign key relationships + between the given mappers or corresponding class mappers, and creates relation() + objects representing those relationships, including a backreference. Attempts to find + the "secondary" table in a many-to-many relationship as well. The names of the relations + will be a lowercase version of the related class. In the case of one-to-many or many-to-many, + the name will be "pluralized", which currently is based on the English language (i.e. an 's' or + 'es' added to it). + + NOTE: this method usually works poorly, and its usage is generally not advised. + """ + table_to_mapper = {} + for item in classes_or_mappers: + if isinstance(item, Mapper): + m = item + else: + klass = item + m = class_mapper(klass) + table_to_mapper[m.mapped_table] = m + def pluralize(name): + # oh crap, do we need locale stuff now + if name[-1] == 's': + return name + "es" + else: + return name + "s" + for table,mapper in table_to_mapper.iteritems(): + for fk in table.foreign_keys: + if fk.column.table is table: + continue + secondary = None + try: + m2 = table_to_mapper[fk.column.table] + except KeyError: + if len(fk.column.table.primary_key): + continue + for sfk in fk.column.table.foreign_keys: + if sfk.column.table is table: + continue + m2 = table_to_mapper.get(sfk.column.table) + secondary = fk.column.table + if m2 is None: + continue + if secondary: + propname = pluralize(m2.class_.__name__.lower()) + propname2 = pluralize(mapper.class_.__name__.lower()) + else: + propname = m2.class_.__name__.lower() + propname2 = pluralize(mapper.class_.__name__.lower()) + mapper.add_property(propname, relation(m2, secondary=secondary, backref=propname2)) + + \ No newline at end of file diff --git a/spyce-2.1/sqlalchemy/orm/__init__.pyc b/spyce-2.1/sqlalchemy/orm/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8261793f7638aa4958cd231375787f37a037a463 Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/__init__.pyc differ diff --git a/spyce-2.1/sqlalchemy/orm/attributes.py b/spyce-2.1/sqlalchemy/orm/attributes.py new file mode 100755 index 0000000000000000000000000000000000000000..f7fb98673ebcffbccab87c59a9d21137d7427916 --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/attributes.py @@ -0,0 +1,757 @@ +# attributes.py - manages object attributes +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from sqlalchemy import util +from sqlalchemy.orm import util as orm_util +from sqlalchemy import logging +import weakref + +class InstrumentedAttribute(object): + """a property object that instruments attribute access on object instances. All methods correspond to + a single attribute on a particular class.""" + + PASSIVE_NORESULT = object() + + def __init__(self, manager, key, uselist, callable_, typecallable, trackparent=False, extension=None, copy_function=None, compare_function=None, mutable_scalars=False, **kwargs): + self.manager = manager + self.key = key + self.uselist = uselist + self.callable_ = callable_ + self.typecallable= typecallable + self.trackparent = trackparent + self.mutable_scalars = mutable_scalars + if copy_function is None: + if uselist: + self._copyfunc = lambda x: [y for y in x] + else: + # scalar values are assumed to be immutable unless a copy function + # is passed + self._copyfunc = lambda x: x + else: + self._copyfunc = copy_function + if compare_function is None: + self._compare_function = lambda x,y: x == y + else: + self._compare_function = compare_function + self.extensions = util.to_list(extension or []) + + def __set__(self, obj, value): + self.set(None, obj, value) + def __delete__(self, obj): + self.delete(None, obj) + def __get__(self, obj, owner): + if obj is None: + return self + return self.get(obj) + + def is_equal(self, x, y): + return self._compare_function(x, y) + def copy(self, value): + return self._copyfunc(value) + + def check_mutable_modified(self, obj): + if self.mutable_scalars: + h = self.get_history(obj, passive=True) + if h is not None and h.is_modified(): + obj._state['modified'] = True + return True + else: + return False + else: + return False + + + def hasparent(self, item, optimistic=False): + """return the boolean value of a "hasparent" flag attached to the given item. + + the 'optimistic' flag determines what the default return value should be if + no "hasparent" flag can be located. as this function is used to determine if + an instance is an "orphan", instances that were loaded from storage should be assumed + to not be orphans, until a True/False value for this flag is set. an instance attribute + that is loaded by a callable function will also not have a "hasparent" flag. + """ + return item._state.get(('hasparent', id(self)), optimistic) + + def sethasparent(self, item, value): + """sets a boolean flag on the given item corresponding to whether or not it is + attached to a parent object via the attribute represented by this InstrumentedAttribute.""" + item._state[('hasparent', id(self))] = value + + def get_history(self, obj, passive=False): + """return a new AttributeHistory object for the given object/this attribute's key. + + if passive is True, then dont execute any callables; if the attribute's value + can only be achieved via executing a callable, then return None.""" + # get the current state. this may trigger a lazy load if + # passive is False. + current = self.get(obj, passive=passive, raiseerr=False) + if current is InstrumentedAttribute.PASSIVE_NORESULT: + return None + return AttributeHistory(self, obj, current, passive=passive) + + def set_callable(self, obj, callable_): + """set a callable function for this attribute on the given object. + + this callable will be executed when the attribute is next accessed, + and is assumed to construct part of the instances previously stored state. When + its value or values are loaded, they will be established as part of the + instance's "committed state". while "trackparent" information will be assembled + for these instances, attribute-level event handlers will not be fired. + + the callable overrides the class level callable set in the InstrumentedAttribute + constructor. + """ + if callable_ is None: + self.initialize(obj) + else: + obj._state[('callable', self)] = callable_ + + def reset(self, obj): + """removes any per-instance callable functions corresponding to this InstrumentedAttribute's attribute + from the given object, and removes this InstrumentedAttribute's + attribute from the given object's dictionary.""" + try: + del obj._state[('callable', self)] + except KeyError: + pass + self.clear(obj) + + def clear(self, obj): + """removes this InstrumentedAttribute's attribute from the given object's dictionary. subsequent calls to + getattr(obj, key) will raise an AttributeError by default.""" + try: + del obj.__dict__[self.key] + except KeyError: + pass + + def _get_callable(self, obj): + if obj._state.has_key(('callable', self)): + return obj._state[('callable', self)] + elif self.callable_ is not None: + return self.callable_(obj) + else: + return None + + def _blank_list(self): + if self.typecallable is not None: + return self.typecallable() + else: + return [] + + def _adapt_list(self, data): + if self.typecallable is not None: + t = self.typecallable() + if data is not None: + [t.append(x) for x in data] + return t + else: + return data + + def initialize(self, obj): + """initialize this attribute on the given object instance. + + if this is a list-based attribute, a new, blank list will be created. + if a scalar attribute, the value will be initialized to None.""" + if self.uselist: + l = InstrumentedList(self, obj, self._blank_list()) + obj.__dict__[self.key] = l + return l + else: + obj.__dict__[self.key] = None + return None + + def get(self, obj, passive=False, raiseerr=True): + """retrieves a value from the given object. if a callable is assembled + on this object's attribute, and passive is False, the callable will be executed + and the resulting value will be set as the new value for this attribute.""" + try: + return obj.__dict__[self.key] + except KeyError: + state = obj._state + # if an instance-wide "trigger" was set, call that + # and start again + if state.has_key('trigger'): + trig = state['trigger'] + del state['trigger'] + trig() + return self.get(obj, passive=passive, raiseerr=raiseerr) + + if self.uselist: + callable_ = self._get_callable(obj) + if callable_ is not None: + if passive: + return InstrumentedAttribute.PASSIVE_NORESULT + self.logger.debug("Executing lazy callable on %s.%s" % (orm_util.instance_str(obj), self.key)) + values = callable_() + l = InstrumentedList(self, obj, self._adapt_list(values), init=False) + + # if a callable was executed, then its part of the "committed state" + # if any, so commit the newly loaded data + orig = state.get('original', None) + if orig is not None: + orig.commit_attribute(self, obj, l) + + else: + # note that we arent raising AttributeErrors, just creating a new + # blank list and setting it. + # this might be a good thing to be changeable by options. + l = InstrumentedList(self, obj, self._blank_list(), init=False) + obj.__dict__[self.key] = l + return l + else: + callable_ = self._get_callable(obj) + if callable_ is not None: + if passive: + return InstrumentedAttribute.PASSIVE_NORESULT + self.logger.debug("Executing lazy callable on %s.%s" % (orm_util.instance_str(obj), self.key)) + value = callable_() + obj.__dict__[self.key] = value + + # if a callable was executed, then its part of the "committed state" + # if any, so commit the newly loaded data + orig = state.get('original', None) + if orig is not None: + orig.commit_attribute(self, obj) + return obj.__dict__[self.key] + else: + # note that we arent raising AttributeErrors, just returning None. + # this might be a good thing to be changeable by options. + return None + + def set(self, event, obj, value): + """sets a value on the given object. 'event' is the InstrumentedAttribute that + initiated the set() operation and is used to control the depth of a circular setter + operation.""" + if event is not self: + state = obj._state + # if an instance-wide "trigger" was set, call that + if state.has_key('trigger'): + trig = state['trigger'] + del state['trigger'] + trig() + if self.uselist: + value = InstrumentedList(self, obj, value) + old = self.get(obj) + obj.__dict__[self.key] = value + state['modified'] = True + if not self.uselist: + if self.trackparent: + if value is not None: + self.sethasparent(value, True) + if old is not None: + self.sethasparent(old, False) + for ext in self.extensions: + ext.set(event or self, obj, value, old) + else: + # mark all the old elements as detached from the parent + old.list_replaced() + + def delete(self, event, obj): + """deletes a value from the given object. 'event' is the InstrumentedAttribute that + initiated the delete() operation and is used to control the depth of a circular delete + operation.""" + if event is not self: + try: + if not self.uselist and (self.trackparent or len(self.extensions)): + old = self.get(obj) + del obj.__dict__[self.key] + except KeyError: + # TODO: raise this? not consistent with get() ? + raise AttributeError(self.key) + obj._state['modified'] = True + if not self.uselist: + if self.trackparent: + if old is not None: + self.sethasparent(old, False) + for ext in self.extensions: + ext.delete(event or self, obj, old) + + def append(self, event, obj, value): + """appends an element to a list based element or sets a scalar based element to the given value. + Used by GenericBackrefExtension to "append" an item independent of list/scalar semantics. + 'event' is the InstrumentedAttribute that initiated the append() operation and is used to control + the depth of a circular append operation.""" + if self.uselist: + if event is not self: + self.get(obj).append_with_event(value, event) + else: + self.set(event, obj, value) + + def remove(self, event, obj, value): + """removes an element from a list based element or sets a scalar based element to None. + Used by GenericBackrefExtension to "remove" an item independent of list/scalar semantics. + 'event' is the InstrumentedAttribute that initiated the remove() operation and is used to control + the depth of a circular remove operation.""" + if self.uselist: + if event is not self: + self.get(obj).remove_with_event(value, event) + else: + self.set(event, obj, None) + + def append_event(self, event, obj, value): + """called by InstrumentedList when an item is appended""" + obj._state['modified'] = True + if self.trackparent and value is not None: + self.sethasparent(value, True) + for ext in self.extensions: + ext.append(event or self, obj, value) + + def remove_event(self, event, obj, value): + """called by InstrumentedList when an item is removed""" + obj._state['modified'] = True + if self.trackparent and value is not None: + self.sethasparent(value, False) + for ext in self.extensions: + ext.delete(event or self, obj, value) +InstrumentedAttribute.logger = logging.class_logger(InstrumentedAttribute) + +class InstrumentedList(object): + """instruments a list-based attribute. all mutator operations (i.e. append, remove, etc.) will fire off events to the + InstrumentedAttribute that manages the object's attribute. those events in turn trigger things like + backref operations and whatever is implemented by do_list_value_changed on InstrumentedAttribute. + + note that this list does a lot less than earlier versions of SA list-based attributes, which used HistoryArraySet. + this list wrapper does *not* maintain setlike semantics, meaning you can add as many duplicates as + you want (which can break a lot of SQL), and also does not do anything related to history tracking. + + Please see ticket #213 for information on the future of this class, where it will be broken out into more + collection-specific subtypes.""" + def __init__(self, attr, obj, data, init=True): + self.attr = attr + # this weakref is to prevent circular references between the parent object + # and the list attribute, which interferes with immediate garbage collection. + self.__obj = weakref.ref(obj) + self.key = attr.key + self.data = data or attr._blank_list() + + # adapt to lists or sets + # TODO: make three subclasses of InstrumentedList that come off from a + # metaclass, based on the type of data sent in + if hasattr(self.data, 'append'): + self._data_appender = self.data.append + self._clear_data = self._clear_list + elif hasattr(self.data, 'add'): + self._data_appender = self.data.add + self._clear_data = self._clear_set + if isinstance(self.data, dict): + self._clear_data = self._clear_dict + + if init: + for x in self.data: + self.__setrecord(x) + + def list_replaced(self): + """fires off delete event handlers for each item in the list but + doesnt affect the original data list""" + [self.__delrecord(x) for x in self.data] + + def clear(self): + """clears all items in this InstrumentedList and fires off delete event handlers for each item""" + self._clear_data() + def _clear_dict(self): + [self.__delrecord(x) for x in self.data.values()] + self.data.clear() + def _clear_set(self): + [self.__delrecord(x) for x in self.data] + self.data.clear() + def _clear_list(self): + self[:] = [] + + def __getstate__(self): + """implemented to allow pickling, since __obj is a weakref, also the InstrumentedAttribute has callables + attached to it""" + return {'key':self.key, 'obj':self.obj, 'data':self.data} + def __setstate__(self, d): + """implemented to allow pickling, since __obj is a weakref, also the InstrumentedAttribute has callables + attached to it""" + self.key = d['key'] + self.__obj = weakref.ref(d['obj']) + self.data = d['data'] + self.attr = getattr(d['obj'].__class__, self.key) + + obj = property(lambda s:s.__obj()) + + def unchanged_items(self): + """deprecated""" + return self.attr.get_history(self.obj).unchanged_items + def added_items(self): + """deprecated""" + return self.attr.get_history(self.obj).added_items + def deleted_items(self): + """deprecated""" + return self.attr.get_history(self.obj).deleted_items + + def __iter__(self): + return iter(self.data) + def __repr__(self): + return repr(self.data) + + def __getattr__(self, attr): + """proxies unknown methods and attributes to the underlying + data array. this allows custom list classes to be used.""" + return getattr(self.data, attr) + + def __setrecord(self, item, event=None): + self.attr.append_event(event, self.obj, item) + return True + + def __delrecord(self, item, event=None): + self.attr.remove_event(event, self.obj, item) + return True + + def append_with_event(self, item, event): + self.__setrecord(item, event) + self._data_appender(item) + + def append_without_event(self, item): + self._data_appender(item) + + def remove_with_event(self, item, event): + self.__delrecord(item, event) + self.data.remove(item) + + def append(self, item, _mapper_nohistory=False): + """fires off dependent events, and appends the given item to the underlying list. + _mapper_nohistory is a backwards compatibility hack; call append_without_event instead.""" + if _mapper_nohistory: + self.append_without_event(item) + else: + self.__setrecord(item) + self._data_appender(item) + + + def __getitem__(self, i): + return self.data[i] + def __setitem__(self, i, item): + if isinstance(i, slice): + self.__setslice__(i.start, i.stop, item) + else: + self.__setrecord(item) + self.data[i] = item + def __delitem__(self, i): + if isinstance(i, slice): + self.__delslice__(i.start, i.stop) + else: + self.__delrecord(self.data[i], None) + del self.data[i] + + def __lt__(self, other): return self.data < self.__cast(other) + def __le__(self, other): return self.data <= self.__cast(other) + def __eq__(self, other): return self.data == self.__cast(other) + def __ne__(self, other): return self.data != self.__cast(other) + def __gt__(self, other): return self.data > self.__cast(other) + def __ge__(self, other): return self.data >= self.__cast(other) + def __cast(self, other): + if isinstance(other, InstrumentedList): return other.data + else: return other + def __cmp__(self, other): + return cmp(self.data, self.__cast(other)) + def __contains__(self, item): return item in self.data + def __len__(self): return len(self.data) + def __setslice__(self, i, j, other): + i = max(i, 0); j = max(j, 0) + [self.__delrecord(x) for x in self.data[i:]] + g = [a for a in list(other) if self.__setrecord(a)] + self.data[i:] = g + def __delslice__(self, i, j): + i = max(i, 0); j = max(j, 0) + for a in self.data[i:j]: + self.__delrecord(a) + del self.data[i:j] + def insert(self, i, item): + if self.__setrecord(item): + self.data.insert(i, item) + def pop(self, i=-1): + item = self.data[i] + self.__delrecord(item) + return self.data.pop(i) + def remove(self, item): + self.__delrecord(item) + self.data.remove(item) + def extend(self, item_list): + for item in item_list: + self.append(item) + def __add__(self, other): + raise NotImplementedError() + def __radd__(self, other): + raise NotImplementedError() + def __iadd__(self, other): + raise NotImplementedError() + +class AttributeExtension(object): + """an abstract class which specifies "append", "delete", and "set" + event handlers to be attached to an object property.""" + def append(self, event, obj, child): + pass + def delete(self, event, obj, child): + pass + def set(self, event, obj, child, oldchild): + pass + +class GenericBackrefExtension(AttributeExtension): + """an extension which synchronizes a two-way relationship. A typical two-way + relationship is a parent object containing a list of child objects, where each + child object references the parent. The other are two objects which contain + scalar references to each other.""" + def __init__(self, key): + self.key = key + def set(self, event, obj, child, oldchild): + if oldchild is child: + return + if oldchild is not None: + getattr(oldchild.__class__, self.key).remove(event, oldchild, obj) + if child is not None: + getattr(child.__class__, self.key).append(event, child, obj) + def append(self, event, obj, child): + getattr(child.__class__, self.key).append(event, child, obj) + def delete(self, event, obj, child): + getattr(child.__class__, self.key).remove(event, child, obj) + +class CommittedState(object): + """stores the original state of an object when the commit() method on the attribute manager + is called.""" + NO_VALUE = object() + + def __init__(self, manager, obj): + self.data = {} + for attr in manager.managed_attributes(obj.__class__): + self.commit_attribute(attr, obj) + + def commit_attribute(self, attr, obj, value=NO_VALUE): + """establish the value of attribute 'attr' on instance 'obj' as "committed". + + this corresponds to a previously saved state being restored. """ + if value is CommittedState.NO_VALUE: + if obj.__dict__.has_key(attr.key): + value = obj.__dict__[attr.key] + if value is not CommittedState.NO_VALUE: + self.data[attr.key] = attr.copy(value) + + # not tracking parent on lazy-loaded instances at the moment. + # its not needed since they will be "optimistically" tested + #if attr.uselist: + #if attr.trackparent: + # [attr.sethasparent(x, True) for x in self.data[attr.key] if x is not None] + #else: + #if attr.trackparent and value is not None: + # attr.sethasparent(value, True) + + def rollback(self, manager, obj): + for attr in manager.managed_attributes(obj.__class__): + if self.data.has_key(attr.key): + if attr.uselist: + obj.__dict__[attr.key][:] = self.data[attr.key] + else: + obj.__dict__[attr.key] = self.data[attr.key] + else: + del obj.__dict__[attr.key] + + def __repr__(self): + return "CommittedState: %s" % repr(self.data) + +class AttributeHistory(object): + """calculates the "history" of a particular attribute on a particular instance, based on the CommittedState + associated with the instance, if any.""" + def __init__(self, attr, obj, current, passive=False): + self.attr = attr + + # get the "original" value. if a lazy load was fired when we got + # the 'current' value, this "original" was also populated just + # now as well (therefore we have to get it second) + orig = obj._state.get('original', None) + if orig is not None: + original = orig.data.get(attr.key) + else: + original = None + + if attr.uselist: + self._current = current + else: + self._current = [current] + if attr.uselist: + s = util.Set(original or []) + self._added_items = [] + self._unchanged_items = [] + self._deleted_items = [] + if current: + for a in current: + if a in s: + self._unchanged_items.append(a) + else: + self._added_items.append(a) + for a in s: + if a not in self._unchanged_items: + self._deleted_items.append(a) + else: + if attr.is_equal(current, original): + self._unchanged_items = [current] + self._added_items = [] + self._deleted_items = [] + else: + self._added_items = [current] + if original is not None: + self._deleted_items = [original] + else: + self._deleted_items = [] + self._unchanged_items = [] + #print "key", attr.key, "orig", original, "current", current, "added", self._added_items, "unchanged", self._unchanged_items, "deleted", self._deleted_items + def __iter__(self): + return iter(self._current) + def is_modified(self): + return len(self._deleted_items) > 0 or len(self._added_items) > 0 + def added_items(self): + return self._added_items + def unchanged_items(self): + return self._unchanged_items + def deleted_items(self): + return self._deleted_items + def hasparent(self, obj): + """deprecated. this should be called directly from the appropriate InstrumentedAttribute object.""" + return self.attr.hasparent(obj) + +class AttributeManager(object): + """allows the instrumentation of object attributes. AttributeManager is stateless, but can be + overridden by subclasses to redefine some of its factory operations.""" + + def rollback(self, *obj): + """retrieves the committed history for each object in the given list, and rolls back the attributes + each instance to their original value.""" + for o in obj: + orig = o._state.get('original') + if orig is not None: + orig.rollback(self, o) + else: + self._clear(o) + + def _clear(self, obj): + for attr in self.managed_attributes(obj.__class__): + try: + del obj.__dict__[attr.key] + except KeyError: + pass + + def commit(self, *obj): + """creates a CommittedState instance for each object in the given list, representing + its "unchanged" state, and associates it with the instance. AttributeHistory objects + will indicate the modified state of instance attributes as compared to its value in this + CommittedState object.""" + for o in obj: + o._state['original'] = CommittedState(self, o) + o._state['modified'] = False + + def managed_attributes(self, class_): + """returns an iterator of all InstrumentedAttribute objects associated with the given class.""" + if not isinstance(class_, type): + raise repr(class_) + " is not a type" + for key in dir(class_): + value = getattr(class_, key, None) + if isinstance(value, InstrumentedAttribute): + yield value + + def noninherited_managed_attributes(self, class_): + if not isinstance(class_, type): + raise repr(class_) + " is not a type" + for key in list(class_.__dict__): + value = getattr(class_, key, None) + if isinstance(value, InstrumentedAttribute): + yield value + + def is_modified(self, object): + for attr in self.managed_attributes(object.__class__): + if attr.check_mutable_modified(object): + return True + return object._state.get('modified', False) + + def init_attr(self, obj): + """sets up the __sa_attr_state dictionary on the given instance. This dictionary is + automatically created when the '_state' attribute of the class is first accessed, but calling + it here will save a single throw of an AttributeError that occurs in that creation step.""" + setattr(obj, '_%s__sa_attr_state' % obj.__class__.__name__, {}) + + def get_history(self, obj, key, **kwargs): + """returns a new AttributeHistory object for the given attribute on the given object.""" + return getattr(obj.__class__, key).get_history(obj, **kwargs) + + def get_as_list(self, obj, key, passive=False): + """returns an attribute of the given name from the given object. if the attribute + is a scalar, returns it as a single-item list, otherwise returns the list based attribute. + if the attribute's value is to be produced by an unexecuted callable, + the callable will only be executed if the given 'passive' flag is False. + """ + attr = getattr(obj.__class__, key) + x = attr.get(obj, passive=passive) + if x is InstrumentedAttribute.PASSIVE_NORESULT: + return [] + elif attr.uselist: + return x + else: + return [x] + + def trigger_history(self, obj, callable): + """clears all managed object attributes and places the given callable + as an attribute-wide "trigger", which will execute upon the next attribute access, after + which the trigger is removed.""" + self._clear(obj) + try: + del obj._state['original'] + except KeyError: + pass + obj._state['trigger'] = callable + + def untrigger_history(self, obj): + """removes a trigger function set by trigger_history. does not restore the previous state of the object.""" + del obj._state['trigger'] + + def has_trigger(self, obj): + """returns True if the given object has a trigger function set by trigger_history().""" + return obj._state.has_key('trigger') + + def reset_instance_attribute(self, obj, key): + """removes any per-instance callable functions corresponding to given attribute key + from the given object, and removes this attribute from the given object's dictionary.""" + attr = getattr(obj.__class__, key) + attr.reset(obj) + + def reset_class_managed(self, class_): + """removes all InstrumentedAttribute property objects from the given class.""" + for attr in self.noninherited_managed_attributes(class_): + delattr(class_, attr.key) + + def is_class_managed(self, class_, key): + """returns True if the given key correponds to an instrumented property on the given class.""" + return hasattr(class_, key) and isinstance(getattr(class_, key), InstrumentedAttribute) + + def init_instance_attribute(self, obj, key, uselist, callable_=None, **kwargs): + """initializes an attribute on an instance to either a blank value, cancelling + out any class- or instance-level callables that were present, or if a callable + is supplied sets the callable to be invoked when the attribute is next accessed.""" + getattr(obj.__class__, key).set_callable(obj, callable_) + + def create_prop(self, class_, key, uselist, callable_, typecallable, **kwargs): + """creates a scalar property object, defaulting to InstrumentedAttribute, which + will communicate change events back to this AttributeManager.""" + return InstrumentedAttribute(self, key, uselist, callable_, typecallable, **kwargs) + + def register_attribute(self, class_, key, uselist, callable_=None, **kwargs): + """registers an attribute at the class level to be instrumented for all instances + of the class.""" + #print self, "register attribute", key, "for class", class_ + if not hasattr(class_, '_state'): + def _get_state(self): + try: + return self.__sa_attr_state + except AttributeError: + self.__sa_attr_state = {} + return self.__sa_attr_state + class_._state = property(_get_state) + + typecallable = kwargs.pop('typecallable', None) + if typecallable is None: + typecallable = getattr(class_, key, None) + if isinstance(typecallable, InstrumentedAttribute): + typecallable = None + setattr(class_, key, self.create_prop(class_, key, uselist, callable_, typecallable=typecallable, **kwargs)) + diff --git a/spyce-2.1/sqlalchemy/orm/attributes.pyc b/spyce-2.1/sqlalchemy/orm/attributes.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81c2926e95cee1779286870fb18f435ec9bc64cb Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/attributes.pyc differ diff --git a/spyce-2.1/sqlalchemy/orm/dependency.py b/spyce-2.1/sqlalchemy/orm/dependency.py new file mode 100755 index 0000000000000000000000000000000000000000..133d51b7fc3c6844d00c52cd3522ea5a4dbad90a --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/dependency.py @@ -0,0 +1,348 @@ +# orm/dependency.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +"""bridges the PropertyLoader (i.e. a relation()) and the UOWTransaction +together to allow processing of scalar- and list-based dependencies at flush time.""" + +from sqlalchemy.orm import sync +from sqlalchemy.orm.sync import ONETOMANY,MANYTOONE,MANYTOMANY +from sqlalchemy import sql, util +from sqlalchemy.orm import session as sessionlib + +def create_dependency_processor(prop): + types = { + ONETOMANY : OneToManyDP, + MANYTOONE: ManyToOneDP, + MANYTOMANY : ManyToManyDP, + } + if prop.association is not None: + return AssociationDP(prop) + else: + return types[prop.direction](prop) + +class DependencyProcessor(object): + def __init__(self, prop): + self.prop = prop + self.cascade = prop.cascade + self.mapper = prop.mapper + self.parent = prop.parent + self.association = prop.association + self.secondary = prop.secondary + self.direction = prop.direction + self.is_backref = prop.is_backref + self.post_update = prop.post_update + self.foreignkey = prop.foreignkey + self.passive_deletes = prop.passive_deletes + self.key = prop.key + + self._compile_synchronizers() + + def register_dependencies(self, uowcommit): + """tells a UOWTransaction what mappers are dependent on which, with regards + to the two or three mappers handled by this PropertyLoader. + + Also registers itself as a "processor" for one of its mappers, which + will be executed after that mapper's objects have been saved or before + they've been deleted. The process operation manages attributes and dependent + operations upon the objects of one of the involved mappers.""" + raise NotImplementedError() + + def whose_dependent_on_who(self, obj1, obj2): + """given an object pair assuming obj2 is a child of obj1, returns a tuple + with the dependent object second, or None if they are equal. + used by objectstore's object-level topological sort (i.e. cyclical + table dependency).""" + if obj1 is obj2: + return None + elif self.direction == ONETOMANY: + return (obj1, obj2) + else: + return (obj2, obj1) + + def process_dependencies(self, task, deplist, uowcommit, delete = False): + """this method is called during a flush operation to synchronize data between a parent and child object. + it is called within the context of the various mappers and sometimes individual objects sorted according to their + insert/update/delete order (topological sort).""" + raise NotImplementedError() + + def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): + """used before the flushes' topological sort to traverse through related objects and insure every + instance which will require save/update/delete is properly added to the UOWTransaction.""" + raise NotImplementedError() + + def _synchronize(self, obj, child, associationrow, clearkeys): + """called during a flush to synchronize primary key identifier values between a parent/child object, as well as + to an associationrow in the case of many-to-many.""" + raise NotImplementedError() + + def _compile_synchronizers(self): + """assembles a list of 'synchronization rules', which are instructions on how to populate + the objects on each side of a relationship. This is done when a DependencyProcessor is + first initialized. + + The list of rules is used within commits by the _synchronize() method when dependent + objects are processed.""" + self.syncrules = sync.ClauseSynchronizer(self.parent, self.mapper, self.direction) + if self.direction == sync.MANYTOMANY: + self.syncrules.compile(self.prop.primaryjoin, issecondary=False) + self.syncrules.compile(self.prop.secondaryjoin, issecondary=True) + else: + self.syncrules.compile(self.prop.primaryjoin, foreignkey=self.foreignkey) + + def get_object_dependencies(self, obj, uowcommit, passive = True): + """returns the list of objects that are dependent on the given object, as according to the relationship + this dependency processor represents""" + return sessionlib.attribute_manager.get_history(obj, self.key, passive = passive) + + def _conditional_post_update(self, obj, uowcommit, related): + """execute a post_update call. + + for relations that contain the post_update flag, an additional UPDATE statement may be + associated after an INSERT or before a DELETE in order to resolve circular row dependencies. + This method will check for the post_update flag being set on a particular relationship, and + given a target object and list of one or more related objects, and execute the UPDATE if the + given related object list contains INSERTs or DELETEs.""" + if obj is not None and self.post_update: + for x in related: + if x is not None and (uowcommit.is_deleted(x) or not hasattr(x, '_instance_key')): + uowcommit.register_object(obj, postupdate=True, post_update_cols=self.syncrules.dest_columns()) + break + +class OneToManyDP(DependencyProcessor): + def register_dependencies(self, uowcommit): + if self.post_update: + stub = MapperStub(self.parent, self.mapper, self.key) + uowcommit.register_dependency(self.mapper, stub) + uowcommit.register_dependency(self.parent, stub) + uowcommit.register_processor(stub, self, self.parent) + else: + uowcommit.register_dependency(self.parent, self.mapper) + uowcommit.register_processor(self.parent, self, self.parent) + def process_dependencies(self, task, deplist, uowcommit, delete = False): + #print self.mapper.mapped_table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) + if delete: + # head object is being deleted, and we manage its list of child objects + # the child objects have to have their foreign key to the parent set to NULL + if not self.cascade.delete_orphan or self.post_update: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=False) + for child in childlist.deleted_items(): + if child is not None and childlist.hasparent(child) is False: + self._synchronize(obj, child, None, True) + self._conditional_post_update(child, uowcommit, [obj]) + for child in childlist.unchanged_items(): + if child is not None: + self._synchronize(obj, child, None, True) + self._conditional_post_update(child, uowcommit, [obj]) + else: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=True) + if childlist is not None: + for child in childlist.added_items(): + self._synchronize(obj, child, None, False) + self._conditional_post_update(child, uowcommit, [obj]) + for child in childlist.deleted_items(): + if not self.cascade.delete_orphan: + self._synchronize(obj, child, None, True) + + def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): + #print self.mapper.mapped_table.name + " " + self.key + " " + repr(len(deplist)) + " preprocess_dep isdelete " + repr(delete) + " direction " + repr(self.direction) + + if delete: + # head object is being deleted, and we manage its list of child objects + # the child objects have to have their foreign key to the parent set to NULL + if self.post_update: + pass + elif self.cascade.delete_orphan: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=False) + for child in childlist.deleted_items(): + if child is not None and childlist.hasparent(child) is False: + uowcommit.register_object(child, isdelete=True) + for c in self.mapper.cascade_iterator('delete', child): + uowcommit.register_object(c, isdelete=True) + for child in childlist.unchanged_items(): + if child is not None: + uowcommit.register_object(child, isdelete=True) + for c in self.mapper.cascade_iterator('delete', child): + uowcommit.register_object(c, isdelete=True) + else: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=False) + for child in childlist.deleted_items(): + if child is not None and childlist.hasparent(child) is False: + uowcommit.register_object(child) + for child in childlist.unchanged_items(): + if child is not None: + uowcommit.register_object(child) + else: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=True) + if childlist is not None: + for child in childlist.added_items(): + if child is not None: + uowcommit.register_object(child) + for child in childlist.deleted_items(): + if not self.cascade.delete_orphan: + uowcommit.register_object(child, isdelete=False) + elif childlist.hasparent(child) is False: + uowcommit.register_object(child, isdelete=True) + for c in self.mapper.cascade_iterator('delete', child): + uowcommit.register_object(c, isdelete=True) + + def _synchronize(self, obj, child, associationrow, clearkeys): + source = obj + dest = child + if dest is None: + return + self.syncrules.execute(source, dest, obj, child, clearkeys) + +class ManyToOneDP(DependencyProcessor): + def register_dependencies(self, uowcommit): + if self.post_update: + stub = MapperStub(self.parent, self.mapper, self.key) + uowcommit.register_dependency(self.mapper, stub) + uowcommit.register_dependency(self.parent, stub) + uowcommit.register_processor(stub, self, self.parent) + else: + uowcommit.register_dependency(self.mapper, self.parent) + uowcommit.register_processor(self.mapper, self, self.parent) + def process_dependencies(self, task, deplist, uowcommit, delete = False): + #print self.mapper.mapped_table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) + if delete: + if self.post_update and not self.cascade.delete_orphan: + # post_update means we have to update our row to not reference the child object + # before we can DELETE the row + for obj in deplist: + self._synchronize(obj, None, None, True) + childlist = self.get_object_dependencies(obj, uowcommit, passive=False) + self._conditional_post_update(obj, uowcommit, childlist.deleted_items() + childlist.unchanged_items() + childlist.added_items()) + else: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=True) + if childlist is not None: + for child in childlist.added_items(): + self._synchronize(obj, child, None, False) + self._conditional_post_update(obj, uowcommit, childlist.deleted_items() + childlist.unchanged_items() + childlist.added_items()) + + def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): + #print self.mapper.mapped_table.name + " " + self.key + " " + repr(len(deplist)) + " PRE process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) + if self.post_update: + return + if delete: + if self.cascade.delete: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=False) + for child in childlist.deleted_items() + childlist.unchanged_items(): + if child is not None and childlist.hasparent(child) is False: + uowcommit.register_object(child, isdelete=True) + for c in self.mapper.cascade_iterator('delete', child): + uowcommit.register_object(c, isdelete=True) + else: + for obj in deplist: + uowcommit.register_object(obj) + if self.cascade.delete_orphan: + childlist = self.get_object_dependencies(obj, uowcommit, passive=False) + for child in childlist.deleted_items(): + if childlist.hasparent(child) is False: + uowcommit.register_object(child, isdelete=True) + for c in self.mapper.cascade_iterator('delete', child): + uowcommit.register_object(c, isdelete=True) + + def _synchronize(self, obj, child, associationrow, clearkeys): + source = child + dest = obj + if dest is None: + return + self.syncrules.execute(source, dest, obj, child, clearkeys) + +class ManyToManyDP(DependencyProcessor): + def register_dependencies(self, uowcommit): + # many-to-many. create a "Stub" mapper to represent the + # "middle table" in the relationship. This stub mapper doesnt save + # or delete any objects, but just marks a dependency on the two + # related mappers. its dependency processor then populates the + # association table. + + if self.is_backref: + # if we are the "backref" half of a two-way backref + # relationship, let the other mapper handle inserting the rows + return + stub = MapperStub(self.parent, self.mapper, self.key) + uowcommit.register_dependency(self.parent, stub) + uowcommit.register_dependency(self.mapper, stub) + uowcommit.register_processor(stub, self, self.parent) + + def process_dependencies(self, task, deplist, uowcommit, delete = False): + #print self.mapper.table.name + " " + self.key + " " + repr(len(deplist)) + " process_dep isdelete " + repr(delete) + " direction " + repr(self.direction) + connection = uowcommit.transaction.connection(self.mapper) + secondary_delete = [] + secondary_insert = [] + if delete: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit, passive=False) + for child in childlist.deleted_items() + childlist.unchanged_items(): + associationrow = {} + self._synchronize(obj, child, associationrow, False) + secondary_delete.append(associationrow) + else: + for obj in deplist: + childlist = self.get_object_dependencies(obj, uowcommit) + if childlist is None: continue + for child in childlist.added_items(): + associationrow = {} + self._synchronize(obj, child, associationrow, False) + secondary_insert.append(associationrow) + for child in childlist.deleted_items(): + associationrow = {} + self._synchronize(obj, child, associationrow, False) + secondary_delete.append(associationrow) + if len(secondary_delete): + secondary_delete.sort() + # TODO: precompile the delete/insert queries? + statement = self.secondary.delete(sql.and_(*[c == sql.bindparam(c.key) for c in self.secondary.c if c.key in associationrow])) + connection.execute(statement, secondary_delete) + if len(secondary_insert): + statement = self.secondary.insert() + connection.execute(statement, secondary_insert) + + def preprocess_dependencies(self, task, deplist, uowcommit, delete = False): + pass + def _synchronize(self, obj, child, associationrow, clearkeys): + dest = associationrow + source = None + if dest is None: + return + self.syncrules.execute(source, dest, obj, child, clearkeys) + +class AssociationDP(OneToManyDP): + def __init__(self, *args, **kwargs): + super(AssociationDP, self).__init__(*args, **kwargs) + self.cascade.delete = True + self.cascade.delete_orphan = True + +class MapperStub(object): + """poses as a Mapper representing the association table in a many-to-many + join, when performing a flush(). + + The Task objects in the objectstore module treat it just like + any other Mapper, but in fact it only serves as a "dependency" placeholder + for the many-to-many update task.""" + __metaclass__ = util.ArgSingleton + def __init__(self, parent, mapper, key): + self.mapper = mapper + self._inheriting_mappers = [] + def register_dependencies(self, uowcommit): + pass + def save_obj(self, *args, **kwargs): + pass + def delete_obj(self, *args, **kwargs): + pass + def primary_mapper(self): + return self + def base_mapper(self): + return self \ No newline at end of file diff --git a/spyce-2.1/sqlalchemy/orm/dependency.pyc b/spyce-2.1/sqlalchemy/orm/dependency.pyc new file mode 100644 index 0000000000000000000000000000000000000000..224f476f2b12d99885b0d388abfc2d37f2f4e976 Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/dependency.pyc differ diff --git a/spyce-2.1/sqlalchemy/orm/interfaces.py b/spyce-2.1/sqlalchemy/orm/interfaces.py new file mode 100755 index 0000000000000000000000000000000000000000..d6c4204f95bc91bce19ce8903c07a37db479b729 --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/interfaces.py @@ -0,0 +1,174 @@ +# interfaces.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +from sqlalchemy import util, logging + +class MapperProperty(object): + """manages the relationship of a Mapper to a single class attribute, as well + as that attribute as it appears on individual instances of the class, including + attribute instrumentation, attribute access, loading behavior, and dependency calculations.""" + def setup(self, querycontext, **kwargs): + """called when a statement is being constructed. """ + pass + def execute(self, selectcontext, instance, row, identitykey, isnew): + """called when the mapper receives a row. instance is the parent instance + corresponding to the row. """ + raise NotImplementedError() + def cascade_iterator(self, type, object, recursive=None): + return [] + def cascade_callable(self, type, object, callable_, recursive=None): + return [] + def get_criterion(self, query, key, value): + """Returns a WHERE clause suitable for this MapperProperty corresponding to the + given key/value pair, where the key is a column or object property name, and value + is a value to be matched. This is only picked up by PropertyLoaders. + + this is called by a Query's join_by method to formulate a set of key/value pairs into + a WHERE criterion that spans multiple tables if needed.""" + return None + def set_parent(self, parent): + self.parent = parent + def init(self, key, parent): + """called after all mappers are compiled to assemble relationships between + mappers, establish instrumented class attributes""" + self.key = key + self.do_init() + def adapt_to_inherited(self, key, newparent): + """adapt this MapperProperty to a new parent, assuming the new parent is an inheriting + descendant of the old parent. """ + newparent._compile_property(key, self, init=False, setparent=False) + def do_init(self): + """template method for subclasses""" + pass + def register_dependencies(self, *args, **kwargs): + """called by the Mapper in response to the UnitOfWork calling the Mapper's + register_dependencies operation. Should register with the UnitOfWork all + inter-mapper dependencies as well as dependency processors (see UOW docs for more details)""" + pass + def is_primary(self): + """return True if this MapperProperty's mapper is the primary mapper for its class. + + This flag is used to indicate that the MapperProperty can define attribute instrumentation + for the class at the class level (as opposed to the individual instance level.)""" + return self.parent._is_primary_mapper() + +class StrategizedProperty(MapperProperty): + """a MapperProperty which uses selectable strategies to affect loading behavior. + There is a single default strategy selected, and alternate strategies can be selected + at selection time through the usage of StrategizedOption objects.""" + def _get_context_strategy(self, context): + return self._get_strategy(context.attributes.get((LoaderStrategy, self), self.strategy.__class__)) + def _get_strategy(self, cls): + try: + return self._all_strategies[cls] + except KeyError: + strategy = cls(self) + strategy.init() + strategy.is_default = False + self._all_strategies[cls] = strategy + return strategy + def setup(self, querycontext, **kwargs): + self._get_context_strategy(querycontext).setup_query(querycontext, **kwargs) + def execute(self, selectcontext, instance, row, identitykey, isnew): + self._get_context_strategy(selectcontext).process_row(selectcontext, instance, row, identitykey, isnew) + def do_init(self): + self._all_strategies = {} + self.strategy = self.create_strategy() + self._all_strategies[self.strategy.__class__] = self.strategy + self.strategy.init() + if self.is_primary(): + self.strategy.init_class_attribute() + +class OperationContext(object): + """serves as a context during a query construction or instance loading operation. + accepts MapperOption objects which may modify its state before proceeding.""" + def __init__(self, mapper, options): + self.mapper = mapper + self.options = options + self.attributes = {} + self.recursion_stack = util.Set() + for opt in options: + self.accept_option(opt) + def accept_option(self, opt): + pass + +class MapperOption(object): + """describes a modification to an OperationContext.""" + def process_query_context(self, context): + pass + def process_selection_context(self, context): + pass + def process_query(self, query): + pass + +class PropertyOption(MapperOption): + """a MapperOption that is applied to a property off the mapper + or one of its child mappers, identified by a dot-separated key.""" + def __init__(self, key): + self.key = key + def process_query_property(self, context, property): + pass + def process_selection_property(self, context, property): + pass + def process_query_context(self, context): + self.process_query_property(context, self._get_property(context)) + def process_selection_context(self, context): + self.process_selection_property(context, self._get_property(context)) + def _get_property(self, context): + try: + prop = self.__prop + except AttributeError: + mapper = context.mapper + for token in self.key.split('.'): + prop = mapper.props[token] + mapper = getattr(prop, 'mapper', None) + self.__prop = prop + return prop +PropertyOption.logger = logging.class_logger(PropertyOption) + +class StrategizedOption(PropertyOption): + """a MapperOption that affects which LoaderStrategy will be used for an operation + by a StrategizedProperty.""" + def process_query_property(self, context, property): + self.logger.debug("applying option to QueryContext, property key '%s'" % self.key) + context.attributes[(LoaderStrategy, property)] = self.get_strategy_class() + def process_selection_property(self, context, property): + self.logger.debug("applying option to SelectionContext, property key '%s'" % self.key) + context.attributes[(LoaderStrategy, property)] = self.get_strategy_class() + def get_strategy_class(self): + raise NotImplementedError() + + +class LoaderStrategy(object): + """describes the loading behavior of a StrategizedProperty object. The LoaderStrategy + interacts with the querying process in three ways: + + * it controls the configuration of the InstrumentedAttribute placed on a class to + handle the behavior of the attribute. this may involve setting up class-level callable + functions to fire off a select operation when the attribute is first accessed (i.e. a lazy load) + + * it processes the QueryContext at statement construction time, where it can modify the SQL statement + that is being produced. simple column attributes may add their represented column to the list of + selected columns, "eager loading" properties may add LEFT OUTER JOIN clauses to the statement. + + * it processes the SelectionContext at row-processing time. This may involve setting instance-level + lazyloader functions on newly constructed instances, or may involve recursively appending child items + to a list in response to additionally eager-loaded objects in the query. + """ + def __init__(self, parent): + self.parent_property = parent + self.is_default = True + def init(self): + self.parent = self.parent_property.parent + self.key = self.parent_property.key + def init_class_attribute(self): + pass + def setup_query(self, context, **kwargs): + pass + def process_row(self, selectcontext, instance, row, identitykey, isnew): + pass + diff --git a/spyce-2.1/sqlalchemy/orm/interfaces.pyc b/spyce-2.1/sqlalchemy/orm/interfaces.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9da0158e0420ac972cf55d1e25851f7dc717950a Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/interfaces.pyc differ diff --git a/spyce-2.1/sqlalchemy/orm/mapper.py b/spyce-2.1/sqlalchemy/orm/mapper.py new file mode 100755 index 0000000000000000000000000000000000000000..e07f691f41d1e83ebc1cd392055b3985653be629 --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/mapper.py @@ -0,0 +1,1419 @@ +# orm/mapper.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from sqlalchemy import sql, schema, util, exceptions, logging +from sqlalchemy import sql_util as sqlutil +from sqlalchemy.orm import util as mapperutil +from sqlalchemy.orm import sync +from sqlalchemy.orm.interfaces import MapperProperty, MapperOption, OperationContext +import weakref + +__all__ = ['Mapper', 'MapperExtension', 'class_mapper', 'object_mapper', 'EXT_PASS', 'mapper_registry', 'ExtensionOption'] + +# a dictionary mapping classes to their primary mappers +mapper_registry = weakref.WeakKeyDictionary() + +# a list of MapperExtensions that will be installed in all mappers by default +global_extensions = [] + +# a constant returned by get_attr_by_column to indicate +# this mapper is not handling an attribute for a particular +# column +NO_ATTRIBUTE = object() + +# returned by a MapperExtension method to indicate a "do nothing" response +EXT_PASS = object() + +class Mapper(object): + """Defines the correlation of class attributes to database table columns. + + Instances of this class should be constructed via the sqlalchemy.orm.mapper() function.""" + def __init__(self, + class_, + local_table, + properties = None, + primary_key = None, + non_primary = False, + inherits = None, + inherit_condition = None, + extension = None, + order_by = False, + allow_column_override = False, + entity_name = None, + always_refresh = False, + version_id_col = None, + polymorphic_on=None, + _polymorphic_map=None, + polymorphic_identity=None, + concrete=False, + select_table=None, + allow_null_pks=False, + batch=True, + column_prefix=None): + """construct a new mapper. + + All arguments may be sent to the sqlalchemy.orm.mapper() function where they are + passed through to here. + + class_ - the class to be mapped. + + local_table - the table to which the class is mapped, or None if this mapper inherits + from another mapper using concrete table inheritance. + + properties - a dictionary mapping the string names of object attributes to MapperProperty + instances, which define the persistence behavior of that attribute. Note that the columns in the + mapped table are automatically converted into ColumnProperty instances based on the "key" + property of each Column (although they can be overridden using this dictionary). + + primary_key - a list of Column objects which define the "primary key" to be used against this mapper's + selectable unit. This is normally simply the primary key of the "local_table", but can be overridden here. + + non_primary - construct a Mapper that will define only the selection of instances, not their persistence. + + inherits - another Mapper for which this Mapper will have an inheritance relationship with. + + inherit_condition - for joined table inheritance, a SQL expression (constructed ClauseElement) which + will define how the two tables are joined; defaults to a natural join between the two tables. + + extension - a MapperExtension instance or list of MapperExtension instances which will be applied to + all operations by this Mapper. + + order_by - a single Column or list of Columns for which selection operations should use as the default + ordering for entities. Defaults to the OID/ROWID of the table if any, or the first primary key column of the table. + + allow_column_override - if True, allows association relationships to be set up which override the usage of + a column that is on the table (based on key/attribute name). + + entity_name - a name to be associated with the class, to allow alternate mappings for a single class. + + always_refresh - if True, all query operations for this mapped class will overwrite all data + within object instances that already exist within the session, erasing any in-memory changes with whatever + information was loaded from the database. + + version_id_col - a Column which must have an integer type that will be used to keep a running "version id" of + mapped entities in the database. this is used during save operations to insure that no other thread or process + has updated the instance during the lifetime of the entity, else a ConcurrentModificationError exception is thrown. + + polymorphic_on - used with mappers in an inheritance relationship, a Column which will identify the class/mapper + combination to be used with a particular row. requires the polymorphic_identity value to be set for all mappers + in the inheritance hierarchy. + + _polymorphic_map - used internally to propigate the full map of polymorphic identifiers to surrogate mappers. + + polymorphic_identity - a value which will be stored in the Column denoted by polymorphic_on, corresponding to the + "class identity" of this mapper. + + concrete - if True, indicates this mapper should use concrete table inheritance with its parent mapper. + + select_table - a Table or (more commonly) Selectable which will be used to select instances of this mapper's class. + usually used to provide polymorphic loading among several classes in an inheritance hierarchy. + + allow_null_pks - indicates that composite primary keys where one or more (but not all) columns contain NULL is a valid + primary key. Primary keys which contain NULL values usually indicate that a result row does not contain an entity + and should be skipped. + + batch - indicates that save operations of multiple entities can be batched together for efficiency. + setting to False indicates that an instance will be fully saved before saving the next instance, which + includes inserting/updating all table rows corresponding to the entity as well as calling all MapperExtension + methods corresponding to the save operation. + + column_prefix - a string which will be prepended to the "key" name of all Columns when creating column-based + properties from the given Table. does not affect explicitly specified column-based properties + """ + if not issubclass(class_, object): + raise exceptions.ArgumentError("Class '%s' is not a new-style class" % class_.__name__) + + for table in (local_table, select_table): + if table is not None and isinstance(table, sql._SelectBaseMixin): + # some db's, noteably postgres, dont want to select from a select + # without an alias. also if we make our own alias internally, then + # the configured properties on the mapper are not matched against the alias + # we make, theres workarounds but it starts to get really crazy (its crazy enough + # the SQL that gets generated) so just require an alias + raise exceptions.ArgumentError("Mapping against a Select object requires that it has a name. Use an alias to give it a name, i.e. s = select(...).alias('myselect')") + + self.class_ = class_ + self.entity_name = entity_name + self.class_key = ClassKey(class_, entity_name) + self.primary_key = primary_key + self.non_primary = non_primary + self.order_by = order_by + self.always_refresh = always_refresh + self.version_id_col = version_id_col + self.concrete = concrete + self.single = False + self.inherits = inherits + self.select_table = select_table + self.local_table = local_table + self.inherit_condition = inherit_condition + self.extension = extension + self.properties = properties or {} + self.allow_column_override = allow_column_override + self.allow_null_pks = allow_null_pks + self.delete_orphans = [] + self.batch = batch + self.column_prefix = column_prefix + # a Column which is used during a select operation to retrieve the + # "polymorphic identity" of the row, which indicates which Mapper should be used + # to construct a new object instance from that row. + self.polymorphic_on = polymorphic_on + self._eager_loaders = util.Set() + + # our 'polymorphic identity', a string name that when located in a result set row + # indicates this Mapper should be used to construct the object instance for that row. + self.polymorphic_identity = polymorphic_identity + + # a dictionary of 'polymorphic identity' names, associating those names with + # Mappers that will be used to construct object instances upon a select operation. + if _polymorphic_map is None: + self.polymorphic_map = {} + else: + self.polymorphic_map = _polymorphic_map + + class LOrderedProp(util.OrderedProperties): + """this extends OrderedProperties to trigger a compile() before the + members of the object are accessed.""" + def _get_data(s): + self.compile() + return s.__dict__['_OrderedProperties__data'] + _OrderedProperties__data = property(_get_data) + + self.columns = LOrderedProp() + self.c = self.columns + + # each time the options() method is called, the resulting Mapper is + # stored in this dictionary based on the given options for fast re-access + self._options = {} + + # a set of all mappers which inherit from this one. + self._inheriting_mappers = util.Set() + + # a second mapper that is used for selecting, if the "select_table" argument + # was sent to this mapper. + self.__surrogate_mapper = None + + # whether or not our compile() method has been called already. + self.__is_compiled = False + + # if this mapper is to be a primary mapper (i.e. the non_primary flag is not set), + # associate this Mapper with the given class_ and entity name. subsequent + # calls to class_mapper() for the class_/entity name combination will return this + # mapper. + self._compile_class() + + self.__log("constructed") + + # uncomment to compile at construction time (the old way) + # this will break mapper setups that arent declared in the order + # of dependency + #self.compile() + + def __log(self, msg): + self.logger.info("(" + self.class_.__name__ + "|" + (self.entity_name is not None and "/%s" % self.entity_name or "") + (self.local_table and self.local_table.name or str(self.local_table)) + (not self._is_primary_mapper() and "|non-primary" or "") + ") " + msg) + + def __log_debug(self, msg): + self.logger.debug("(" + self.class_.__name__ + "|" + (self.entity_name is not None and "/%s" % self.entity_name or "") + (self.local_table and self.local_table.name or str(self.local_table)) + (not self._is_primary_mapper() and "|non-primary" or "") + ") " + msg) + + def _is_orphan(self, obj): + optimistic = has_identity(obj) + for (key,klass) in self.delete_orphans: + if getattr(klass, key).hasparent(obj, optimistic=optimistic): + return False + else: + if len(self.delete_orphans): + if not has_identity(obj): + raise exceptions.FlushError("instance %s is an unsaved, pending instance and is an orphan (is not attached to %s)" % + ( + obj, + ", nor ".join(["any parent '%s' instance via that classes' '%s' attribute" % (klass.__name__, key) for (key,klass) in self.delete_orphans]) + )) + else: + return True + else: + return False + + def _get_props(self): + self.compile() + return self.__props + props = property(_get_props, doc="compiles this mapper if needed, and returns the \ + dictionary of MapperProperty objects associated with this mapper.") + + def compile(self): + """compile this mapper into its final internal format. + + this is the 'external' version of the method which is not reentrant.""" + if self.__is_compiled: + return self + + self._compile_all() + + # if we're not primary, compile us + if self.non_primary: + self._do_compile() + self._initialize_properties() + + return self + + def _compile_all(self): + # compile all primary mappers + for mapper in mapper_registry.values(): + if not mapper.__is_compiled: + mapper._do_compile() + + # initialize properties on all mappers + for mapper in mapper_registry.values(): + if not mapper.__props_init: + mapper._initialize_properties() + + def _check_compile(self): + if self.non_primary: + self._do_compile() + self._initialize_properties() + return self + + def _do_compile(self): + """compile this mapper into its final internal format. + + this is the 'internal' version of the method which is assumed to be called within compile() + and is reentrant. + """ + if self.__is_compiled: + return self + self.__log("_do_compile() started") + self.__is_compiled = True + self.__props_init = False + self._compile_extensions() + self._compile_inheritance() + self._compile_tables() + self._compile_properties() + self._compile_selectable() + self.__log("_do_compile() complete") + return self + + def _compile_extensions(self): + """goes through the global_extensions list as well as the list of MapperExtensions + specified for this Mapper and creates a linked list of those extensions.""" + extlist = util.Set() + for ext_class in global_extensions: + if isinstance(ext_class, MapperExtension): + extlist.add(ext_class) + else: + extlist.add(ext_class()) + + extension = self.extension + if extension is not None: + for ext_obj in util.to_list(extension): + extlist.add(ext_obj) + + self.extension = _ExtensionCarrier() + for ext in extlist: + self.extension.append(ext) + + def _compile_inheritance(self): + """determines if this Mapper inherits from another mapper, and if so calculates the mapped_table + for this Mapper taking the inherited mapper into account. for joined table inheritance, creates + a SyncRule that will synchronize column values between the joined tables. also initializes polymorphic variables + used in polymorphic loads.""" + if self.inherits is not None: + if isinstance(self.inherits, type): + self.inherits = class_mapper(self.inherits, compile=False)._do_compile() + else: + self.inherits = self.inherits._do_compile() + if not issubclass(self.class_, self.inherits.class_): + raise exceptions.ArgumentError("Class '%s' does not inherit from '%s'" % (self.class_.__name__, self.inherits.class_.__name__)) + if self._is_primary_mapper() != self.inherits._is_primary_mapper(): + np = self._is_primary_mapper() and "primary" or "non-primary" + raise exceptions.ArgumentError("Inheritance of %s mapper for class '%s' is only allowed from a %s mapper" % (np, self.class_.__name__, np)) + # inherit_condition is optional. + if self.local_table is None: + self.local_table = self.inherits.local_table + self.single = True + if not self.local_table is self.inherits.local_table: + if self.concrete: + self._synchronizer= None + self.mapped_table = self.local_table + else: + if self.inherit_condition is None: + # figure out inherit condition from our table to the immediate table + # of the inherited mapper, not its full table which could pull in other + # stuff we dont want (allows test/inheritance.InheritTest4 to pass) + self.inherit_condition = sql.join(self.inherits.local_table, self.local_table).onclause + self.mapped_table = sql.join(self.inherits.mapped_table, self.local_table, self.inherit_condition) + # generate sync rules. similarly to creating the on clause, specify a + # stricter set of tables to create "sync rules" by,based on the immediate + # inherited table, rather than all inherited tables + self._synchronizer = sync.ClauseSynchronizer(self, self, sync.ONETOMANY) + self._synchronizer.compile(self.mapped_table.onclause) + else: + self._synchronizer = None + self.mapped_table = self.local_table + if self.polymorphic_identity is not None: + self.inherits._add_polymorphic_mapping(self.polymorphic_identity, self) + if self.polymorphic_on is None and self.inherits.polymorphic_on is not None: + self.polymorphic_on = self.mapped_table.corresponding_column(self.inherits.polymorphic_on, keys_ok=True, raiseerr=False) + if self.order_by is False: + self.order_by = self.inherits.order_by + self.polymorphic_map = self.inherits.polymorphic_map + self.batch = self.inherits.batch + self.inherits._inheriting_mappers.add(self) + else: + self._synchronizer = None + self.mapped_table = self.local_table + if self.polymorphic_identity is not None: + self._add_polymorphic_mapping(self.polymorphic_identity, self) + + if self.mapped_table is None: + raise exceptions.ArgumentError("Mapper '%s' does not have a mapped_table specified. (Are you using the return value of table.create()? It no longer has a return value.)" % str(self)) + + # convert polymorphic class associations to mappers + for key in self.polymorphic_map.keys(): + if isinstance(self.polymorphic_map[key], type): + self.polymorphic_map[key] = class_mapper(self.polymorphic_map[key]) + + def _add_polymorphic_mapping(self, key, class_or_mapper, entity_name=None): + """adds a Mapper to our 'polymorphic map' """ + if isinstance(class_or_mapper, type): + class_or_mapper = class_mapper(class_or_mapper, entity_name=entity_name) + self.polymorphic_map[key] = class_or_mapper + + def _compile_tables(self): + """after the inheritance relationships have been reconciled, sets up some more table-based instance + variables and determines the "primary key" columns for all tables represented by this Mapper.""" + + # summary of the various Selectable units: + # mapped_table - the Selectable that represents a join of the underlying Tables to be saved (or just the Table) + # local_table - the Selectable that was passed to this Mapper's constructor, if any + # select_table - the Selectable that will be used during queries. if this is specified + # as a constructor keyword argument, it takes precendence over mapped_table, otherwise its mapped_table + # unjoined_table - our Selectable, minus any joins constructed against the inherits table. + # this is either select_table if it was given explicitly, or in the case of a mapper that inherits + # its local_table + # tables - a collection of underlying Table objects pulled from mapped_table + + if self.select_table is None: + self.select_table = self.mapped_table + self.unjoined_table = self.local_table + + # locate all tables contained within the "table" passed in, which + # may be a join or other construct + self.tables = sqlutil.TableFinder(self.mapped_table) + + # determine primary key columns, either passed in, or get them from our set of tables + self.pks_by_table = {} + if self.primary_key is not None: + # determine primary keys using user-given list of primary key columns as a guide + # + # TODO: this might not work very well for joined-table and/or polymorphic + # inheritance mappers since local_table isnt taken into account nor is select_table + # need to test custom primary key columns used with inheriting mappers + for k in self.primary_key: + self.pks_by_table.setdefault(k.table, util.OrderedSet()).add(k) + if k.table != self.mapped_table: + # associate pk cols from subtables to the "main" table + self.pks_by_table.setdefault(self.mapped_table, util.OrderedSet()).add(k) + else: + # no user-defined primary key columns - go through all of our represented tables + # and assemble primary key columns + for t in self.tables + [self.mapped_table]: + try: + l = self.pks_by_table[t] + except KeyError: + l = self.pks_by_table.setdefault(t, util.OrderedSet()) + for k in t.primary_key: + #if k.key not in t.c and k._label not in t.c: + # this is a condition that was occurring when table reflection was doubling up primary keys + # that were overridden in the Table constructor + # raise exceptions.AssertionError("Column " + str(k) + " not located in the column set of table " + str(t)) + l.add(k) + + if len(self.pks_by_table[self.mapped_table]) == 0: + raise exceptions.ArgumentError("Could not assemble any primary key columns for mapped table '%s'" % (self.mapped_table.name)) + + + def _compile_properties(self): + """inspects the properties dictionary sent to the Mapper's constructor as well as the mapped_table, and creates + MapperProperty objects corresponding to each mapped column and relation. also grabs MapperProperties from the + inherited mapper, if any, and creates copies of them to attach to this Mapper.""" + # object attribute names mapped to MapperProperty objects + self.__props = {} + + # table columns mapped to lists of MapperProperty objects + # using a list allows a single column to be defined as + # populating multiple object attributes + self.columntoproperty = mapperutil.TranslatingDict(self.mapped_table) + + # load custom properties + if self.properties is not None: + for key, prop in self.properties.iteritems(): + self._compile_property(key, prop, False) + + if self.inherits is not None: + for key, prop in self.inherits.__props.iteritems(): + if not self.__props.has_key(key): + prop.adapt_to_inherited(key, self) + + # load properties from the main table object, + # not overriding those set up in the 'properties' argument + for column in self.mapped_table.columns: + if self.columntoproperty.has_key(column): + continue + if not self.columns.has_key(column.key): + self.columns[column.key] = self.select_table.corresponding_column(column, keys_ok=True, raiseerr=True) + + column_key = (self.column_prefix or '') + column.key + prop = self.__props.get(column.key, None) + if prop is None: + prop = ColumnProperty(column) + self.__props[column_key] = prop + prop.set_parent(self) + self.__log("adding ColumnProperty %s" % (column_key)) + elif isinstance(prop, ColumnProperty): + if prop.parent is not self: + prop = ColumnProperty(deferred=prop.deferred, group=prop.group, *prop.columns) + prop.set_parent(self) + self.__props[column_key] = prop + prop.columns.append(column) + self.__log("appending to existing ColumnProperty %s" % (column_key)) + else: + if not self.allow_column_override: + raise exceptions.ArgumentError("WARNING: column '%s' not being added due to property '%s'. Specify 'allow_column_override=True' to mapper() to ignore this condition." % (column.key, repr(prop))) + else: + continue + + # its a ColumnProperty - match the ultimate table columns + # back to the property + proplist = self.columntoproperty.setdefault(column, []) + proplist.append(prop) + + + def _initialize_properties(self): + """calls the init() method on all MapperProperties attached to this mapper. this will incur the + compilation of related mappers.""" + self.__log("_initialize_properties() started") + l = [(key, prop) for key, prop in self.__props.iteritems()] + for key, prop in l: + if getattr(prop, 'key', None) is None: + prop.init(key, self) + self.__log("_initialize_properties() complete") + self.__props_init = True + + def _compile_selectable(self): + """if the 'select_table' keyword argument was specified, + set up a second "surrogate mapper" that will be used for select operations. + the columns of select_table should encompass all the columns of the mapped_table either directly + or through proxying relationships.""" + if self.select_table is not self.mapped_table: + props = {} + if self.properties is not None: + for key, prop in self.properties.iteritems(): + if sql.is_column(prop): + props[key] = self.select_table.corresponding_column(prop) + elif (isinstance(prop, list) and sql.is_column(prop[0])): + props[key] = [self.select_table.corresponding_column(c) for c in prop] + self.__surrogate_mapper = Mapper(self.class_, self.select_table, non_primary=True, properties=props, _polymorphic_map=self.polymorphic_map, polymorphic_on=self.select_table.corresponding_column(self.polymorphic_on)) + + def _compile_class(self): + """if this mapper is to be a primary mapper (i.e. the non_primary flag is not set), + associate this Mapper with the given class_ and entity name. subsequent + calls to class_mapper() for the class_/entity name combination will return this + mapper. also decorates the __init__ method on the mapped class to include auto-session attachment logic.""" + if self.non_primary: + return + + if not self.non_primary and (mapper_registry.has_key(self.class_key)): + raise exceptions.ArgumentError("Class '%s' already has a primary mapper defined with entity name '%s'. Use non_primary=True to create a non primary Mapper, or to create a new primary mapper, remove this mapper first via sqlalchemy.orm.clear_mapper(mapper), or preferably sqlalchemy.orm.clear_mappers() to clear all mappers." % (self.class_, self.entity_name)) + + attribute_manager.reset_class_managed(self.class_) + + oldinit = self.class_.__init__ + def init(self, *args, **kwargs): + entity_name = kwargs.pop('_sa_entity_name', None) + mapper = mapper_registry.get(ClassKey(self.__class__, entity_name)) + if mapper is not None: + mapper = mapper.compile() + + # this gets the AttributeManager to do some pre-initialization, + # in order to save on KeyErrors later on + attribute_manager.init_attr(self) + + if kwargs.has_key('_sa_session'): + session = kwargs.pop('_sa_session') + else: + # works for whatever mapper the class is associated with + if mapper is not None: + session = mapper.extension.get_session() + if session is EXT_PASS: + session = None + else: + session = None + # if a session was found, either via _sa_session or via mapper extension, + # and we have found a mapper, save() this instance to the session, and give it an associated entity_name. + # otherwise, this instance will not have a session or mapper association until it is + # save()d to some session. + if session is not None and mapper is not None: + self._entity_name = entity_name + session._register_pending(self) + + if oldinit is not None: + try: + oldinit(self, *args, **kwargs) + except: + if session is not None: + session.expunge(self) + raise + # override oldinit, insuring that its not already a Mapper-decorated init method + if oldinit is None or not hasattr(oldinit, '_sa_mapper_init'): + init._sa_mapper_init = True + try: + init.__name__ = oldinit.__name__ + init.__doc__ = oldinit.__doc__ + except: + # cant set __name__ in py 2.3 ! + pass + self.class_.__init__ = init + mapper_registry[self.class_key] = self + if self.entity_name is None: + self.class_.c = self.c + + def base_mapper(self): + """return the ultimate base mapper in an inheritance chain""" + if self.inherits is not None: + return self.inherits.base_mapper() + else: + return self + + def common_parent(self, other): + """return true if the given mapper shares a common inherited parent as this mapper""" + return self.base_mapper() is other.base_mapper() + + def isa(self, other): + """return True if the given mapper inherits from this mapper""" + m = other + while m is not self and m.inherits is not None: + m = m.inherits + return m is self + + def iterate_to_root(self): + m = self + while m is not None: + yield m + m = m.inherits + + def polymorphic_iterator(self): + """iterates through the collection including this mapper and all descendant mappers. + + this includes not just the immediately inheriting mappers but all their inheriting mappers as well. + + To iterate through an entire hierarchy, use mapper.base_mapper().polymorphic_iterator().""" + def iterate(m): + yield m + for mapper in m._inheriting_mappers: + for x in iterate(mapper): + yield x + return iterate(self) + + def add_properties(self, dict_of_properties): + """adds the given dictionary of properties to this mapper, using add_property.""" + for key, value in dict_of_properties.iteritems(): + self.add_property(key, value) + + def add_property(self, key, prop): + """add an indiviual MapperProperty to this mapper. + + If the mapper has not been compiled yet, just adds the property to the initial + properties dictionary sent to the constructor. if this Mapper + has already been compiled, then the given MapperProperty is compiled immediately.""" + self.properties[key] = prop + if self.__is_compiled: + # if we're compiled, make sure all the other mappers are compiled too + self._compile_all() + self._compile_property(key, prop, init=True) + + def _create_prop_from_column(self, column, skipmissing=False): + if sql.is_column(column): + try: + column = self.mapped_table.corresponding_column(column) + except KeyError: + if skipmissing: + return + raise exceptions.ArgumentError("Column '%s' is not represented in mapper's table" % prop._label) + return ColumnProperty(column) + elif isinstance(column, list) and sql.is_column(column[0]): + try: + column = [self.mapped_table.corresponding_column(c) for c in column] + except KeyError, e: + # TODO: want to take the columns we have from this + if skipmissing: + return + raise exceptions.ArgumentError("Column '%s' is not represented in mapper's table" % e.args[0]) + return ColumnProperty(*column) + else: + return None + + def _compile_property(self, key, prop, init=True, skipmissing=False, setparent=True): + """add a MapperProperty to this or another Mapper, including configuration of the property. + + The properties' parent attribute will be set, and the property will also be + copied amongst the mappers which inherit from this one. + + if the given prop is a Column or list of Columns, a ColumnProperty will be created. + """ + self.__log("_compile_property(%s, %s)" % (key, prop.__class__.__name__)) + + if not isinstance(prop, MapperProperty): + prop = self._create_prop_from_column(prop, skipmissing=skipmissing) + if prop is None: + raise exceptions.ArgumentError("'%s' is not an instance of MapperProperty or Column" % repr(prop)) + + self.__props[key] = prop + if setparent: + prop.set_parent(self) + + if isinstance(prop, ColumnProperty): + col = self.select_table.corresponding_column(prop.columns[0], keys_ok=True, raiseerr=False) + if col is None: + col = prop.columns[0] + self.columns[key] = col + for col in prop.columns: + proplist = self.columntoproperty.setdefault(col, []) + proplist.append(prop) + + if init: + prop.init(key, self) + + for mapper in self._inheriting_mappers: + prop.adapt_to_inherited(key, mapper) + + def __str__(self): + return "Mapper|" + self.class_.__name__ + "|" + (self.entity_name is not None and "/%s" % self.entity_name or "") + (self.local_table and self.local_table.name or str(self.local_table)) + (not self._is_primary_mapper() and "|non-primary" or "") + + def _is_primary_mapper(self): + """returns True if this mapper is the primary mapper for its class key (class + entity_name)""" + return mapper_registry.get(self.class_key, None) is self + + def primary_mapper(self): + """returns the primary mapper corresponding to this mapper's class key (class + entity_name)""" + return mapper_registry[self.class_key] + + def is_assigned(self, instance): + """return True if this mapper handles the given instance. + + this is dependent not only on class assignment but the optional "entity_name" parameter as well.""" + return instance.__class__ is self.class_ and getattr(instance, '_entity_name', None) == self.entity_name + + def _assign_entity_name(self, instance): + """assign this Mapper's entity name to the given instance. + + subsequent Mapper lookups for this instance will return the primary + mapper corresponding to this Mapper's class and entity name.""" + instance._entity_name = self.entity_name + + def get_session(self): + """return the contextual session provided by the mapper extension chain, if any. + + raises InvalidRequestError if a session cannot be retrieved from the extension chain + """ + self.compile() + s = self.extension.get_session() + if s is EXT_PASS: + raise exceptions.InvalidRequestError("No contextual Session is established. Use a MapperExtension that implements get_session or use 'import sqlalchemy.mods.threadlocal' to establish a default thread-local contextual session.") + return s + + def has_eager(self): + """return True if one of the properties attached to this Mapper is eager loading""" + return len(self._eager_loaders) > 0 + + def instances(self, cursor, session, *mappers, **kwargs): + """return a list of mapped instances corresponding to the rows in a given ResultProxy.""" + import sqlalchemy.orm.query + return sqlalchemy.orm.Query(self, session).instances(cursor, *mappers, **kwargs) + + def identity_key_from_row(self, row): + """return an identity-map key for use in storing/retrieving an item from the identity map. + + row - a sqlalchemy.dbengine.RowProxy instance or other map corresponding result-set + column names to their values within a row. + """ + return (self.class_, tuple([row[column] for column in self.pks_by_table[self.mapped_table]]), self.entity_name) + + def identity_key_from_primary_key(self, primary_key): + """return an identity-map key for use in storing/retrieving an item from an identity map. + + primary_key - a list of values indicating the identifier. + """ + return (self.class_, tuple(util.to_list(primary_key)), self.entity_name) + + def identity_key_from_instance(self, instance): + """return the identity key for the given instance, based on its primary key attributes. + + this value is typically also found on the instance itself under the attribute name '_instance_key'. + """ + return self.identity_key_from_primary_key(self.primary_key_from_instance(instance)) + + def primary_key_from_instance(self, instance): + """return the list of primary key values for the given instance.""" + return [self.get_attr_by_column(instance, column) for column in self.pks_by_table[self.mapped_table]] + + def instance_key(self, instance): + """deprecated. a synonym for identity_key_from_instance.""" + return self.identity_key_from_instance(instance) + + def identity_key(self, primary_key): + """deprecated. a synonym for identity_key_from_primary_key.""" + return self.identity_key_from_primary_key(primary_key) + + def identity(self, instance): + """deprecated. a synoynm for primary_key_from_instance.""" + return self.primary_key_from_instance(instance) + + def _getpropbycolumn(self, column, raiseerror=True): + try: + prop = self.columntoproperty[column] + except KeyError: + try: + prop = self.__props[column.key] + if not raiseerror: + return None + raise exceptions.InvalidRequestError("Column '%s.%s' is not available, due to conflicting property '%s':%s" % (column.table.name, column.name, column.key, repr(prop))) + except KeyError: + if not raiseerror: + return None + raise exceptions.InvalidRequestError("No column %s.%s is configured on mapper %s..." % (column.table.name, column.name, str(self))) + return prop[0] + + def get_attr_by_column(self, obj, column, raiseerror=True): + """return an instance attribute using a Column as the key.""" + prop = self._getpropbycolumn(column, raiseerror) + if prop is None: + return NO_ATTRIBUTE + #self.__log_debug("get column attribute '%s' from instance %s" % (column.key, mapperutil.instance_str(obj))) + return prop.getattr(obj) + + def set_attr_by_column(self, obj, column, value): + """set the value of an instance attribute using a Column as the key.""" + self.columntoproperty[column][0].setattr(obj, value) + + def save_obj(self, objects, uowtransaction, postupdate=False, post_update_cols=None, single=False): + """issue INSERT and/or UPDATE statements for a list of objects. + + this is called within the context of a UOWTransaction during a flush operation. + + save_obj issues SQL statements not just for instances mapped directly by this mapper, but + for instances mapped by all inheriting mappers as well. This is to maintain proper insert + ordering among a polymorphic chain of instances. Therefore save_obj is typically + called only on a "base mapper", or a mapper which does not inherit from any other mapper.""" + + self.__log_debug("save_obj() start, " + (single and "non-batched" or "batched")) + + # if batch=false, call save_obj separately for each object + if not single and not self.batch: + for obj in objects: + self.save_obj([obj], uowtransaction, postupdate=postupdate, post_update_cols=post_update_cols, single=True) + return + + connection = uowtransaction.transaction.connection(self) + + if not postupdate: + for obj in objects: + if not has_identity(obj): + for mapper in object_mapper(obj).iterate_to_root(): + mapper.extension.before_insert(mapper, connection, obj) + else: + for mapper in object_mapper(obj).iterate_to_root(): + mapper.extension.before_update(mapper, connection, obj) + + for obj in objects: + # detect if we have a "pending" instance (i.e. has no instance_key attached to it), + # and another instance with the same identity key already exists as persistent. convert to an + # UPDATE if so. + mapper = object_mapper(obj) + instance_key = mapper.instance_key(obj) + is_row_switch = not postupdate and not has_identity(obj) and instance_key in uowtransaction.uow.identity_map + if is_row_switch: + existing = uowtransaction.uow.identity_map[instance_key] + if not uowtransaction.is_deleted(existing): + raise exceptions.FlushError("New instance %s with identity key %s conflicts with persistent instance %s" % (mapperutil.instance_str(obj), str(instance_key), mapperutil.instance_str(existing))) + self.__log_debug("detected row switch for identity %s. will update %s, remove %s from transaction" % (instance_key, mapperutil.instance_str(obj), mapperutil.instance_str(existing))) + uowtransaction.unregister_object(existing) + + inserted_objects = util.Set() + updated_objects = util.Set() + + table_to_mapper = {} + for mapper in self.base_mapper().polymorphic_iterator(): + for t in mapper.tables: + table_to_mapper.setdefault(t, mapper) + + for table in sqlutil.TableCollection(list(table_to_mapper.keys())).sort(reverse=False): + # two lists to store parameters for each table/object pair located + insert = [] + update = [] + + for obj in objects: + mapper = object_mapper(obj) + if table not in mapper.tables or not mapper._has_pks(table): + continue + instance_key = mapper.instance_key(obj) + self.__log_debug("save_obj() table '%s' instance %s identity %s" % (table.name, mapperutil.instance_str(obj), str(instance_key))) + + isinsert = not instance_key in uowtransaction.uow.identity_map and not postupdate and not has_identity(obj) + params = {} + hasdata = False + for col in table.columns: + if col is mapper.version_id_col: + if not isinsert: + params[col._label] = mapper.get_attr_by_column(obj, col) + params[col.key] = params[col._label] + 1 + else: + params[col.key] = 1 + elif col in mapper.pks_by_table[table]: + # column is a primary key ? + if not isinsert: + # doing an UPDATE? put primary key values as "WHERE" parameters + # matching the bindparam we are creating below, i.e. "_" + params[col._label] = mapper.get_attr_by_column(obj, col) + else: + # doing an INSERT, primary key col ? + # if the primary key values are not populated, + # leave them out of the INSERT altogether, since PostGres doesn't want + # them to be present for SERIAL to take effect. A SQLEngine that uses + # explicit sequences will put them back in if they are needed + value = mapper.get_attr_by_column(obj, col) + if value is not None: + params[col.key] = value + elif mapper.polymorphic_on is not None and mapper.polymorphic_on.shares_lineage(col): + if isinsert: + self.__log_debug("Using polymorphic identity '%s' for insert column '%s'" % (mapper.polymorphic_identity, col.key)) + value = mapper.polymorphic_identity + if col.default is None or value is not None: + params[col.key] = value + else: + # column is not a primary key ? + if not isinsert: + # doing an UPDATE ? get the history for the attribute, with "passive" + # so as not to trigger any deferred loads. if there is a new + # value, add it to the bind parameters + if post_update_cols is not None and col not in post_update_cols: + continue + elif is_row_switch: + params[col.key] = self.get_attr_by_column(obj, col) + hasdata = True + continue + prop = mapper._getpropbycolumn(col, False) + if prop is None: + continue + history = prop.get_history(obj, passive=True) + if history: + a = history.added_items() + if len(a): + params[col.key] = a[0] + hasdata = True + else: + # doing an INSERT, non primary key col ? + # add the attribute's value to the + # bind parameters, unless its None and the column has a + # default. if its None and theres no default, we still might + # not want to put it in the col list but SQLIte doesnt seem to like that + # if theres no columns at all + value = mapper.get_attr_by_column(obj, col, False) + if value is NO_ATTRIBUTE: + continue + if col.default is None or value is not None: + params[col.key] = value + + if not isinsert: + if hasdata: + # if none of the attributes changed, dont even + # add the row to be updated. + update.append((obj, params, mapper)) + else: + insert.append((obj, params, mapper)) + + if len(update): + mapper = table_to_mapper[table] + clause = sql.and_() + for col in mapper.pks_by_table[table]: + clause.clauses.append(col == sql.bindparam(col._label, type=col.type)) + if mapper.version_id_col is not None: + clause.clauses.append(mapper.version_id_col == sql.bindparam(mapper.version_id_col._label, type=col.type)) + statement = table.update(clause) + rows = 0 + supports_sane_rowcount = True + def comparator(a, b): + for col in mapper.pks_by_table[table]: + x = cmp(a[1][col._label],b[1][col._label]) + if x != 0: + return x + return 0 + update.sort(comparator) + for rec in update: + (obj, params, mapper) = rec + c = connection.execute(statement, params) + mapper._postfetch(connection, table, obj, c, c.last_updated_params()) + + updated_objects.add(obj) + rows += c.cursor.rowcount + + if c.supports_sane_rowcount() and rows != len(update): + raise exceptions.ConcurrentModificationError("Updated rowcount %d does not match number of objects updated %d" % (rows, len(update))) + + if len(insert): + statement = table.insert() + def comparator(a, b): + return cmp(a[0]._sa_insert_order, b[0]._sa_insert_order) + insert.sort(comparator) + for rec in insert: + (obj, params, mapper) = rec + c = connection.execute(statement, params) + primary_key = c.last_inserted_ids() + if primary_key is not None: + i = 0 + for col in mapper.pks_by_table[table]: + if mapper.get_attr_by_column(obj, col) is None and len(primary_key) > i: + mapper.set_attr_by_column(obj, col, primary_key[i]) + i+=1 + mapper._postfetch(connection, table, obj, c, c.last_inserted_params()) + + # synchronize newly inserted ids from one table to the next + # TODO: this fires off more than needed, try to organize syncrules + # per table + def sync(mapper): + inherit = mapper.inherits + if inherit is not None: + sync(inherit) + if mapper._synchronizer is not None: + mapper._synchronizer.execute(obj, obj) + sync(mapper) + + inserted_objects.add(obj) + if not postupdate: + for obj in inserted_objects: + for mapper in object_mapper(obj).iterate_to_root(): + mapper.extension.after_insert(mapper, connection, obj) + for obj in updated_objects: + for mapper in object_mapper(obj).iterate_to_root(): + mapper.extension.after_update(mapper, connection, obj) + + def _postfetch(self, connection, table, obj, resultproxy, params): + """after an INSERT or UPDATE, asks the returned result if PassiveDefaults fired off on the database side + which need to be post-fetched, *or* if pre-exec defaults like ColumnDefaults were fired off + and should be populated into the instance. this is only for non-primary key columns.""" + if resultproxy.lastrow_has_defaults(): + clause = sql.and_() + for p in self.pks_by_table[table]: + clause.clauses.append(p == self.get_attr_by_column(obj, p)) + row = connection.execute(table.select(clause), None).fetchone() + for c in table.c: + if self.get_attr_by_column(obj, c, False) is None: + self.set_attr_by_column(obj, c, row[c]) + else: + for c in table.c: + if c.primary_key or not params.has_key(c.name): + continue + v = self.get_attr_by_column(obj, c, False) + if v is NO_ATTRIBUTE: + continue + elif v != params.get_original(c.name): + self.set_attr_by_column(obj, c, params.get_original(c.name)) + + def delete_obj(self, objects, uowtransaction): + """issue DELETE statements for a list of objects. + + this is called within the context of a UOWTransaction during a flush operation.""" + + self.__log_debug("delete_obj() start") + + connection = uowtransaction.transaction.connection(self) + + [self.extension.before_delete(self, connection, obj) for obj in objects] + deleted_objects = util.Set() + for table in self.tables.sort(reverse=True): + if not self._has_pks(table): + continue + delete = [] + for obj in objects: + params = {} + if not hasattr(obj, "_instance_key"): + continue + else: + delete.append(params) + for col in self.pks_by_table[table]: + params[col.key] = self.get_attr_by_column(obj, col) + if self.version_id_col is not None: + params[self.version_id_col.key] = self.get_attr_by_column(obj, self.version_id_col) + deleted_objects.add(obj) + if len(delete): + def comparator(a, b): + for col in self.pks_by_table[table]: + x = cmp(a[col.key],b[col.key]) + if x != 0: + return x + return 0 + delete.sort(comparator) + clause = sql.and_() + for col in self.pks_by_table[table]: + clause.clauses.append(col == sql.bindparam(col.key, type=col.type)) + if self.version_id_col is not None: + clause.clauses.append(self.version_id_col == sql.bindparam(self.version_id_col.key, type=self.version_id_col.type)) + statement = table.delete(clause) + c = connection.execute(statement, delete) + if c.supports_sane_rowcount() and c.rowcount != len(delete): + raise exceptions.ConcurrentModificationError("Updated rowcount %d does not match number of objects updated %d" % (c.cursor.rowcount, len(delete))) + + [self.extension.after_delete(self, connection, obj) for obj in deleted_objects] + + def _has_pks(self, table): + try: + for k in self.pks_by_table[table]: + if not self.columntoproperty.has_key(k): + return False + else: + return True + except KeyError: + return False + + def register_dependencies(self, uowcommit, *args, **kwargs): + """register DependencyProcessor instances with a unitofwork.UOWTransaction. + + this calls register_dependencies on all attached MapperProperty instances.""" + for prop in self.__props.values(): + prop.register_dependencies(uowcommit, *args, **kwargs) + + def cascade_iterator(self, type, object, recursive=None): + """iterate each element in an object graph, for all relations taht meet the given cascade rule. + + type - the name of the cascade rule (i.e. save-update, delete, etc.) + + object - the lead object instance. child items will be processed per the relations + defined for this object's mapper. + + recursive - used by the function for internal context during recursive calls, leave as None.""" + if recursive is None: + recursive=util.Set() + for prop in self.__props.values(): + for c in prop.cascade_iterator(type, object, recursive): + yield c + + def cascade_callable(self, type, object, callable_, recursive=None): + """execute a callable for each element in an object graph, for all relations that meet the given cascade rule. + + type - the name of the cascade rule (i.e. save-update, delete, etc.) + + object - the lead object instance. child items will be processed per the relations + defined for this object's mapper. + + callable_ - the callable function. + + recursive - used by the function for internal context during recursive calls, leave as None.""" + if recursive is None: + recursive=util.Set() + for prop in self.__props.values(): + prop.cascade_callable(type, object, callable_, recursive) + + + def get_select_mapper(self): + """return the mapper used for issuing selects. + + this mapper is the same mapper as 'self' unless the select_table argument was specified for this mapper.""" + return self.__surrogate_mapper or self + + def _instance(self, context, row, result = None): + """pulls an object instance from the given row and appends it to the given result + list. if the instance already exists in the given identity map, its not added. in + either case, executes all the property loaders on the instance to also process extra + information in the row.""" + + if self.polymorphic_on is not None: + discriminator = row[self.polymorphic_on] + mapper = self.polymorphic_map[discriminator] + if mapper is not self: + row = self.translate_row(mapper, row) + return mapper._instance(context, row, result=result) + + # look in main identity map. if its there, we dont do anything to it, + # including modifying any of its related items lists, as its already + # been exposed to being modified by the application. + + populate_existing = context.populate_existing or self.always_refresh + identitykey = self.identity_key_from_row(row) + if context.session.has_key(identitykey): + instance = context.session._get(identitykey) + self.__log_debug("_instance(): using existing instance %s identity %s" % (mapperutil.instance_str(instance), str(identitykey))) + isnew = False + if context.version_check and self.version_id_col is not None and self.get_attr_by_column(instance, self.version_id_col) != row[self.version_id_col]: + raise exceptions.ConcurrentModificationError("Instance '%s' version of %s does not match %s" % (instance, self.get_attr_by_column(instance, self.version_id_col), row[self.version_id_col])) + + if populate_existing or context.session.is_expired(instance, unexpire=True): + if not context.identity_map.has_key(identitykey): + context.identity_map[identitykey] = instance + if self.extension.populate_instance(self, context, row, instance, identitykey, True) is EXT_PASS: + self.populate_instance(context, instance, row, identitykey, True) + if self.extension.append_result(self, context, row, instance, identitykey, result, isnew) is EXT_PASS: + if result is not None: + result.append(instance) + return instance + else: + self.__log_debug("_instance(): identity key %s not in session" % str(identitykey) + repr([mapperutil.instance_str(x) for x in context.session])) + # look in result-local identitymap for it. + exists = context.identity_map.has_key(identitykey) + if not exists: + if self.allow_null_pks: + # check if *all* primary key cols in the result are None - this indicates + # an instance of the object is not present in the row. + for col in self.pks_by_table[self.mapped_table]: + if row[col] is not None: + break + else: + return None + else: + # otherwise, check if *any* primary key cols in the result are None - this indicates + # an instance of the object is not present in the row. + for col in self.pks_by_table[self.mapped_table]: + if row[col] is None: + return None + + # plugin point + instance = self.extension.create_instance(self, context, row, self.class_) + if instance is EXT_PASS: + instance = self._create_instance(context.session) + self.__log_debug("_instance(): created new instance %s identity %s" % (mapperutil.instance_str(instance), str(identitykey))) + context.identity_map[identitykey] = instance + isnew = True + else: + instance = context.identity_map[identitykey] + isnew = False + + # call further mapper properties on the row, to pull further + # instances from the row and possibly populate this item. + if self.extension.populate_instance(self, context, row, instance, identitykey, isnew) is EXT_PASS: + self.populate_instance(context, instance, row, identitykey, isnew) + if self.extension.append_result(self, context, row, instance, identitykey, result, isnew) is EXT_PASS: + if result is not None: + result.append(instance) + return instance + + def _create_instance(self, session): + obj = self.class_.__new__(self.class_) + obj._entity_name = self.entity_name + + # this gets the AttributeManager to do some pre-initialization, + # in order to save on KeyErrors later on + attribute_manager.init_attr(obj) + + return obj + + def translate_row(self, tomapper, row): + """translate the column keys of a row into a new or proxied row that + can be understood by another mapper. + + This can be used in conjunction with populate_instance to populate + an instance using an alternate mapper.""" + newrow = util.DictDecorator(row) + for c in tomapper.mapped_table.c: + c2 = self.mapped_table.corresponding_column(c, keys_ok=True, raiseerr=True) + if row.has_key(c2): + newrow[c] = row[c2] + return newrow + + def populate_instance(self, selectcontext, instance, row, identitykey, isnew): + """populate an instance from a result row. + + This method iterates through the list of MapperProperty objects attached to this Mapper + and calls each properties execute() method.""" + for prop in self.__props.values(): + prop.execute(selectcontext, instance, row, identitykey, isnew) + +Mapper.logger = logging.class_logger(Mapper) + + +class MapperExtension(object): + """base implementation for an object that provides overriding behavior to various + Mapper functions. For each method in MapperExtension, a result of EXT_PASS indicates + the functionality is not overridden.""" + def get_session(self): + """retrieve a contextual Session instance with which to register a new object. + + Note: this is not called if a session is provided with the __init__ params (i.e. _sa_session)""" + return EXT_PASS + def load(self, query, *args, **kwargs): + """override the load method of the Query object. + + the return value of this method is used as the result of query.load() if the + value is anything other than EXT_PASS.""" + return EXT_PASS + def get(self, query, *args, **kwargs): + """override the get method of the Query object. + + the return value of this method is used as the result of query.get() if the + value is anything other than EXT_PASS.""" + return EXT_PASS + def get_by(self, query, *args, **kwargs): + """override the get_by method of the Query object. + + the return value of this method is used as the result of query.get_by() if the + value is anything other than EXT_PASS.""" + return EXT_PASS + def select_by(self, query, *args, **kwargs): + """override the select_by method of the Query object. + + the return value of this method is used as the result of query.select_by() if the + value is anything other than EXT_PASS.""" + return EXT_PASS + def select(self, query, *args, **kwargs): + """override the select method of the Query object. + + the return value of this method is used as the result of query.select() if the + value is anything other than EXT_PASS.""" + return EXT_PASS + def create_instance(self, mapper, selectcontext, row, class_): + """receieve a row when a new object instance is about to be created from that row. + the method can choose to create the instance itself, or it can return + None to indicate normal object creation should take place. + + mapper - the mapper doing the operation + + selectcontext - SelectionContext corresponding to the instances() call + + row - the result row from the database + + class_ - the class we are mapping. + """ + return EXT_PASS + def append_result(self, mapper, selectcontext, row, instance, identitykey, result, isnew): + """receive an object instance before that instance is appended to a result list. + + If this method returns EXT_PASS, result appending will proceed normally. + if this method returns any other value or None, result appending will not proceed for + this instance, giving this extension an opportunity to do the appending itself, if desired. + + mapper - the mapper doing the operation + + selectcontext - SelectionContext corresponding to the instances() call + + row - the result row from the database + + instance - the object instance to be appended to the result + + identitykey - the identity key of the instance + + result - list to which results are being appended + + isnew - indicates if this is the first time we have seen this object instance in the current result + set. if you are selecting from a join, such as an eager load, you might see the same object instance + many times in the same result set. + """ + return EXT_PASS + def populate_instance(self, mapper, selectcontext, row, instance, identitykey, isnew): + """receive a newly-created instance before that instance has its attributes populated. + + The normal population of attributes is according to each + attribute's corresponding MapperProperty (which includes column-based attributes as well + as relationships to other classes). If this method returns EXT_PASS, instance population + will proceed normally. If any other value or None is returned, instance population + will not proceed, giving this extension an opportunity to populate the instance itself, + if desired. + """ + return EXT_PASS + def before_insert(self, mapper, connection, instance): + """receive an object instance before that instance is INSERTed into its table. + + this is a good place to set up primary key values and such that arent handled otherwise.""" + return EXT_PASS + def before_update(self, mapper, connection, instance): + """receive an object instance before that instance is UPDATEed.""" + return EXT_PASS + def after_update(self, mapper, connection, instance): + """receive an object instance after that instance is UPDATEed.""" + return EXT_PASS + def after_insert(self, mapper, connection, instance): + """receive an object instance after that instance is INSERTed.""" + return EXT_PASS + def before_delete(self, mapper, connection, instance): + """receive an object instance before that instance is DELETEed.""" + return EXT_PASS + def after_delete(self, mapper, connection, instance): + """receive an object instance after that instance is DELETEed.""" + return EXT_PASS + +class _ExtensionCarrier(MapperExtension): + def __init__(self): + self.__elements = [] + self.__callables = {} + def insert(self, extension): + """insert a MapperExtension at the beginning of this ExtensionCarrier's list.""" + self.__elements.insert(0, extension) + def append(self, extension): + """append a MapperExtension at the end of this ExtensionCarrier's list.""" + self.__elements.append(extension) + def __getattribute__(self, key): + if key in MapperExtension.__dict__: + try: + return self.__callables[key] + except KeyError: + return self.__callables.setdefault(key, lambda *args, **kwargs:self._do(key, *args, **kwargs)) + else: + return super(_ExtensionCarrier, self).__getattribute__(key) + def _do(self, funcname, *args, **kwargs): + for elem in self.__elements: + if elem is self: + raise exceptions.AssertionError("ExtensionCarrier set to itself") + ret = getattr(elem, funcname)(*args, **kwargs) + if ret is not EXT_PASS: + return ret + else: + return EXT_PASS + +class ExtensionOption(MapperOption): + def __init__(self, ext): + self.ext = ext + def process_query(self, query): + query._insert_extension(self.ext) + +class ClassKey(object): + """keys a class and an entity name to a mapper, via the mapper_registry.""" + __metaclass__ = util.ArgSingleton + def __init__(self, class_, entity_name): + self.class_ = class_ + self.entity_name = entity_name + def __hash__(self): + return hash((self.class_, self.entity_name)) + def __eq__(self, other): + return self is other + def __repr__(self): + return "ClassKey(%s, %s)" % (repr(self.class_), repr(self.entity_name)) + +def has_identity(object): + return hasattr(object, '_instance_key') + +def has_mapper(object): + """returns True if the given object has a mapper association""" + return hasattr(object, '_entity_name') + + + +def object_mapper(object, raiseerror=True): + """given an object, returns the primary Mapper associated with the object instance""" + try: + mapper = mapper_registry[ClassKey(object.__class__, getattr(object, '_entity_name', None))] + except (KeyError, AttributeError): + if raiseerror: + raise exceptions.InvalidRequestError("Class '%s' entity name '%s' has no mapper associated with it" % (object.__class__.__name__, getattr(object, '_entity_name', None))) + else: + return None + return mapper.compile() + +def class_mapper(class_, entity_name=None, compile=True): + """given a ClassKey, returns the primary Mapper associated with the key.""" + try: + mapper = mapper_registry[ClassKey(class_, entity_name)] + except (KeyError, AttributeError): + raise exceptions.InvalidRequestError("Class '%s' entity name '%s' has no mapper associated with it" % (class_.__name__, entity_name)) + if compile: + return mapper.compile() + else: + return mapper + + diff --git a/spyce-2.1/sqlalchemy/orm/mapper.pyc b/spyce-2.1/sqlalchemy/orm/mapper.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b54ddf15f24b2318fa9b4235368fa2d75cebd34 Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/mapper.pyc differ diff --git a/spyce-2.1/sqlalchemy/orm/properties.py b/spyce-2.1/sqlalchemy/orm/properties.py new file mode 100755 index 0000000000000000000000000000000000000000..6d11436118fe4ba2362a02c783f95d7473026682 --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/properties.py @@ -0,0 +1,317 @@ +# properties.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""defines a set of mapper.MapperProperty objects, including basic column properties as +well as relationships. the objects rely upon the LoaderStrategy objects in the strategies.py +module to handle load operations. PropertyLoader also relies upon the dependency.py module +to handle flush-time dependency sorting and processing.""" + +from sqlalchemy import sql, schema, util, exceptions, sql_util, logging +from sqlalchemy.orm import mapper, sync, strategies, attributes, dependency +from sqlalchemy.orm import session as sessionlib +from sqlalchemy.orm import util as mapperutil +import sets, random +from sqlalchemy.orm.interfaces import * + + +class SynonymProperty(MapperProperty): + def __init__(self, name, proxy=False): + self.name = name + self.proxy = proxy + def setup(self, querycontext, **kwargs): + pass + def execute(self, selectcontext, instance, row, identitykey, isnew): + pass + def do_init(self): + if not self.proxy: + return + class SynonymProp(object): + def __set__(s, obj, value): + setattr(obj, self.name, value) + self.set(None, obj, value) + def __delete__(s, obj): + delattr(obj, self.name) + def __get__(s, obj, owner): + if obj is None: + return s + return getattr(obj, self.name) + setattr(self.parent.class_, self.key, SynonymProp()) + +class ColumnProperty(StrategizedProperty): + """describes an object attribute that corresponds to a table column.""" + def __init__(self, *columns, **kwargs): + """the list of columns describes a single object property. if there + are multiple tables joined together for the mapper, this list represents + the equivalent column as it appears across each table.""" + self.columns = list(columns) + self.group = kwargs.pop('group', None) + self.deferred = kwargs.pop('deferred', False) + def create_strategy(self): + if self.deferred: + return strategies.DeferredColumnLoader(self) + else: + return strategies.ColumnLoader(self) + def getattr(self, object): + return getattr(object, self.key, None) + def setattr(self, object, value): + setattr(object, self.key, value) + def get_history(self, obj, passive=False): + return sessionlib.attribute_manager.get_history(obj, self.key, passive=passive) + +ColumnProperty.logger = logging.class_logger(ColumnProperty) + +mapper.ColumnProperty = ColumnProperty + +class PropertyLoader(StrategizedProperty): + """describes an object property that holds a single item or list of items that correspond + to a related database table.""" + def __init__(self, argument, secondary, primaryjoin, secondaryjoin, foreignkey=None, uselist=None, private=False, association=None, order_by=False, attributeext=None, backref=None, is_backref=False, post_update=False, cascade=None, viewonly=False, lazy=True, collection_class=None, passive_deletes=False): + self.uselist = uselist + self.argument = argument + self.secondary = secondary + self.primaryjoin = primaryjoin + self.secondaryjoin = secondaryjoin + self.post_update = post_update + self.direction = None + self.viewonly = viewonly + self.lazy = lazy + self.foreignkey = util.to_set(foreignkey) + self.collection_class = collection_class + self.passive_deletes = passive_deletes + + if cascade is not None: + self.cascade = mapperutil.CascadeOptions(cascade) + else: + if private: + self.cascade = mapperutil.CascadeOptions("all, delete-orphan") + else: + self.cascade = mapperutil.CascadeOptions("save-update") + + self.association = association + self.order_by = order_by + self.attributeext=attributeext + if isinstance(backref, str): + # propigate explicitly sent primary/secondary join conditions to the BackRef object if + # just a string was sent + if secondary is not None: + # reverse primary/secondary in case of a many-to-many + self.backref = BackRef(backref, primaryjoin=secondaryjoin, secondaryjoin=primaryjoin) + else: + self.backref = BackRef(backref, primaryjoin=primaryjoin, secondaryjoin=secondaryjoin) + else: + self.backref = backref + self.is_backref = is_backref + + private = property(lambda s:s.cascade.delete_orphan) + + def create_strategy(self): + if self.lazy: + return strategies.LazyLoader(self) + elif self.lazy is False: + return strategies.EagerLoader(self) + elif self.lazy is None: + return strategies.NoLoader(self) + + def __str__(self): + return self.__class__.__name__ + " " + str(self.parent) + "->" + self.key + "->" + str(self.mapper) + + def cascade_iterator(self, type, object, recursive): + if not type in self.cascade: + return + passive = type != 'delete' or self.passive_deletes + childlist = sessionlib.attribute_manager.get_history(object, self.key, passive=passive) + if childlist is None: + return + mapper = self.mapper.primary_mapper() + for c in childlist.added_items() + childlist.deleted_items() + childlist.unchanged_items(): + if c is not None and c not in recursive: + if not isinstance(c, self.mapper.class_): + raise exceptions.AssertionError("Attribute '%s' on class '%s' doesn't handle objects of type '%s'" % (self.key, str(self.parent.class_), str(c.__class__))) + recursive.add(c) + yield c + for c2 in mapper.cascade_iterator(type, c, recursive): + yield c2 + + def cascade_callable(self, type, object, callable_, recursive): + if not type in self.cascade: + return + + mapper = self.mapper.primary_mapper() + passive = type != 'delete' or self.passive_deletes + for c in sessionlib.attribute_manager.get_as_list(object, self.key, passive=passive): + if c is not None and c not in recursive: + if not isinstance(c, self.mapper.class_): + raise exceptions.AssertionError("Attribute '%s' on class '%s' doesn't handle objects of type '%s'" % (self.key, str(self.parent.class_), str(c.__class__))) + recursive.add(c) + callable_(c, mapper.entity_name) + mapper.cascade_callable(type, c, callable_, recursive) + + def _get_target_class(self): + """return the target class of the relation, even if the property has not been initialized yet.""" + if isinstance(self.argument, type): + return self.argument + else: + return self.argument.class_ + + def do_init(self): + if isinstance(self.argument, type): + self.mapper = mapper.class_mapper(self.argument, compile=False)._check_compile() + elif isinstance(self.argument, mapper.Mapper): + self.mapper = self.argument._check_compile() + else: + raise exceptions.ArgumentError("relation '%s' expects a class or a mapper argument (received: %s)" % (self.key, type(self.argument))) + + self.mapper = self.mapper.get_select_mapper()._check_compile() + + if self.association is not None: + if isinstance(self.association, type): + self.association = mapper.class_mapper(self.association, compile=False)._check_compile() + + self.target = self.mapper.mapped_table + + if self.cascade.delete_orphan: + if self.parent.class_ is self.mapper.class_: + raise exceptions.ArgumentError("Cant establish 'delete-orphan' cascade rule on a self-referential relationship (attribute '%s' on class '%s'). You probably want cascade='all', which includes delete cascading but not orphan detection." %(self.key, self.parent.class_.__name__)) + self.mapper.primary_mapper().delete_orphans.append((self.key, self.parent.class_)) + + if self.secondaryjoin is not None and self.secondary is None: + raise exceptions.ArgumentError("Property '" + self.key + "' specified with secondary join condition but no secondary argument") + # if join conditions were not specified, figure them out based on foreign keys + try: + if self.secondary is not None: + if self.secondaryjoin is None: + self.secondaryjoin = sql.join(self.mapper.unjoined_table, self.secondary).onclause + if self.primaryjoin is None: + self.primaryjoin = sql.join(self.parent.unjoined_table, self.secondary).onclause + else: + if self.primaryjoin is None: + self.primaryjoin = sql.join(self.parent.unjoined_table, self.target).onclause + except exceptions.ArgumentError, e: + raise exceptions.ArgumentError("Error determining primary and/or secondary join for relationship '%s' between mappers '%s' and '%s'. If the underlying error cannot be corrected, you should specify the 'primaryjoin' (and 'secondaryjoin', if there is an association table present) keyword arguments to the relation() function (or for backrefs, by specifying the backref using the backref() function with keyword arguments) to explicitly specify the join conditions. Nested error is \"%s\"" % (self.key, self.parent, self.mapper, str(e))) + # if the foreign key wasnt specified and theres no assocaition table, try to figure + # out who is dependent on who. we dont need all the foreign keys represented in the join, + # just one of them. + if not len(self.foreignkey) and self.secondaryjoin is None: + # else we usually will have a one-to-many where the secondary depends on the primary + # but its possible that its reversed + self._find_dependent() + + # if we are re-initializing, as in a copy made for an inheriting + # mapper, dont re-evaluate the direction. + if self.direction is None: + self.direction = self._get_direction() + + if self.uselist is None and self.direction == sync.MANYTOONE: + self.uselist = False + + if self.uselist is None: + self.uselist = True + + if not self.viewonly: + self._dependency_processor = dependency.create_dependency_processor(self) + + # primary property handler, set up class attributes + if self.is_primary(): + # if a backref name is defined, set up an extension to populate + # attributes in the other direction + if self.backref is not None: + self.attributeext = self.backref.get_extension() + + if self.backref is not None: + self.backref.compile(self) + elif not sessionlib.attribute_manager.is_class_managed(self.parent.class_, self.key): + raise exceptions.ArgumentError("Attempting to assign a new relation '%s' to a non-primary mapper on class '%s'. New relations can only be added to the primary mapper, i.e. the very first mapper created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__)) + + super(PropertyLoader, self).do_init() + + def _is_self_referential(self): + return self.parent.mapped_table is self.target or self.parent.select_table is self.target + + def _get_direction(self): + """determines our 'direction', i.e. do we represent one to many, many to many, etc.""" + if self.secondaryjoin is not None: + return sync.MANYTOMANY + elif self._is_self_referential(): + # for a self referential mapper, if the "foreignkey" is a single or composite primary key, + # then we are "many to one", since the remote site of the relationship identifies a singular entity. + # otherwise we are "one to many". + for f in self.foreignkey: + if not f.primary_key: + return sync.ONETOMANY + else: + return sync.MANYTOONE + elif len([c for c in self.foreignkey if self.mapper.unjoined_table.corresponding_column(c, False) is not None]): + return sync.ONETOMANY + elif len([c for c in self.foreignkey if self.parent.unjoined_table.corresponding_column(c, False) is not None]): + return sync.MANYTOONE + else: + raise exceptions.ArgumentError("Cant determine relation direction for '%s' in mapper '%s' with primary join\n '%s'" %(self.key, str(self.mapper), str(self.primaryjoin))) + + def _find_dependent(self): + """searches through the primary join condition to determine which side + has the foreign key - from this we return + the "foreign key" for this property which helps determine one-to-many/many-to-one.""" + foreignkeys = util.Set() + def foo(binary): + if binary.operator != '=' or not isinstance(binary.left, schema.Column) or not isinstance(binary.right, schema.Column): + return + for f in binary.left.foreign_keys: + if f.references(binary.right.table): + foreignkeys.add(binary.left) + for f in binary.right.foreign_keys: + if f.references(binary.left.table): + foreignkeys.add(binary.right) + visitor = mapperutil.BinaryVisitor(foo) + self.primaryjoin.accept_visitor(visitor) + if len(foreignkeys) == 0: + raise exceptions.ArgumentError("On relation '%s', can't figure out which side is the foreign key for join condition '%s'. Specify the 'foreignkey' argument to the relation." % (self.key, str(self.primaryjoin))) + self.foreignkey = foreignkeys + + def get_join(self): + if self.secondaryjoin is not None: + return self.primaryjoin & self.secondaryjoin + else: + return self.primaryjoin + + def register_dependencies(self, uowcommit): + if not self.viewonly: + self._dependency_processor.register_dependencies(uowcommit) + +PropertyLoader.logger = logging.class_logger(PropertyLoader) + +class BackRef(object): + """stores the name of a backreference property as well as options to + be used on the resulting PropertyLoader.""" + def __init__(self, key, **kwargs): + self.key = key + self.kwargs = kwargs + def compile(self, prop): + """called by the owning PropertyLoader to set up a backreference on the + PropertyLoader's mapper.""" + # try to set a LazyLoader on our mapper referencing the parent mapper + mapper = prop.mapper.primary_mapper() + if not mapper.props.has_key(self.key): + pj = self.kwargs.pop('primaryjoin', None) + sj = self.kwargs.pop('secondaryjoin', None) + # the backref property is set on the primary mapper + parent = prop.parent.primary_mapper() + relation = PropertyLoader(parent, prop.secondary, pj, sj, backref=prop.key, is_backref=True, **self.kwargs) + mapper._compile_property(self.key, relation); + elif not isinstance(mapper.props[self.key], PropertyLoader): + raise exceptions.ArgumentError("Cant create backref '%s' on mapper '%s'; an incompatible property of that name already exists" % (self.key, str(mapper))) + else: + # else set one of us as the "backreference" + parent = prop.parent.primary_mapper() + if parent.class_ is not mapper.props[self.key]._get_target_class(): + raise exceptions.ArgumentError("Backrefs do not match: backref '%s' expects to connect to %s, but found a backref already connected to %s" % (self.key, str(parent.class_), str(mapper.props[self.key].mapper.class_))) + if not mapper.props[self.key].is_backref: + prop.is_backref=True + if not prop.viewonly: + prop._dependency_processor.is_backref=True + def get_extension(self): + """returns an attribute extension to use with this backreference.""" + return attributes.GenericBackrefExtension(self.key) + diff --git a/spyce-2.1/sqlalchemy/orm/properties.pyc b/spyce-2.1/sqlalchemy/orm/properties.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cec1ae095df8faee268032fe9e4fc63de571888 Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/properties.pyc differ diff --git a/spyce-2.1/sqlalchemy/orm/query.py b/spyce-2.1/sqlalchemy/orm/query.py new file mode 100755 index 0000000000000000000000000000000000000000..6c978f36203932211772d0db9ff86a7c04b97994 --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/query.py @@ -0,0 +1,530 @@ +# orm/query.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from sqlalchemy import sql, util, exceptions, sql_util, logging +from sqlalchemy.orm import mapper +from sqlalchemy.orm.interfaces import OperationContext + +__all__ = ['Query', 'QueryContext', 'SelectionContext'] + +class Query(object): + """encapsulates the object-fetching operations provided by Mappers.""" + def __init__(self, class_or_mapper, session=None, entity_name=None, lockmode=None, with_options=None, extension=None, **kwargs): + if isinstance(class_or_mapper, type): + self.mapper = mapper.class_mapper(class_or_mapper, entity_name=entity_name) + else: + self.mapper = class_or_mapper.compile() + self.with_options = with_options or [] + self.mapper = self.mapper.get_select_mapper().compile() + self.always_refresh = kwargs.pop('always_refresh', self.mapper.always_refresh) + self.order_by = kwargs.pop('order_by', self.mapper.order_by) + self.lockmode = lockmode + self.extension = mapper._ExtensionCarrier() + if extension is not None: + self.extension.append(extension) + self.extension.append(self.mapper.extension) + self._session = session + if not hasattr(self.mapper, '_get_clause'): + _get_clause = sql.and_() + for primary_key in self.mapper.pks_by_table[self.table]: + _get_clause.clauses.append(primary_key == sql.bindparam(primary_key._label, type=primary_key.type)) + self.mapper._get_clause = _get_clause + self._get_clause = self.mapper._get_clause + for opt in self.with_options: + opt.process_query(self) + + def _insert_extension(self, ext): + self.extension.insert(ext) + + def _get_session(self): + if self._session is None: + return self.mapper.get_session() + else: + return self._session + table = property(lambda s:s.mapper.select_table) + session = property(_get_session) + + def get(self, ident, **kwargs): + """return an instance of the object based on the given identifier, or None if not found. + + The ident argument is a scalar or tuple of primary key column values + in the order of the table def's primary key columns.""" + ret = self.extension.get(self, ident, **kwargs) + if ret is not mapper.EXT_PASS: + return ret + key = self.mapper.identity_key(ident) + return self._get(key, ident, **kwargs) + + def load(self, ident, **kwargs): + """return an instance of the object based on the given identifier. + + If not found, raises an exception. The method will *remove all pending changes* to the object + already existing in the Session. The ident argument is a scalar or tuple of primary + key column values in the order of the table def's primary key columns.""" + ret = self.extension.load(self, ident, **kwargs) + if ret is not mapper.EXT_PASS: + return ret + key = self.mapper.identity_key(ident) + instance = self._get(key, ident, reload=True, **kwargs) + if instance is None: + raise exceptions.InvalidRequestError("No instance found for identity %s" % repr(ident)) + return instance + + def get_by(self, *args, **params): + """return a single object instance based on the given key/value criterion. + + this is either the first value in the result list, or None if the list is + empty. + + the keys are mapped to property or column names mapped by this mapper's Table, and the values + are coerced into a WHERE clause separated by AND operators. If the local property/column + names dont contain the key, a search will be performed against this mapper's immediate + list of relations as well, forming the appropriate join conditions if a matching property + is located. + + e.g. u = usermapper.get_by(user_name = 'fred') + """ + ret = self.extension.get_by(self, *args, **params) + if ret is not mapper.EXT_PASS: + return ret + x = self.select_whereclause(self.join_by(*args, **params), limit=1) + if x: + return x[0] + else: + return None + + def select_by(self, *args, **params): + """return an array of object instances based on the given clauses and key/value criterion. + + *args is a list of zero or more ClauseElements which will be connected by AND operators. + + **params is a set of zero or more key/value parameters which are converted into ClauseElements. + the keys are mapped to property or column names mapped by this mapper's Table, and the values + are coerced into a WHERE clause separated by AND operators. If the local property/column + names dont contain the key, a search will be performed against this mapper's immediate + list of relations as well, forming the appropriate join conditions if a matching property + is located. + + e.g. result = usermapper.select_by(user_name = 'fred') + """ + ret = self.extension.select_by(self, *args, **params) + if ret is not mapper.EXT_PASS: + return ret + return self.select_whereclause(self.join_by(*args, **params)) + + def join_by(self, *args, **params): + """return a ClauseElement representing the WHERE clause that would normally be sent to select_whereclause() by select_by().""" + clause = None + for arg in args: + if clause is None: + clause = arg + else: + clause &= arg + + for key, value in params.iteritems(): + (keys, prop) = self._locate_prop(key) + c = (prop.columns[0]==value) & self.join_via(keys) + if clause is None: + clause = c + else: + clause &= c + return clause + + def _locate_prop(self, key, start=None): + import properties + keys = [] + seen = util.Set() + def search_for_prop(mapper_): + if mapper_ in seen: + return None + seen.add(mapper_) + if mapper_.props.has_key(key): + prop = mapper_.props[key] + if isinstance(prop, properties.SynonymProperty): + prop = mapper_.props[prop.name] + if isinstance(prop, properties.PropertyLoader): + keys.insert(0, prop.key) + return prop + else: + for prop in mapper_.props.values(): + if not isinstance(prop, properties.PropertyLoader): + continue + x = search_for_prop(prop.mapper) + if x: + keys.insert(0, prop.key) + return x + else: + return None + p = search_for_prop(start or self.mapper) + if p is None: + raise exceptions.InvalidRequestError("Cant locate property named '%s'" % key) + return [keys, p] + + def join_to(self, key): + """given the key name of a property, will recursively descend through all child properties + from this Query's mapper to locate the property, and will return a ClauseElement + representing a join from this Query's mapper to the endmost mapper.""" + [keys, p] = self._locate_prop(key) + return self.join_via(keys) + + def join_via(self, keys): + """given a list of keys that represents a path from this Query's mapper to a related mapper + based on names of relations from one mapper to the next, returns a + ClauseElement representing a join from this Query's mapper to the endmost mapper. + """ + mapper = self.mapper + clause = None + for key in keys: + prop = mapper.props[key] + if clause is None: + clause = prop.get_join() + else: + clause &= prop.get_join() + mapper = prop.mapper + + return clause + + def selectfirst_by(self, *args, **params): + """works like select_by(), but only returns the first result by itself, or None if no + objects returned. Synonymous with get_by()""" + return self.get_by(*args, **params) + + def selectone_by(self, *args, **params): + """works like selectfirst_by(), but throws an error if not exactly one result was returned.""" + ret = self.select_whereclause(self.join_by(*args, **params), limit=2) + if len(ret) == 1: + return ret[0] + elif len(ret) == 0: + raise exceptions.InvalidRequestError('No rows returned for selectone_by') + else: + raise exceptions.InvalidRequestError('Multiple rows returned for selectone_by') + + def count_by(self, *args, **params): + """returns the count of instances based on the given clauses and key/value criterion. + The criterion is constructed in the same way as the select_by() method.""" + return self.count(self.join_by(*args, **params)) + + def selectfirst(self, *args, **params): + """works like select(), but only returns the first result by itself, or None if no + objects returned.""" + params['limit'] = 1 + ret = self.select_whereclause(*args, **params) + if ret: + return ret[0] + else: + return None + + def selectone(self, *args, **params): + """works like selectfirst(), but throws an error if not exactly one result was returned.""" + ret = list(self.select(*args, **params)[0:2]) + if len(ret) == 1: + return ret[0] + elif len(ret) == 0: + raise exceptions.InvalidRequestError('No rows returned for selectone_by') + else: + raise exceptions.InvalidRequestError('Multiple rows returned for selectone') + + def select(self, arg=None, **kwargs): + """selects instances of the object from the database. + + arg can be any ClauseElement, which will form the criterion with which to + load the objects. + + For more advanced usage, arg can also be a Select statement object, which + will be executed and its resulting rowset used to build new object instances. + in this case, the developer must insure that an adequate set of columns exists in the + rowset with which to build new object instances.""" + + ret = self.extension.select(self, arg=arg, **kwargs) + if ret is not mapper.EXT_PASS: + return ret + try: + s = arg._selectable() + except AttributeError: + return self.select_whereclause(whereclause=arg, **kwargs) + else: + return self.select_statement(s, **kwargs) + + def select_whereclause(self, whereclause=None, params=None, **kwargs): + """given a WHERE criterion, create a SELECT statement, execute and return the resulting instances.""" + statement = self.compile(whereclause, **kwargs) + return self._select_statement(statement, params=params) + + def count(self, whereclause=None, params=None, **kwargs): + """given a WHERE criterion, create a SELECT COUNT statement, execute and return the resulting count value.""" + + from_obj = kwargs.pop('from_obj', []) + alltables = [] + for l in [sql_util.TableFinder(x) for x in from_obj]: + alltables += l + + if self.table not in alltables: + from_obj.append(self.table) + + if self._nestable(**kwargs): + s = sql.select([self.table], whereclause, **kwargs).alias('getcount').count() + else: + primary_key = self.mapper.pks_by_table[self.table] + s = sql.select([sql.func.count(list(primary_key)[0])], whereclause, from_obj=from_obj, **kwargs) + return self.session.scalar(self.mapper, s, params=params) + + def select_statement(self, statement, **params): + """given a ClauseElement-based statement, execute and return the resulting instances.""" + return self._select_statement(statement, params=params) + + def select_text(self, text, **params): + """given a literal string-based statement, execute and return the resulting instances.""" + t = sql.text(text) + return self.execute(t, params=params) + + def options(self, *args, **kwargs): + """return a new Query object, applying the given list of MapperOptions.""" + return Query(self.mapper, self._session, with_options=args) + + def with_lockmode(self, mode): + """return a new Query object with the specified locking mode.""" + return Query(self.mapper, self._session, lockmode=mode) + + def __getattr__(self, key): + if (key.startswith('select_by_')): + key = key[10:] + def foo(arg): + return self.select_by(**{key:arg}) + return foo + elif (key.startswith('get_by_')): + key = key[7:] + def foo(arg): + return self.get_by(**{key:arg}) + return foo + else: + raise AttributeError(key) + + def execute(self, clauseelement, params=None, *args, **kwargs): + """execute the given ClauseElement-based statement against this Query's session/mapper, return the resulting list of instances. + + After execution, closes the ResultProxy and its underlying resources. + This method is one step above the instances() method, which takes the executed statement's ResultProxy directly.""" + result = self.session.execute(self.mapper, clauseelement, params=params) + try: + return self.instances(result, **kwargs) + finally: + result.close() + + def instances(self, cursor, *mappers, **kwargs): + """return a list of mapped instances corresponding to the rows in a given "cursor" (i.e. ResultProxy).""" + self.__log_debug("instances()") + + session = self.session + + context = SelectionContext(self.mapper, session, with_options=self.with_options, **kwargs) + + result = util.UniqueAppender([]) + if mappers: + otherresults = [] + for m in mappers: + otherresults.append(util.UniqueAppender([])) + + for row in cursor.fetchall(): + self.mapper._instance(context, row, result) + i = 0 + for m in mappers: + m._instance(context, row, otherresults[i]) + i+=1 + + # store new stuff in the identity map + for value in context.identity_map.values(): + session._register_persistent(value) + + if mappers: + return [result.data] + [o.data for o in otherresults] + else: + return result.data + + + def _get(self, key, ident=None, reload=False, lockmode=None): + lockmode = lockmode or self.lockmode + if not reload and not self.always_refresh and lockmode is None: + try: + return self.session._get(key) + except KeyError: + pass + + if ident is None: + ident = key[1] + else: + ident = util.to_list(ident) + i = 0 + params = {} + for primary_key in self.mapper.pks_by_table[self.table]: + params[primary_key._label] = ident[i] + # if there are not enough elements in the given identifier, then + # use the previous identifier repeatedly. this is a workaround for the issue + # in [ticket:185], where a mapper that uses joined table inheritance needs to specify + # all primary keys of the joined relationship, which includes even if the join is joining + # two primary key (and therefore synonymous) columns together, the usual case for joined table inheritance. + if len(ident) > i + 1: + i += 1 + try: + statement = self.compile(self._get_clause, lockmode=lockmode) + return self._select_statement(statement, params=params, populate_existing=reload, version_check=(lockmode is not None))[0] + except IndexError: + return None + + def _select_statement(self, statement, params=None, **kwargs): + statement.use_labels = True + if params is None: + params = {} + return self.execute(statement, params=params, **kwargs) + + def _should_nest(self, querycontext): + """return True if the given statement options indicate that we should "nest" the + generated query as a subquery inside of a larger eager-loading query. this is used + with keywords like distinct, limit and offset and the mapper defines eager loads.""" + return ( + len(querycontext.eager_loaders) > 0 + and self._nestable(**querycontext.select_args()) + ) + + def _nestable(self, **kwargs): + """return true if the given statement options imply it should be nested.""" + return (kwargs.get('limit') is not None or kwargs.get('offset') is not None or kwargs.get('distinct', False)) + + def compile(self, whereclause = None, **kwargs): + """given a WHERE criterion, produce a ClauseElement-based statement suitable for usage in the execute() method.""" + context = kwargs.pop('query_context', None) + if context is None: + context = QueryContext(self, kwargs) + order_by = context.order_by + from_obj = context.from_obj + lockmode = context.lockmode + distinct = context.distinct + limit = context.limit + offset = context.offset + if order_by is False: + order_by = self.order_by + if order_by is False: + if self.table.default_order_by() is not None: + order_by = self.table.default_order_by() + + try: + for_update = {'read':'read','update':True,'update_nowait':'nowait',None:False}[lockmode] + except KeyError: + raise exceptions.ArgumentError("Unknown lockmode '%s'" % lockmode) + + if self.mapper.single and self.mapper.polymorphic_on is not None and self.mapper.polymorphic_identity is not None: + whereclause = sql.and_(whereclause, self.mapper.polymorphic_on.in_(*[m.polymorphic_identity for m in self.mapper.polymorphic_iterator()])) + + alltables = [] + for l in [sql_util.TableFinder(x) for x in from_obj]: + alltables += l + + if self.table not in alltables: + from_obj.append(self.table) + + if self._should_nest(context): + # if theres an order by, add those columns to the column list + # of the "rowcount" query we're going to make + if order_by: + order_by = util.to_list(order_by) or [] + cf = sql_util.ColumnFinder() + [o.accept_visitor(cf) for o in order_by] + else: + cf = [] + + s2 = sql.select(self.table.primary_key + list(cf), whereclause, use_labels=True, from_obj=from_obj, **context.select_args()) + if not distinct and order_by: + s2.order_by(*util.to_list(order_by)) + s3 = s2.alias('tbl_row_count') + crit = s3.primary_key==self.table.primary_key + statement = sql.select([], crit, from_obj=[self.table], use_labels=True, for_update=for_update) + # now for the order by, convert the columns to their corresponding columns + # in the "rowcount" query, and tack that new order by onto the "rowcount" query + if order_by: + class Aliasizer(sql_util.Aliasizer): + def get_alias(self, table): + return s3 + order_by = [o.copy_container() for o in order_by] + aliasizer = Aliasizer(*[t for t in sql_util.TableFinder(s3)]) + [o.accept_visitor(aliasizer) for o in order_by] + statement.order_by(*util.to_list(order_by)) + else: + statement = sql.select([], whereclause, from_obj=from_obj, use_labels=True, for_update=for_update, **context.select_args()) + if order_by: + statement.order_by(*util.to_list(order_by)) + # for a DISTINCT query, you need the columns explicitly specified in order + # to use it in "order_by". insure they are in the column criterion (particularly oid). + # TODO: this should be done at the SQL level not the mapper level + if kwargs.get('distinct', False) and order_by: + [statement.append_column(c) for c in util.to_list(order_by)] + + context.statement = statement + # give all the attached properties a chance to modify the query + for value in self.mapper.props.values(): + value.setup(context) + + return statement + + def __log_debug(self, msg): + self.logger.debug(msg) + +Query.logger = logging.class_logger(Query) + +class QueryContext(OperationContext): + """created within the Query.compile() method to store and share + state among all the Mappers and MapperProperty objects used in a query construction.""" + def __init__(self, query, kwargs): + self.query = query + self.order_by = kwargs.pop('order_by', False) + self.from_obj = kwargs.pop('from_obj', []) + self.lockmode = kwargs.pop('lockmode', query.lockmode) + self.distinct = kwargs.pop('distinct', False) + self.limit = kwargs.pop('limit', None) + self.offset = kwargs.pop('offset', None) + self.eager_loaders = util.Set([x for x in query.mapper._eager_loaders]) + self.statement = None + super(QueryContext, self).__init__(query.mapper, query.with_options, **kwargs) + def select_args(self): + """return a dictionary of attributes from this QueryContext that can be applied to a sql.Select statement.""" + return {'limit':self.limit, 'offset':self.offset, 'distinct':self.distinct} + def accept_option(self, opt): + """accept a MapperOption which will process (modify) the state of this QueryContext.""" + opt.process_query_context(self) + + +class SelectionContext(OperationContext): + """created within the query.instances() method to store and share + state among all the Mappers and MapperProperty objects used in a load operation. + + SelectionContext contains these attributes: + + mapper - the Mapper which originated the instances() call. + + session - the Session that is relevant to the instances call. + + identity_map - a dictionary which stores newly created instances that have + not yet been added as persistent to the Session. + + attributes - a dictionary to store arbitrary data; eager loaders use it to + store additional result lists + + populate_existing - indicates if its OK to overwrite the attributes of instances + that were already in the Session + + version_check - indicates if mappers that have version_id columns should verify + that instances existing already within the Session should have this attribute compared + to the freshly loaded value + + """ + def __init__(self, mapper, session, **kwargs): + self.populate_existing = kwargs.pop('populate_existing', False) + self.version_check = kwargs.pop('version_check', False) + self.session = session + self.identity_map = {} + super(SelectionContext, self).__init__(mapper, kwargs.pop('with_options', []), **kwargs) + def accept_option(self, opt): + """accept a MapperOption which will process (modify) the state of this SelectionContext.""" + opt.process_selection_context(self) + \ No newline at end of file diff --git a/spyce-2.1/sqlalchemy/orm/query.pyc b/spyce-2.1/sqlalchemy/orm/query.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2145ba2ad613138f2ce3600ba7419da9c38c58af Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/query.pyc differ diff --git a/spyce-2.1/sqlalchemy/orm/session.py b/spyce-2.1/sqlalchemy/orm/session.py new file mode 100755 index 0000000000000000000000000000000000000000..921449a6af845c4c3174d3ec48c3de2c1137fbbf --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/session.py @@ -0,0 +1,454 @@ +# objectstore.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from sqlalchemy import util, exceptions, sql +from sqlalchemy.orm import unitofwork, query +from sqlalchemy.orm.mapper import object_mapper as _object_mapper +from sqlalchemy.orm.mapper import class_mapper as _class_mapper +import weakref +import sqlalchemy + + +class SessionTransaction(object): + """represents a Session-level Transaction. This corresponds to one or + more sqlalchemy.engine.Transaction instances behind the scenes, with one + Transaction per Engine in use. + + the SessionTransaction object is **not** threadsafe.""" + def __init__(self, session, parent=None, autoflush=True): + self.session = session + self.connections = {} + self.parent = parent + self.autoflush = autoflush + def connection(self, mapper_or_class, entity_name=None): + if isinstance(mapper_or_class, type): + mapper_or_class = _class_mapper(mapper_or_class, entity_name=entity_name) + if self.parent is not None: + return self.parent.connection(mapper_or_class) + engine = self.session.get_bind(mapper_or_class) + return self.get_or_add(engine) + def _begin(self): + return SessionTransaction(self.session, self) + def add(self, connectable): + if self.connections.has_key(connectable.engine): + raise exceptions.InvalidRequestError("Session already has a Connection associated for the given Connection's Engine") + return self.get_or_add(connectable) + def get_or_add(self, connectable): + # we reference the 'engine' attribute on the given object, which in the case of + # Connection, ProxyEngine, Engine, whatever, should return the original + # "Engine" object that is handling the connection. + if self.connections.has_key(connectable.engine): + return self.connections[connectable.engine][0] + e = connectable.engine + c = connectable.contextual_connect() + if not self.connections.has_key(e): + self.connections[e] = (c, c.begin(), c is not connectable) + return self.connections[e][0] + def commit(self): + if self.parent is not None: + return + if self.autoflush: + self.session.flush() + for t in self.connections.values(): + t[1].commit() + self.close() + def rollback(self): + if self.parent is not None: + self.parent.rollback() + return + for k, t in self.connections.iteritems(): + t[1].rollback() + self.close() + def close(self): + if self.parent is not None: + return + for t in self.connections.values(): + if t[2]: + t[0].close() + self.session.transaction = None + +class Session(object): + """encapsulates a set of objects being operated upon within an object-relational operation. + + The Session object is **not** threadsafe. For thread-management of Sessions, see the + sqlalchemy.ext.sessioncontext module.""" + def __init__(self, bind_to=None, hash_key=None, import_session=None, echo_uow=False): + if import_session is not None: + self.uow = unitofwork.UnitOfWork(identity_map=import_session.uow.identity_map) + else: + self.uow = unitofwork.UnitOfWork() + + self.bind_to = bind_to + self.binds = {} + self.echo_uow = echo_uow + self.transaction = None + if hash_key is None: + self.hash_key = id(self) + else: + self.hash_key = hash_key + _sessions[self.hash_key] = self + + def _get_echo_uow(self): + return self.uow.echo + def _set_echo_uow(self, value): + self.uow.echo = value + echo_uow = property(_get_echo_uow,_set_echo_uow) + + def create_transaction(self, **kwargs): + """returns a new SessionTransaction corresponding to an existing or new transaction. + if the transaction is new, the returned SessionTransaction will have commit control + over the underlying transaction, else will have rollback control only.""" + if self.transaction is not None: + return self.transaction._begin() + else: + self.transaction = SessionTransaction(self, **kwargs) + return self.transaction + def connect(self, mapper=None, **kwargs): + """returns a unique connection corresponding to the given mapper. this connection + will not be part of any pre-existing transactional context.""" + return self.get_bind(mapper).connect(**kwargs) + def connection(self, mapper, **kwargs): + """returns a Connection corresponding to the given mapper. used by the execute() + method which performs select operations for Mapper and Query. + if this Session is transactional, + the connection will be in the context of this session's transaction. otherwise, the connection + is returned by the contextual_connect method, which some Engines override to return a thread-local + connection, and will have close_with_result set to True. + + the given **kwargs will be sent to the engine's contextual_connect() method, if no transaction is in progress.""" + if self.transaction is not None: + return self.transaction.connection(mapper) + else: + return self.get_bind(mapper).contextual_connect(**kwargs) + def execute(self, mapper, clause, params, **kwargs): + """using the given mapper to identify the appropriate Engine or Connection to be used for statement execution, + executes the given ClauseElement using the provided parameter dictionary. Returns a ResultProxy corresponding + to the execution's results. If this method allocates a new Connection for the operation, then the ResultProxy's close() + method will release the resources of the underlying Connection, otherwise its a no-op. + """ + return self.connection(mapper, close_with_result=True).execute(clause, params, **kwargs) + def scalar(self, mapper, clause, params, **kwargs): + """works like execute() but returns a scalar result.""" + return self.connection(mapper, close_with_result=True).scalar(clause, params, **kwargs) + + def close(self): + """closes this Session. + """ + self.clear() + if self.transaction is not None: + self.transaction.close() + + def clear(self): + """removes all object instances from this Session. this is equivalent to calling expunge() for all + objects in this Session.""" + for instance in self: + self._unattach(instance) + echo = self.uow.echo + self.uow = unitofwork.UnitOfWork() + self.uow.echo = echo + + def mapper(self, class_, entity_name=None): + """given an Class, return the primary Mapper responsible for persisting it""" + return _class_mapper(class_, entity_name = entity_name) + def bind_mapper(self, mapper, bindto): + """bind the given Mapper to the given Engine or Connection. + + All subsequent operations involving this Mapper will use the given bindto.""" + self.binds[mapper] = bindto + def bind_table(self, table, bindto): + """bind the given Table to the given Engine or Connection. + + All subsequent operations involving this Table will use the given bindto.""" + self.binds[table] = bindto + def get_bind(self, mapper): + """return the Engine or Connection which is used to execute statements on behalf of the given Mapper. + + Calling connect() on the return result will always result in a Connection object. This method + disregards any SessionTransaction that may be in progress. + + The order of searching is as follows: + + if an Engine or Connection was bound to this Mapper specifically within this Session, returns that + Engine or Connection. + + if an Engine or Connection was bound to this Mapper's underlying Table within this Session + (i.e. not to the Table directly), returns that Engine or Conneciton. + + if an Engine or Connection was bound to this Session, returns that Engine or Connection. + + finally, returns the Engine which was bound directly to the Table's MetaData object. + + If no Engine is bound to the Table, an exception is raised. + """ + if mapper is None: + return self.bind_to + elif self.binds.has_key(mapper): + return self.binds[mapper] + elif self.binds.has_key(mapper.mapped_table): + return self.binds[mapper.mapped_table] + elif self.bind_to is not None: + return self.bind_to + else: + e = mapper.mapped_table.engine + if e is None: + raise exceptions.InvalidRequestError("Could not locate any Engine bound to mapper '%s'" % str(mapper)) + return e + def query(self, mapper_or_class, entity_name=None, **kwargs): + """return a new Query object corresponding to this Session and the mapper, or the classes' primary mapper.""" + if isinstance(mapper_or_class, type): + return query.Query(_class_mapper(mapper_or_class, entity_name=entity_name), self, **kwargs) + else: + return query.Query(mapper_or_class, self, **kwargs) + def _sql(self): + class SQLProxy(object): + def __getattr__(self, key): + def call(*args, **kwargs): + kwargs[engine] = self.engine + return getattr(sql, key)(*args, **kwargs) + + sql = property(_sql) + + def flush(self, objects=None): + """flush all the object modifications present in this session to the database. + + 'objects' is a list or tuple of objects specifically to be flushed; if None, all + new and modified objects are flushed.""" + self.uow.flush(self, objects) + + def get(self, class_, ident, **kwargs): + """return an instance of the object based on the given identifier, or None if not found. + + The ident argument is a scalar or tuple of primary key column values in the order of the + table def's primary key columns. + + the entity_name keyword argument may also be specified which further qualifies the underlying + Mapper used to perform the query.""" + entity_name = kwargs.get('entity_name', None) + return self.query(class_, entity_name=entity_name).get(ident) + + def load(self, class_, ident, **kwargs): + """return an instance of the object based on the given identifier. + + If not found, raises an exception. The method will *remove all pending changes* to the object + already existing in the Session. The ident argument is a scalar or tuple of + primary key columns in the order of the table def's primary key columns. + + the entity_name keyword argument may also be specified which further qualifies the underlying + Mapper used to perform the query.""" + entity_name = kwargs.get('entity_name', None) + return self.query(class_, entity_name=entity_name).load(ident) + + def refresh(self, obj): + """reload the attributes for the given object from the database, clear any changes made.""" + self._validate_persistent(obj) + if self.query(obj.__class__)._get(obj._instance_key, reload=True) is None: + raise exceptions.InvalidRequestError("Could not refresh instance '%s'" % repr(obj)) + + def expire(self, obj): + """mark the given object as expired. + + this will add an instrumentation to all mapped attributes on the instance such that when + an attribute is next accessed, the session will reload all attributes on the instance + from the database. + """ + self._validate_persistent(obj) + def exp(): + if self.query(obj.__class__)._get(obj._instance_key, reload=True) is None: + raise exceptions.InvalidRequestError("Could not refresh instance '%s'" % repr(obj)) + attribute_manager.trigger_history(obj, exp) + + def is_expired(self, obj, unexpire=False): + """return True if the given object has been marked as expired.""" + ret = attribute_manager.has_trigger(obj) + if ret and unexpire: + attribute_manager.untrigger_history(obj) + return ret + + def expunge(self, object): + """remove the given object from this Session. + + this will free all internal references to the object. cascading will be applied according to the + 'expunge' cascade rule.""" + for c in [object] + list(_object_mapper(object).cascade_iterator('expunge', object)): + self.uow._remove_deleted(c) + self._unattach(c) + + def save(self, object, entity_name=None): + """ + Add a transient (unsaved) instance to this Session. + + This operation cascades the "save_or_update" method to associated instances if the + relation is mapped with cascade="save-update". + + The 'entity_name' keyword argument will further qualify the specific Mapper used to handle this + instance. + """ + self._save_impl(object, entity_name=entity_name) + _object_mapper(object).cascade_callable('save-update', object, lambda c, e:self._save_or_update_impl(c, e)) + + def update(self, object, entity_name=None): + """Bring the given detached (saved) instance into this Session. + + If there is a persistent instance with the same identifier already associated + with this Session, an exception is thrown. + + This operation cascades the "save_or_update" method to associated instances if the relation is mapped + with cascade="save-update".""" + self._update_impl(object, entity_name=entity_name) + _object_mapper(object).cascade_callable('save-update', object, lambda c, e:self._save_or_update_impl(c, e)) + + def save_or_update(self, object, entity_name=None): + """save or update the given object into this Session. + + The presence of an '_instance_key' attribute on the instance determines whether to + save() or update() the instance.""" + self._save_or_update_impl(object, entity_name=entity_name) + _object_mapper(object).cascade_callable('save-update', object, lambda c, e:self._save_or_update_impl(c, e)) + + def _save_or_update_impl(self, object, entity_name=None): + key = getattr(object, '_instance_key', None) + if key is None: + self._save_impl(object, entity_name=entity_name) + else: + self._update_impl(object, entity_name=entity_name) + + def delete(self, object, entity_name=None): + """mark the given instance as deleted. + + the delete operation occurs upon flush().""" + for c in [object] + list(_object_mapper(object).cascade_iterator('delete', object)): + self.uow.register_deleted(c) + + def merge(self, object, entity_name=None): + """merge the object into a newly loaded or existing instance from this Session. + + note: this method is currently not completely implemented.""" + instance = None + for obj in [object] + list(_object_mapper(object).cascade_iterator('merge', object)): + key = getattr(obj, '_instance_key', None) + if key is None: + mapper = _object_mapper(object) + ident = mapper.identity(object) + for k in ident: + if k is None: + raise exceptions.InvalidRequestError("Instance '%s' does not have a full set of identity values, and does not represent a saved entity in the database. Use the add() method to add unsaved instances to this Session." % repr(obj)) + key = mapper.identity_key(ident) + u = self.uow + if u.identity_map.has_key(key): + # TODO: copy the state of the given object into this one. tricky ! + inst = u.identity_map[key] + else: + inst = self.get(object.__class__, key[1]) + if obj is object: + instance = inst + + return instance + + def _save_impl(self, object, **kwargs): + if hasattr(object, '_instance_key'): + if not self.identity_map.has_key(object._instance_key): + raise exceptions.InvalidRequestError("Instance '%s' is a detached instance or is already persistent in a different Session" % repr(object)) + else: + m = _class_mapper(object.__class__, entity_name=kwargs.get('entity_name', None)) + + # this would be a nice exception to raise...however this is incompatible with a contextual + # session which puts all objects into the session upon construction. + #if m._is_orphan(object): + # raise exceptions.InvalidRequestError("Instance '%s' is an orphan, and must be attached to a parent object to be saved" % (repr(object))) + + m._assign_entity_name(object) + self._register_pending(object) + + def _update_impl(self, object, **kwargs): + if self._is_attached(object) and object not in self.deleted: + return + if not hasattr(object, '_instance_key'): + raise exceptions.InvalidRequestError("Instance '%s' is not persisted" % repr(object)) + self._register_persistent(object) + + def _register_pending(self, obj): + self._attach(obj) + self.uow.register_new(obj) + def _register_persistent(self, obj): + self._attach(obj) + self.uow.register_clean(obj) + def _register_deleted(self, obj): + self._attach(obj) + self.uow.register_deleted(obj) + + def _attach(self, obj): + """Attach the given object to this Session.""" + if getattr(obj, '_sa_session_id', None) != self.hash_key: + old = getattr(obj, '_sa_session_id', None) + if old is not None and _sessions.has_key(old): + raise exceptions.InvalidRequestError("Object '%s' is already attached to session '%s' (this is '%s')" % (repr(obj), old, id(self))) + + # auto-removal from the old session is disabled. but if we decide to + # turn it back on, do it as below: gingerly since _sessions is a WeakValueDict + # and it might be affected by other threads + #try: + # sess = _sessions[old] + #except KeyError: + # sess = None + #if sess is not None: + # sess.expunge(old) + key = getattr(obj, '_instance_key', None) + if key is not None: + self.identity_map[key] = obj + obj._sa_session_id = self.hash_key + + def _unattach(self, obj): + self._validate_attached(obj) + del obj._sa_session_id + + def _validate_attached(self, obj): + """validate that the given object is either pending or persistent within this Session.""" + if not self._is_attached(obj): + raise exceptions.InvalidRequestError("Instance '%s' not attached to this Session" % repr(obj)) + def _validate_persistent(self, obj): + """validate that the given object is persistent within this Session.""" + self.uow._validate_obj(obj) + def _is_attached(self, obj): + return getattr(obj, '_sa_session_id', None) == self.hash_key + def __contains__(self, obj): + return self._is_attached(obj) and (obj in self.uow.new or self.identity_map.has_key(obj._instance_key)) + def __iter__(self): + return iter(list(self.uow.new) + self.uow.identity_map.values()) + def _get(self, key): + return self.identity_map[key] + def has_key(self, key): + return self.identity_map.has_key(key) + + dirty = property(lambda s:s.uow.locate_dirty(), doc="a Set of all objects marked as 'dirty' within this Session") + deleted = property(lambda s:s.uow.deleted, doc="a Set of all objects marked as 'deleted' within this Session") + new = property(lambda s:s.uow.new, doc="a Set of all objects marked as 'new' within this Session.") + identity_map = property(lambda s:s.uow.identity_map, doc="a WeakValueDictionary consisting of all objects within this Session keyed to their _instance_key value.") + + def import_instance(self, *args, **kwargs): + """deprecated; a synynom for merge()""" + return self.merge(*args, **kwargs) + + +# this is the AttributeManager instance used to provide attribute behavior on objects. +# to all the "global variable police" out there: its a stateless object. +attribute_manager = unitofwork.attribute_manager + +# this dictionary maps the hash key of a Session to the Session itself, and +# acts as a Registry with which to locate Sessions. this is to enable +# object instances to be associated with Sessions without having to attach the +# actual Session object directly to the object instance. +_sessions = weakref.WeakValueDictionary() + +def object_session(obj): + """return the Session to which the given object is bound, or None if none.""" + hashkey = getattr(obj, '_sa_session_id', None) + if hashkey is not None: + return _sessions.get(hashkey) + return None + +unitofwork.object_session = object_session +from sqlalchemy.orm import mapper +mapper.attribute_manager = attribute_manager \ No newline at end of file diff --git a/spyce-2.1/sqlalchemy/orm/session.pyc b/spyce-2.1/sqlalchemy/orm/session.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6cb78e7bfa705cb4171c3fe1c98a772d6e778c5f Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/session.pyc differ diff --git a/spyce-2.1/sqlalchemy/orm/strategies.py b/spyce-2.1/sqlalchemy/orm/strategies.py new file mode 100755 index 0000000000000000000000000000000000000000..c4b75f64441b4ebeb4d41385fe1460be5058abc4 --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/strategies.py @@ -0,0 +1,544 @@ +# strategies.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""sqlalchemy.orm.interfaces.LoaderStrategy implementations, and related MapperOptions.""" + +from sqlalchemy import sql, schema, util, exceptions, sql_util, logging +from sqlalchemy.orm import mapper, query +from sqlalchemy.orm.interfaces import * +from sqlalchemy.orm import session as sessionlib +from sqlalchemy.orm import util as mapperutil +import sets, random + + +class ColumnLoader(LoaderStrategy): + def init(self): + super(ColumnLoader, self).init() + self.columns = self.parent_property.columns + def setup_query(self, context, eagertable=None, **kwargs): + for c in self.columns: + if eagertable is not None: + context.statement.append_column(eagertable.corresponding_column(c)) + else: + context.statement.append_column(c) + + def init_class_attribute(self): + self.logger.info("register managed attribute %s on class %s" % (self.key, self.parent.class_.__name__)) + sessionlib.attribute_manager.register_attribute(self.parent.class_, self.key, uselist=False, copy_function=lambda x: self.columns[0].type.copy_value(x), compare_function=lambda x,y:self.columns[0].type.compare_values(x,y), mutable_scalars=self.columns[0].type.is_mutable()) + + def process_row(self, selectcontext, instance, row, identitykey, isnew): + if isnew: + self.logger.debug("populating %s with %s/%s" % (mapperutil.attribute_str(instance, self.key), row.__class__.__name__, self.columns[0].key)) + instance.__dict__[self.key] = row[self.columns[0]] + +ColumnLoader.logger = logging.class_logger(ColumnLoader) + +class DeferredColumnLoader(LoaderStrategy): + """describes an object attribute that corresponds to a table column, which also + will "lazy load" its value from the table. this is per-column lazy loading.""" + def init(self): + super(DeferredColumnLoader, self).init() + self.columns = self.parent_property.columns + self.group = self.parent_property.group + + def init_class_attribute(self): + self.logger.info("register managed attribute %s on class %s" % (self.key, self.parent.class_.__name__)) + sessionlib.attribute_manager.register_attribute(self.parent.class_, self.key, uselist=False, callable_=lambda i:self.setup_loader(i), copy_function=lambda x: self.columns[0].type.copy_value(x), compare_function=lambda x,y:self.columns[0].type.compare_values(x,y), mutable_scalars=self.columns[0].type.is_mutable()) + + def setup_query(self, context, **kwargs): + pass + + def process_row(self, selectcontext, instance, row, identitykey, isnew): + if isnew: + if not self.is_default or len(selectcontext.options): + sessionlib.attribute_manager.init_instance_attribute(instance, self.key, False, callable_=self.setup_loader(instance, selectcontext.options)) + else: + sessionlib.attribute_manager.reset_instance_attribute(instance, self.key) + + def setup_loader(self, instance, options=None): + if not mapper.has_mapper(instance): + return None + else: + prop = mapper.object_mapper(instance).props[self.key] + if prop is not self.parent_property: + return prop._get_strategy(DeferredColumnLoader).setup_loader(instance) + def lazyload(): + self.logger.debug("deferred load %s group %s" % (mapperutil.attribute_str(instance, self.key), str(self.group))) + try: + pk = self.parent.pks_by_table[self.columns[0].table] + except KeyError: + pk = self.columns[0].table.primary_key + + clause = sql.and_() + for primary_key in pk: + attr = self.parent.get_attr_by_column(instance, primary_key) + if not attr: + return None + clause.clauses.append(primary_key == attr) + + session = sessionlib.object_session(instance) + if session is None: + raise exceptions.InvalidRequestError("Parent instance %s is not bound to a Session; deferred load operation of attribute '%s' cannot proceed" % (instance.__class__, self.key)) + + localparent = mapper.object_mapper(instance) + if self.group is not None: + groupcols = [p for p in localparent.props.values() if isinstance(p.strategy, DeferredColumnLoader) and p.group==self.group] + result = session.execute(localparent, sql.select([g.columns[0] for g in groupcols], clause, use_labels=True), None) + try: + row = result.fetchone() + for prop in groupcols: + if prop is self: + continue + # set a scalar object instance directly on the object, + # bypassing SmartProperty event handlers. + sessionlib.attribute_manager.init_instance_attribute(instance, prop.key, uselist=False) + instance.__dict__[prop.key] = row[prop.columns[0]] + return row[self.columns[0]] + finally: + result.close() + else: + return session.scalar(localparent, sql.select([self.columns[0]], clause, use_labels=True),None) + + return lazyload + +DeferredColumnLoader.logger = logging.class_logger(DeferredColumnLoader) + +class DeferredOption(StrategizedOption): + def __init__(self, key, defer=False): + super(DeferredOption, self).__init__(key) + self.defer = defer + def get_strategy_class(self): + if self.defer: + return DeferredColumnLoader + else: + return ColumnLoader + +class AbstractRelationLoader(LoaderStrategy): + def init(self): + super(AbstractRelationLoader, self).init() + self.primaryjoin = self.parent_property.primaryjoin + self.secondaryjoin = self.parent_property.secondaryjoin + self.secondary = self.parent_property.secondary + self.foreignkey = self.parent_property.foreignkey + self.mapper = self.parent_property.mapper + self.target = self.parent_property.target + self.uselist = self.parent_property.uselist + self.cascade = self.parent_property.cascade + self.attributeext = self.parent_property.attributeext + self.order_by = self.parent_property.order_by + + def _init_instance_attribute(self, instance, callable_=None): + return sessionlib.attribute_manager.init_instance_attribute(instance, self.key, self.uselist, cascade=self.cascade, trackparent=True, callable_=callable_) + + def _register_attribute(self, class_, callable_=None): + self.logger.info("register managed %s attribute %s on class %s" % ((self.uselist and "list-holding" or "scalar"), self.key, self.parent.class_.__name__)) + sessionlib.attribute_manager.register_attribute(class_, self.key, uselist = self.uselist, extension=self.attributeext, cascade=self.cascade, trackparent=True, typecallable=self.parent_property.collection_class, callable_=callable_) + +class NoLoader(AbstractRelationLoader): + def init_class_attribute(self): + self.parent_property._get_strategy(LazyLoader).init_class_attribute() + def process_row(self, selectcontext, instance, row, identitykey, isnew): + if isnew: + if not self.is_default or len(selectcontext.options): + self.logger.debug("set instance-level no loader on %s" % mapperutil.attribute_str(instance, self.key)) + self._init_instance_attribute(instance) + +NoLoader.logger = logging.class_logger(NoLoader) + +class LazyLoader(AbstractRelationLoader): + def init(self): + super(LazyLoader, self).init() + (self.lazywhere, self.lazybinds, self.lazyreverse) = self._create_lazy_clause(self.parent.unjoined_table, self.primaryjoin, self.secondaryjoin, self.foreignkey) + # determine if our "lazywhere" clause is the same as the mapper's + # get() clause. then we can just use mapper.get() + self.use_get = not self.uselist and query.Query(self.mapper)._get_clause.compare(self.lazywhere) + + def init_class_attribute(self): + self._register_attribute(self.parent.class_, callable_=lambda i: self.setup_loader(i)) + + def setup_loader(self, instance, options=None): + if not mapper.has_mapper(instance): + return None + else: + prop = mapper.object_mapper(instance).props[self.key] + if prop is not self.parent_property: + return prop._get_strategy(LazyLoader).setup_loader(instance) + def lazyload(): + self.logger.debug("lazy load attribute %s on instance %s" % (self.key, mapperutil.instance_str(instance))) + params = {} + allparams = True + # if the instance wasnt loaded from the database, then it cannot lazy load + # child items. one reason for this is that a bi-directional relationship + # will not update properly, since bi-directional uses lazy loading functions + # in both directions, and this instance will not be present in the lazily-loaded + # results of the other objects since its not in the database + if not mapper.has_identity(instance): + return None + #print "setting up loader, lazywhere", str(self.lazywhere), "binds", self.lazybinds + for col, bind in self.lazybinds.iteritems(): + params[bind.key] = self.parent.get_attr_by_column(instance, col) + if params[bind.key] is None: + allparams = False + break + + if not allparams: + return None + + session = sessionlib.object_session(instance) + if session is None: + try: + session = mapper.object_mapper(instance).get_session() + except exceptions.InvalidRequestError: + raise exceptions.InvalidRequestError("Parent instance %s is not bound to a Session, and no contextual session is established; lazy load operation of attribute '%s' cannot proceed" % (instance.__class__, self.key)) + + # if we have a simple straight-primary key load, use mapper.get() + # to possibly save a DB round trip + if self.use_get: + ident = [] + for primary_key in self.mapper.pks_by_table[self.mapper.mapped_table]: + bind = self.lazyreverse[primary_key] + ident.append(params[bind.key]) + return session.query(self.mapper).get(ident) + elif self.order_by is not False: + order_by = self.order_by + elif self.secondary is not None and self.secondary.default_order_by() is not None: + order_by = self.secondary.default_order_by() + else: + order_by = False + result = session.query(self.mapper, with_options=options).select_whereclause(self.lazywhere, order_by=order_by, params=params) + + if self.uselist: + return result + else: + if len(result): + return result[0] + else: + return None + return lazyload + + def process_row(self, selectcontext, instance, row, identitykey, isnew): + if isnew: + # new object instance being loaded from a result row + if not self.is_default or len(selectcontext.options): + self.logger.debug("set instance-level lazy loader on %s" % mapperutil.attribute_str(instance, self.key)) + # we are not the primary manager for this attribute on this class - set up a per-instance lazyloader, + # which will override the clareset_instance_attributess-level behavior + self._init_instance_attribute(instance, callable_=self.setup_loader(instance, selectcontext.options)) + else: + self.logger.debug("set class-level lazy loader on %s" % mapperutil.attribute_str(instance, self.key)) + # we are the primary manager for this attribute on this class - reset its per-instance attribute state, + # so that the class-level lazy loader is executed when next referenced on this instance. + # this usually is not needed unless the constructor of the object referenced the attribute before we got + # to load data into it. + sessionlib.attribute_manager.reset_instance_attribute(instance, self.key) + + def _create_lazy_clause(self, table, primaryjoin, secondaryjoin, foreignkey): + binds = {} + reverse = {} + def column_in_table(table, column): + return table.corresponding_column(column, raiseerr=False, keys_ok=False) is not None + + def find_column_in_expr(expr): + if not isinstance(expr, sql.ColumnElement): + return None + columns = [] + class FindColumnInColumnClause(sql.ClauseVisitor): + def visit_column(self, c): + columns.append(c) + expr.accept_visitor(FindColumnInColumnClause()) + return len(columns) and columns[0] or None + + def bind_label(): + return "lazy_" + hex(random.randint(0, 65535))[2:] + def visit_binary(binary): + leftcol = find_column_in_expr(binary.left) + rightcol = find_column_in_expr(binary.right) + if leftcol is None or rightcol is None: + return + circular = leftcol.table is rightcol.table + if ((not circular and column_in_table(table, leftcol)) or (circular and rightcol in foreignkey)): + col = leftcol + binary.left = binds.setdefault(leftcol, + sql.bindparam(bind_label(), None, shortname=leftcol.name, type=binary.right.type)) + reverse[rightcol] = binds[col] + + if (leftcol is not rightcol) and ((not circular and column_in_table(table, rightcol)) or (circular and leftcol in foreignkey)): + col = rightcol + binary.right = binds.setdefault(rightcol, + sql.bindparam(bind_label(), None, shortname=rightcol.name, type=binary.left.type)) + reverse[leftcol] = binds[col] + + lazywhere = primaryjoin.copy_container() + li = mapperutil.BinaryVisitor(visit_binary) + lazywhere.accept_visitor(li) + if secondaryjoin is not None: + lazywhere = sql.and_(lazywhere, secondaryjoin) + LazyLoader.logger.debug("create_lazy_clause " + str(lazywhere)) + return (lazywhere, binds, reverse) + +LazyLoader.logger = logging.class_logger(LazyLoader) + + + +class EagerLoader(AbstractRelationLoader): + """loads related objects inline with a parent query.""" + def init(self): + super(EagerLoader, self).init() + if self.parent.isa(self.mapper): + raise exceptions.ArgumentError("Error creating eager relationship '%s' on parent class '%s' to child class '%s': Cant use eager loading on a self referential relationship." % (self.key, repr(self.parent.class_), repr(self.mapper.class_))) + self.parent._eager_loaders.add(self.parent_property) + + self.clauses = {} + self.clauses_by_lead_mapper = {} + + class AliasedClauses(object): + """defines a set of join conditions and table aliases which are aliased on a randomly-generated + alias name, corresponding to the connection of an optional parent AliasedClauses object and a + target mapper. + + EagerLoader has a distinct AliasedClauses object per parent AliasedClauses object, + so that all paths from one mapper to another across a chain of eagerloaders generates a distinct + chain of joins. The AliasedClauses objects are generated and cached on an as-needed basis. + + e.g.: + + mapper A --> + (EagerLoader 'items') --> + mapper B --> + (EagerLoader 'keywords') --> + mapper C + + will generate: + + EagerLoader 'items' --> { + None : AliasedClauses(items, None, alias_suffix='AB34') # mappera JOIN mapperb_AB34 + } + + EagerLoader 'keywords' --> [ + None : AliasedClauses(keywords, None, alias_suffix='43EF') # mapperb JOIN mapperc_43EF + AliasedClauses(items, None, alias_suffix='AB34') : + AliasedClauses(keywords, items, alias_suffix='8F44') # mapperb_AB34 JOIN mapperc_8F44 + ] + """ + def __init__(self, eagerloader, parentclauses=None): + self.parent = eagerloader + self.target = eagerloader.target + self.eagertarget = eagerloader.target.alias() + if eagerloader.secondary: + self.eagersecondary = eagerloader.secondary.alias() + self.aliasizer = sql_util.Aliasizer(eagerloader.target, eagerloader.secondary, aliases={ + eagerloader.target:self.eagertarget, + eagerloader.secondary:self.eagersecondary + }) + self.eagersecondaryjoin = eagerloader.secondaryjoin.copy_container() + self.eagersecondaryjoin.accept_visitor(self.aliasizer) + self.eagerprimary = eagerloader.primaryjoin.copy_container() + self.eagerprimary.accept_visitor(self.aliasizer) + else: + self.aliasizer = sql_util.Aliasizer(eagerloader.target, aliases={eagerloader.target:self.eagertarget}) + self.eagerprimary = eagerloader.primaryjoin.copy_container() + self.eagerprimary.accept_visitor(self.aliasizer) + + if parentclauses is not None: + self.eagerprimary.accept_visitor(parentclauses.aliasizer) + + if eagerloader.order_by: + self.eager_order_by = self._aliasize_orderby(eagerloader.order_by) + else: + self.eager_order_by = None + + self._row_decorator = self._create_decorator_row() + + def _aliasize_orderby(self, orderby, copy=True): + if copy: + orderby = [o.copy_container() for o in util.to_list(orderby)] + else: + orderby = util.to_list(orderby) + for i in range(0, len(orderby)): + if isinstance(orderby[i], schema.Column): + orderby[i] = self.eagertarget.corresponding_column(orderby[i]) + else: + orderby[i].accept_visitor(self.aliasizer) + return orderby + + def _create_decorator_row(self): + class EagerRowAdapter(object): + def __init__(self, row): + self.row = row + def has_key(self, key): + return map.has_key(key) or self.row.has_key(key) + def __getitem__(self, key): + if map.has_key(key): + key = map[key] + return self.row[key] + def keys(self): + return map.keys() + map = {} + for c in self.eagertarget.c: + parent = self.target.corresponding_column(c) + map[parent] = c + map[parent._label] = c + map[parent.name] = c + return EagerRowAdapter + + def _decorate_row(self, row): + # adapts a row at row iteration time to transparently + # convert plain columns into the aliased columns that were actually + # added to the column clause of the SELECT. + return self._row_decorator(row) + + def init_class_attribute(self): + self.parent_property._get_strategy(LazyLoader).init_class_attribute() + + def setup_query(self, context, eagertable=None, parentclauses=None, parentmapper=None, **kwargs): + """add a left outer join to the statement thats being constructed""" + if parentmapper is None: + localparent = context.mapper + else: + localparent = parentmapper + + if self.mapper in context.recursion_stack: + return + else: + context.recursion_stack.add(self.parent) + + statement = context.statement + + if hasattr(statement, '_outerjoin'): + towrap = statement._outerjoin + elif isinstance(localparent.mapped_table, schema.Table): + # if the mapper is against a plain Table, look in the from_obj of the select statement + # to join against whats already there. + for (fromclause, finder) in [(x, sql_util.TableFinder(x)) for x in statement.froms]: + # dont join against an Alias'ed Select. we are really looking either for the + # table itself or a Join that contains the table. this logic still might need + # adjustments for scenarios not thought of yet. + if not isinstance(fromclause, sql.Alias) and localparent.mapped_table in finder: + towrap = fromclause + break + else: + raise exceptions.InvalidRequestError("EagerLoader cannot locate a clause with which to outer join to, in query '%s' %s" % (str(statement), self.localparent.mapped_table)) + else: + # if the mapper is against a select statement or something, we cant handle that at the + # same time as a custom FROM clause right now. + towrap = localparent.mapped_table + + try: + clauses = self.clauses[parentclauses] + except KeyError: + clauses = EagerLoader.AliasedClauses(self, parentclauses) + self.clauses[parentclauses] = clauses + self.clauses_by_lead_mapper[context.mapper] = clauses + + if self.secondaryjoin is not None: + statement._outerjoin = sql.outerjoin(towrap, clauses.eagersecondary, clauses.eagerprimary).outerjoin(clauses.eagertarget, clauses.eagersecondaryjoin) + if self.order_by is False and self.secondary.default_order_by() is not None: + statement.order_by(*clauses.eagersecondary.default_order_by()) + else: + statement._outerjoin = towrap.outerjoin(clauses.eagertarget, clauses.eagerprimary) + if self.order_by is False and clauses.eagertarget.default_order_by() is not None: + statement.order_by(*clauses.eagertarget.default_order_by()) + + if clauses.eager_order_by: + statement.order_by(*util.to_list(clauses.eager_order_by)) + elif getattr(statement, 'order_by_clause', None): + clauses._aliasize_orderby(statement.order_by_clause, False) + + statement.append_from(statement._outerjoin) + for value in self.mapper.props.values(): + value.setup(context, eagertable=clauses.eagertarget, parentclauses=clauses, parentmapper=self.mapper) + + def process_row(self, selectcontext, instance, row, identitykey, isnew): + """receive a row. tell our mapper to look for a new object instance in the row, and attach + it to a list on the parent instance.""" + + if self in selectcontext.recursion_stack: + return + + try: + # decorate the row according to the stored AliasedClauses for this eager load, + # or look for a user-defined decorator in the SelectContext (which was set up by the contains_eager() option) + if selectcontext.attributes.has_key((EagerLoader, self.parent_property)): + # custom row decoration function, placed in the selectcontext by the + # contains_eager() mapper option + decorator = selectcontext.attributes[(EagerLoader, self.parent_property)] + if decorator is None: + decorated_row = row + else: + decorated_row = decorator(row) + else: + clauses = self.clauses_by_lead_mapper[selectcontext.mapper] + decorated_row = clauses._decorate_row(row) + # check for identity key + identity_key = self.mapper.identity_key_from_row(decorated_row) + except KeyError: + # else degrade to a lazy loader + self.logger.debug("degrade to lazy loader on %s" % mapperutil.attribute_str(instance, self.key)) + self.parent_property._get_strategy(LazyLoader).process_row(selectcontext, instance, row, identitykey, isnew) + return + + # TODO: recursion check a speed hit...? try to get a "termination point" into the AliasedClauses + # or EagerRowAdapter ? + selectcontext.recursion_stack.add(self) + try: + if not self.uselist: + self.logger.debug("eagerload scalar instance on %s" % mapperutil.attribute_str(instance, self.key)) + if isnew: + # set a scalar object instance directly on the parent object, + # bypassing SmartProperty event handlers. + instance.__dict__[self.key] = self.mapper._instance(selectcontext, decorated_row, None) + else: + # call _instance on the row, even though the object has been created, + # so that we further descend into properties + self.mapper._instance(selectcontext, decorated_row, None) + else: + if isnew: + self.logger.debug("initialize UniqueAppender on %s" % mapperutil.attribute_str(instance, self.key)) + # call the SmartProperty's initialize() method to create a new, blank list + l = getattr(instance.__class__, self.key).initialize(instance) + + # create an appender object which will add set-like semantics to the list + appender = util.UniqueAppender(l.data) + + # store it in the "scratch" area, which is local to this load operation. + selectcontext.attributes[(instance, self.key)] = appender + result_list = selectcontext.attributes[(instance, self.key)] + self.logger.debug("eagerload list instance on %s" % mapperutil.attribute_str(instance, self.key)) + self.mapper._instance(selectcontext, decorated_row, result_list) + finally: + selectcontext.recursion_stack.remove(self) + +EagerLoader.logger = logging.class_logger(EagerLoader) + +class EagerLazyOption(StrategizedOption): + def __init__(self, key, lazy=True): + super(EagerLazyOption, self).__init__(key) + self.lazy = lazy + def process_query_property(self, context, prop): + if self.lazy: + if prop in context.eager_loaders: + context.eager_loaders.remove(prop) + else: + context.eager_loaders.add(prop) + super(EagerLazyOption, self).process_query_property(context, prop) + def get_strategy_class(self): + if self.lazy: + return LazyLoader + elif self.lazy is False: + return EagerLoader + elif self.lazy is None: + return NoLoader +EagerLazyOption.logger = logging.class_logger(EagerLazyOption) + +class RowDecorateOption(PropertyOption): + def __init__(self, key, decorator=None): + super(RowDecorateOption, self).__init__(key) + self.decorator = decorator + def process_selection_property(self, context, property): + context.attributes[(EagerLoader, property)] = self.decorator +RowDecorateOption.logger = logging.class_logger(RowDecorateOption) + + diff --git a/spyce-2.1/sqlalchemy/orm/strategies.pyc b/spyce-2.1/sqlalchemy/orm/strategies.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08e2a6d578588b2e3893441e65c82d21b17486a1 Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/strategies.pyc differ diff --git a/spyce-2.1/sqlalchemy/orm/sync.py b/spyce-2.1/sqlalchemy/orm/sync.py new file mode 100755 index 0000000000000000000000000000000000000000..156033868cf2b8bfd1cde454b9f6802f4c5d45ea --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/sync.py @@ -0,0 +1,151 @@ +# mapper/sync.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + + +from sqlalchemy import sql, schema, exceptions +from sqlalchemy import logging +from sqlalchemy.orm import util as mapperutil + +"""contains the ClauseSynchronizer class, which is used to map attributes between two objects +in a manner corresponding to a SQL clause that compares column values.""" + +ONETOMANY = 0 +MANYTOONE = 1 +MANYTOMANY = 2 + +class ClauseSynchronizer(object): + """Given a SQL clause, usually a series of one or more binary + expressions between columns, and a set of 'source' and 'destination' mappers, compiles a set of SyncRules + corresponding to that information. The ClauseSynchronizer can then be executed given a set of parent/child + objects or destination dictionary, which will iterate through each of its SyncRules and execute them. + Each SyncRule will copy the value of a single attribute from the parent + to the child, corresponding to the pair of columns in a particular binary expression, using the source and + destination mappers to map those two columns to object attributes within parent and child.""" + def __init__(self, parent_mapper, child_mapper, direction): + self.parent_mapper = parent_mapper + self.child_mapper = child_mapper + self.direction = direction + self.syncrules = [] + + def compile(self, sqlclause, issecondary=None, foreignkey=None): + def compile_binary(binary): + """assemble a SyncRule given a single binary condition""" + if binary.operator != '=' or not isinstance(binary.left, schema.Column) or not isinstance(binary.right, schema.Column): + return + + source_column = None + dest_column = None + if foreignkey is not None: + # for self-referential relationships, + # the best we can do right now is figure out which side + # is the primary key + # TODO: need some better way for this + if binary.left.table == binary.right.table: + if binary.left.primary_key: + source_column = binary.left + dest_column = binary.right + elif binary.right.primary_key: + source_column = binary.right + dest_column = binary.left + else: + raise exceptions.ArgumentError("Can't locate a primary key column in self-referential equality clause '%s'" % str(binary)) + # for other relationships we are more flexible + # and go off the 'foreignkey' property + elif binary.left in foreignkey: + dest_column = binary.left + source_column = binary.right + elif binary.right in foreignkey: + dest_column = binary.right + source_column = binary.left + else: + return + else: + if binary.left in [f.column for f in binary.right.foreign_keys]: + dest_column = binary.right + source_column = binary.left + elif binary.right in [f.column for f in binary.left.foreign_keys]: + dest_column = binary.left + source_column = binary.right + + if source_column and dest_column: + if self.direction == ONETOMANY: + self.syncrules.append(SyncRule(self.parent_mapper, source_column, dest_column, dest_mapper=self.child_mapper)) + elif self.direction == MANYTOONE: + self.syncrules.append(SyncRule(self.child_mapper, source_column, dest_column, dest_mapper=self.parent_mapper)) + else: + if not issecondary: + self.syncrules.append(SyncRule(self.parent_mapper, source_column, dest_column, dest_mapper=self.child_mapper, issecondary=issecondary)) + else: + self.syncrules.append(SyncRule(self.child_mapper, source_column, dest_column, dest_mapper=self.parent_mapper, issecondary=issecondary)) + + rules_added = len(self.syncrules) + processor = BinaryVisitor(compile_binary) + sqlclause.accept_visitor(processor) + if len(self.syncrules) == rules_added: + raise exceptions.ArgumentError("No syncrules generated for join criterion " + str(sqlclause)) + + def dest_columns(self): + return [r.dest_column for r in self.syncrules if r.dest_column is not None] + + def execute(self, source, dest, obj=None, child=None, clearkeys=None): + for rule in self.syncrules: + rule.execute(source, dest, obj, child, clearkeys) + +class SyncRule(object): + """An instruction indicating how to populate the objects on each side of a relationship. + i.e. if table1 column A is joined against + table2 column B, and we are a one-to-many from table1 to table2, a syncrule would say + 'take the A attribute from object1 and assign it to the B attribute on object2'. + + A rule contains the source mapper, the source column, destination column, + destination mapper in the case of a one/many relationship, and + the integer direction of this mapper relative to the association in the case + of a many to many relationship. + """ + def __init__(self, source_mapper, source_column, dest_column, dest_mapper=None, issecondary=None): + self.source_mapper = source_mapper + self.source_column = source_column + self.issecondary = issecondary + self.dest_mapper = dest_mapper + self.dest_column = dest_column + + #print "SyncRule", source_mapper, source_column, dest_column, dest_mapper + def dest_primary_key(self): + try: + return self._dest_primary_key + except AttributeError: + self._dest_primary_key = self.dest_mapper is not None and self.dest_column in self.dest_mapper.pks_by_table[self.dest_column.table] + return self._dest_primary_key + + def execute(self, source, dest, obj, child, clearkeys): + if source is None: + if self.issecondary is False: + source = obj + elif self.issecondary is True: + source = child + if clearkeys or source is None: + value = None + else: + value = self.source_mapper.get_attr_by_column(source, self.source_column) + if isinstance(dest, dict): + dest[self.dest_column.key] = value + else: + if clearkeys and self.dest_primary_key(): + raise exceptions.AssertionError("Dependency rule tried to blank-out primary key column '%s' on instance '%s'" % (str(self.dest_column), mapperutil.instance_str(dest))) + + if logging.is_debug_enabled(self.logger): + self.logger.debug("execute() instances: %s(%s)->%s(%s) ('%s')" % (mapperutil.instance_str(source), str(self.source_column), mapperutil.instance_str(dest), str(self.dest_column), value)) + self.dest_mapper.set_attr_by_column(dest, self.dest_column, value) + +SyncRule.logger = logging.class_logger(SyncRule) + +class BinaryVisitor(sql.ClauseVisitor): + def __init__(self, func): + self.func = func + def visit_binary(self, binary): + self.func(binary) + diff --git a/spyce-2.1/sqlalchemy/orm/sync.pyc b/spyce-2.1/sqlalchemy/orm/sync.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec0d25664ef5ee6ee1eb6b5c6626792a8af4c9a7 Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/sync.pyc differ diff --git a/spyce-2.1/sqlalchemy/orm/unitofwork.py b/spyce-2.1/sqlalchemy/orm/unitofwork.py new file mode 100755 index 0000000000000000000000000000000000000000..a6f789e51a630df7614c1c79e542fe1eac7c61d8 --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/unitofwork.py @@ -0,0 +1,856 @@ +# orm/unitofwork.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""the internals for the Unit Of Work system. includes hooks into the attributes package +enabling the routing of change events to Unit Of Work objects, as well as the flush() mechanism +which creates a dependency structure that executes change operations. + +a Unit of Work is essentially a system of maintaining a graph of in-memory objects and their +modified state. Objects are maintained as unique against their primary key identity using +an "identity map" pattern. The Unit of Work then maintains lists of objects that are new, +dirty, or deleted and provides the capability to flush all those changes at once. +""" + +from sqlalchemy import util, logging, topological +from sqlalchemy.orm import attributes +from sqlalchemy.orm.mapper import object_mapper, class_mapper +from sqlalchemy.exceptions import * +import StringIO +import weakref +import sets + +class UOWEventHandler(attributes.AttributeExtension): + """an event handler added to all class attributes which handles session operations.""" + def __init__(self, key, class_, cascade=None): + self.key = key + self.class_ = class_ + self.cascade = cascade + def append(self, event, obj, item): + # process "save_update" cascade rules for when an instance is appended to the list of another instance + sess = object_session(obj) + if sess is not None: + if self.cascade is not None and self.cascade.save_update and item not in sess: + mapper = object_mapper(obj) + prop = mapper.props[self.key] + ename = prop.mapper.entity_name + sess.save_or_update(item, entity_name=ename) + + def delete(self, event, obj, item): + # currently no cascade rules for removing an item from a list + # (i.e. it stays in the Session) + pass + + def set(self, event, obj, newvalue, oldvalue): + # process "save_update" cascade rules for when an instance is attached to another instance + sess = object_session(obj) + if sess is not None: + if newvalue is not None and self.cascade is not None and self.cascade.save_update and newvalue not in sess: + mapper = object_mapper(obj) + prop = mapper.props[self.key] + ename = prop.mapper.entity_name + sess.save_or_update(newvalue, entity_name=ename) + +class UOWProperty(attributes.InstrumentedAttribute): + """override InstrumentedAttribute to provide an extra AttributeExtension to all managed attributes + as well as the 'property' property.""" + def __init__(self, manager, class_, key, uselist, callable_, typecallable, cascade=None, extension=None, **kwargs): + extension = util.to_list(extension or []) + extension.insert(0, UOWEventHandler(key, class_, cascade=cascade)) + super(UOWProperty, self).__init__(manager, key, uselist, callable_, typecallable, extension=extension,**kwargs) + self.class_ = class_ + + property = property(lambda s:class_mapper(s.class_).props[s.key], doc="returns the MapperProperty object associated with this property") + +class UOWAttributeManager(attributes.AttributeManager): + """override AttributeManager to provide the UOWProperty instance for all InstrumentedAttributes.""" + def create_prop(self, class_, key, uselist, callable_, typecallable, **kwargs): + return UOWProperty(self, class_, key, uselist, callable_, typecallable, **kwargs) + +class UnitOfWork(object): + """main UOW object which stores lists of dirty/new/deleted objects. + provides top-level "flush" functionality as well as the transaction + boundaries with the SQLEngine(s) involved in a write operation.""" + def __init__(self, identity_map=None): + if identity_map is not None: + self.identity_map = identity_map + else: + self.identity_map = weakref.WeakValueDictionary() + + self.new = util.Set() #OrderedSet() + self.deleted = util.Set() + self.logger = logging.instance_logger(self) + + echo = logging.echo_property() + + def _remove_deleted(self, obj): + if hasattr(obj, "_instance_key"): + del self.identity_map[obj._instance_key] + try: + self.deleted.remove(obj) + except KeyError: + pass + try: + self.new.remove(obj) + except KeyError: + pass + + def _validate_obj(self, obj): + if (hasattr(obj, '_instance_key') and not self.identity_map.has_key(obj._instance_key)) or \ + (not hasattr(obj, '_instance_key') and obj not in self.new): + raise InvalidRequestError("Instance '%s' is not attached or pending within this session" % repr(obj)) + + def register_attribute(self, class_, key, uselist, **kwargs): + attribute_manager.register_attribute(class_, key, uselist, **kwargs) + + def register_callable(self, obj, key, func, uselist, **kwargs): + attribute_manager.set_callable(obj, key, func, uselist, **kwargs) + + def register_clean(self, obj): + try: + self.new.remove(obj) + except KeyError: + pass + if not hasattr(obj, '_instance_key'): + mapper = object_mapper(obj) + obj._instance_key = mapper.instance_key(obj) + try: + delattr(obj, '_sa_insert_order') + except AttributeError: + pass + self.identity_map[obj._instance_key] = obj + attribute_manager.commit(obj) + + def register_new(self, obj): + if hasattr(obj, '_instance_key'): + raise InvalidRequestError("Object '%s' already has an identity - it cant be registered as new" % repr(obj)) + if obj not in self.new: + self.new.add(obj) + obj._sa_insert_order = len(self.new) + + def register_deleted(self, obj): + if obj not in self.deleted: + self._validate_obj(obj) + self.deleted.add(obj) + + def locate_dirty(self): + return util.Set([x for x in self.identity_map.values() if x not in self.deleted and attribute_manager.is_modified(x)]) + + def flush(self, session, objects=None): + # this context will track all the objects we want to save/update/delete, + # and organize a hierarchical dependency structure. it also handles + # communication with the mappers and relationships to fire off SQL + # and synchronize attributes between related objects. + echo = logging.is_info_enabled(self.logger) + + flush_context = UOWTransaction(self, session) + + # create the set of all objects we want to operate upon + if objects is not None: + # specific list passed in + objset = util.Set(objects) + else: + # or just everything + objset = util.Set(self.identity_map.values()).union(self.new) + + # detect persistent objects that have changes + dirty = self.locate_dirty() + + # store objects whose fate has been decided + processed = util.Set() + + + # put all saves/updates into the flush context. detect orphans and throw them into deleted. + for obj in self.new.union(dirty).intersection(objset).difference(self.deleted): + if obj in processed: + continue + if object_mapper(obj)._is_orphan(obj): + for c in [obj] + list(object_mapper(obj).cascade_iterator('delete', obj)): + if c in processed: + continue + flush_context.register_object(c, isdelete=True) + processed.add(c) + else: + flush_context.register_object(obj) + processed.add(obj) + + # put all remaining deletes into the flush context. + for obj in self.deleted: + if (objset is not None and not obj in objset) or obj in processed: + continue + flush_context.register_object(obj, isdelete=True) + + trans = session.create_transaction(autoflush=False) + flush_context.transaction = trans + try: + flush_context.execute() + except: + trans.rollback() + raise + trans.commit() + + flush_context.post_exec() + +class UOWTransaction(object): + """handles the details of organizing and executing transaction tasks + during a UnitOfWork object's flush() operation.""" + def __init__(self, uow, session): + self.uow = uow + self.session = session + # unique list of all the mappers we come across + self.mappers = util.Set() + self.dependencies = {} + self.tasks = {} + self.logger = logging.instance_logger(self) + self.echo = uow.echo + + echo = logging.echo_property() + + def register_object(self, obj, isdelete = False, listonly = False, postupdate=False, post_update_cols=None, **kwargs): + """adds an object to this UOWTransaction to be updated in the database. + + 'isdelete' indicates whether the object is to be deleted or saved (update/inserted). + + 'listonly', indicates that only this object's dependency relationships should be + refreshed/updated to reflect a recent save/upcoming delete operation, but not a full + save/delete operation on the object itself, unless an additional save/delete + registration is entered for the object.""" + #print "REGISTER", repr(obj), repr(getattr(obj, '_instance_key', None)), str(isdelete), str(listonly) + + # things can get really confusing if theres duplicate instances floating around, + # so make sure everything is OK + self.uow._validate_obj(obj) + + mapper = object_mapper(obj) + self.mappers.add(mapper) + task = self.get_task_by_mapper(mapper) + if postupdate: + task.append_postupdate(obj, post_update_cols) + return + + # for a cyclical task, things need to be sorted out already, + # so this object should have already been added to the appropriate sub-task + # can put an assertion here to make sure.... + if task.circular: + return + + task.append(obj, listonly, isdelete=isdelete, **kwargs) + + def unregister_object(self, obj): + #print "UNREGISTER", obj + mapper = object_mapper(obj) + task = self.get_task_by_mapper(mapper) + if obj in task.objects: + task.delete(obj) + + + def is_deleted(self, obj): + mapper = object_mapper(obj) + task = self.get_task_by_mapper(mapper) + return task.is_deleted(obj) + + def get_task_by_mapper(self, mapper, dontcreate=False): + """every individual mapper involved in the transaction has a single + corresponding UOWTask object, which stores all the operations involved + with that mapper as well as operations dependent on those operations. + this method returns or creates the single per-transaction instance of + UOWTask that exists for that mapper.""" + try: + return self.tasks[mapper] + except KeyError: + if dontcreate: + return None + task = UOWTask(self, mapper) + task.mapper.register_dependencies(self) + return task + + def register_dependency(self, mapper, dependency): + """called by mapper.PropertyLoader to register the objects handled by + one mapper being dependent on the objects handled by another.""" + # correct for primary mapper (the mapper offcially associated with the class) + # also convert to the "base mapper", the parentmost task at the top of an inheritance chain + # dependency sorting is done via non-inheriting mappers only, dependencies between mappers + # in the same inheritance chain is done at the per-object level + mapper = mapper.primary_mapper().base_mapper() + dependency = dependency.primary_mapper().base_mapper() + + self.dependencies[(mapper, dependency)] = True + + def register_processor(self, mapper, processor, mapperfrom): + """called by mapper.PropertyLoader to register itself as a "processor", which + will be associated with a particular UOWTask, and be given a list of "dependent" + objects corresponding to another UOWTask to be processed, either after that secondary + task saves its objects or before it deletes its objects.""" + # when the task from "mapper" executes, take the objects from the task corresponding + # to "mapperfrom"'s list of save/delete objects, and send them to "processor" + # for dependency processing + + #print "registerprocessor", str(mapper), repr(processor), repr(processor.key), str(mapperfrom) + + # correct for primary mapper (the mapper offcially associated with the class) + mapper = mapper.primary_mapper() + mapperfrom = mapperfrom.primary_mapper() + + task = self.get_task_by_mapper(mapper) + targettask = self.get_task_by_mapper(mapperfrom) + up = UOWDependencyProcessor(processor, targettask) + task.dependencies.add(up) + + def execute(self): + # insure that we have a UOWTask for every mapper that will be involved + # in the topological sort + [self.get_task_by_mapper(m) for m in self._get_noninheriting_mappers()] + + # pre-execute dependency processors. this process may + # result in new tasks, objects and/or dependency processors being added, + # particularly with 'delete-orphan' cascade rules. + # keep running through the full list of tasks until all + # objects have been processed. + while True: + ret = False + for task in self.tasks.values(): + for up in list(task.dependencies): + if up.preexecute(self): + ret = True + if not ret: + break + + head = self._sort_dependencies() + if self.echo: + if head is None: + self.logger.info("Task dump: None") + else: + self.logger.info("Task dump:\n" + head.dump()) + if head is not None: + head.execute(self) + self.logger.info("Execute Complete") + + def post_exec(self): + """after an execute/flush is completed, all of the objects and lists that have + been flushed are updated in the parent UnitOfWork object to mark them as clean.""" + + for task in self.tasks.values(): + for elem in task.objects.values(): + if elem.isdelete: + self.uow._remove_deleted(elem.obj) + else: + self.uow.register_clean(elem.obj) + + def _sort_dependencies(self): + """creates a hierarchical tree of dependent tasks. the root node is returned. + when the root node is executed, it also executes its child tasks recursively.""" + def sort_hier(node): + if node is None: + return None + task = self.get_task_by_mapper(node.item) + if node.cycles is not None: + tasks = [] + for n in node.cycles: + tasks.append(self.get_task_by_mapper(n.item)) + task.circular = task._sort_circular_dependencies(self, tasks) + for child in node.children: + t = sort_hier(child) + if t is not None: + task.childtasks.append(t) + return task + + mappers = self._get_noninheriting_mappers() + head = DependencySorter(self.dependencies, list(mappers)).sort(allow_all_cycles=True) + self.logger.debug("Dependency sort:\n"+ str(head)) + task = sort_hier(head) + return task + + def _get_noninheriting_mappers(self): + """returns a list of UOWTasks whose mappers are not inheriting from the mapper of another UOWTask. + i.e., this returns the root UOWTasks for all the inheritance hierarchies represented in this UOWTransaction.""" + mappers = util.Set() + for task in self.tasks.values(): + base = task.mapper.base_mapper() + mappers.add(base) + return mappers + +class UOWTask(object): + """represents the full list of objects that are to be saved/deleted by a specific Mapper.""" + def __init__(self, uowtransaction, mapper, circular_parent=None): + if not circular_parent: + uowtransaction.tasks[mapper] = self + + # the transaction owning this UOWTask + self.uowtransaction = uowtransaction + + # the Mapper which this UOWTask corresponds to + self.mapper = mapper + + # a dictionary mapping object instances to a corresponding UOWTaskElement. + # Each UOWTaskElement represents one instance which is to be saved or + # deleted by this UOWTask's Mapper. + # in the case of the row-based "circular sort", the UOWTaskElement may + # also reference further UOWTasks which are dependent on that UOWTaskElement. + self.objects = {} #util.OrderedDict() + + # a list of UOWDependencyProcessors which are executed after saves and + # before deletes, to synchronize data to dependent objects + self.dependencies = util.Set() + + # a list of UOWTasks that are dependent on this UOWTask, which + # are to be executed after this UOWTask performs saves and post-save + # dependency processing, and before pre-delete processing and deletes + self.childtasks = [] + + # whether this UOWTask is circular, meaning it holds a second + # UOWTask that contains a special row-based dependency structure. + self.circular = None + + # for a task thats part of that row-based dependency structure, points + # back to the "public facing" task. + self.circular_parent = circular_parent + + # a list of UOWDependencyProcessors are derived from the main + # set of dependencies, referencing sub-UOWTasks attached to this + # one which represent portions of the total list of objects. + # this is used for the row-based "circular sort" + self.cyclical_dependencies = util.Set() + + def is_empty(self): + return len(self.objects) == 0 and len(self.dependencies) == 0 and len(self.childtasks) == 0 + + def append(self, obj, listonly = False, childtask = None, isdelete = False): + """appends an object to this task, to be either saved or deleted depending on the + 'isdelete' attribute of this UOWTask. 'listonly' indicates that the object should + only be processed as a dependency and not actually saved/deleted. if the object + already exists with a 'listonly' flag of False, it is kept as is. 'childtask' is used + internally when creating a hierarchical list of self-referential tasks, to assign + dependent operations at the per-object instead of per-task level. """ + try: + rec = self.objects[obj] + retval = False + except KeyError: + rec = UOWTaskElement(obj) + self.objects[obj] = rec + retval = True + if not listonly: + rec.listonly = False + if childtask: + rec.childtasks.append(childtask) + if isdelete: + rec.isdelete = True + return retval + + def append_postupdate(self, obj, post_update_cols): + # postupdates are UPDATED immeditely (for now) + # convert post_update_cols list to a Set so that __hashcode__ is used to compare columns + # instead of __eq__ + self.mapper.save_obj([obj], self.uowtransaction, postupdate=True, post_update_cols=util.Set(post_update_cols)) + return True + + def delete(self, obj): + try: + del self.objects[obj] + except KeyError: + pass + + def _save_objects(self, trans): + self.mapper.save_obj(self.polymorphic_tosave_objects, trans) + def _delete_objects(self, trans): + for task in self.polymorphic_tasks(): + task.mapper.delete_obj(task.todelete_objects, trans) + + def execute(self, trans): + """executes this UOWTask. saves objects to be saved, processes all dependencies + that have been registered, and deletes objects to be deleted. """ + + UOWExecutor().execute(trans, self) + + def polymorphic_tasks(self): + """returns an iteration consisting of this UOWTask, and all UOWTasks whose + mappers are inheriting descendants of this UOWTask's mapper. UOWTasks are returned in order + of their hierarchy to each other, meaning if UOWTask B's mapper inherits from UOWTask A's + mapper, then UOWTask B will appear after UOWTask A in the iteration.""" + + # first us + yield self + + # "circular dependency" tasks aren't polymorphic + if self.circular_parent is not None: + return + + # closure to locate the "next level" of inherited mapper UOWTasks + def _tasks_by_mapper(mapper): + for m in mapper._inheriting_mappers: + inherit_task = self.uowtransaction.tasks.get(m, None) + if inherit_task is not None: + yield inherit_task + else: + for t in _tasks_by_mapper(m): + yield t + + # main yield loop + for task in _tasks_by_mapper(self.mapper): + for t in task.polymorphic_tasks(): + yield t + + def contains_object(self, obj, polymorphic=False): + if polymorphic: + for task in self.polymorphic_tasks(): + if obj in task.objects: + return True + else: + if obj in self.objects: + return True + return False + + def is_inserted(self, obj): + return not hasattr(obj, '_instance_key') + + def is_deleted(self, obj): + try: + return self.objects[obj].isdelete + except KeyError: + return False + + def get_elements(self, polymorphic=False): + if polymorphic: + for task in self.polymorphic_tasks(): + for rec in task.objects.values(): + yield rec + else: + for rec in self.objects.values(): + yield rec + + polymorphic_tosave_elements = property(lambda self: [rec for rec in self.get_elements(polymorphic=True) if not rec.isdelete]) + polymorphic_todelete_elements = property(lambda self: [rec for rec in self.get_elements(polymorphic=True) if rec.isdelete]) + tosave_elements = property(lambda self: [rec for rec in self.get_elements(polymorphic=False) if not rec.isdelete]) + todelete_elements = property(lambda self:[rec for rec in self.get_elements(polymorphic=False) if rec.isdelete]) + tosave_objects = property(lambda self:[rec.obj for rec in self.get_elements(polymorphic=False) if rec.obj is not None and not rec.listonly and rec.isdelete is False]) + todelete_objects = property(lambda self:[rec.obj for rec in self.get_elements(polymorphic=False) if rec.obj is not None and not rec.listonly and rec.isdelete is True]) + polymorphic_tosave_objects = property(lambda self:[rec.obj for rec in self.get_elements(polymorphic=True) if rec.obj is not None and not rec.listonly and rec.isdelete is False]) + + def _sort_circular_dependencies(self, trans, cycles): + """for a single task, creates a hierarchical tree of "subtasks" which associate + specific dependency actions with individual objects. This is used for a + "cyclical" task, or a task where elements + of its object list contain dependencies on each other. + + this is not the normal case; this logic only kicks in when something like + a hierarchical tree is being represented.""" + allobjects = [] + for task in cycles: + allobjects += [e.obj for e in task.get_elements(polymorphic=True)] + tuples = [] + + cycles = util.Set(cycles) + + #print "BEGIN CIRC SORT-------" + #print "PRE-CIRC:" + #print list(cycles) #[0].dump() + + # dependency processors that arent part of the cyclical thing + # get put here + extradeplist = [] + + # organizes a set of new UOWTasks that will be assembled into + # the final tree, for the purposes of holding new UOWDependencyProcessors + # which process small sub-sections of dependent parent/child operations + dependencies = {} + def get_dependency_task(obj, depprocessor): + try: + dp = dependencies[obj] + except KeyError: + dp = dependencies.setdefault(obj, {}) + try: + l = dp[depprocessor] + except KeyError: + l = UOWTask(self.uowtransaction, depprocessor.targettask.mapper, circular_parent=self) + dp[depprocessor] = l + return l + + def dependency_in_cycles(dep): + # TODO: make a simpler way to get at the "root inheritance" mapper + proctask = trans.get_task_by_mapper(dep.processor.mapper.primary_mapper().base_mapper(), True) + targettask = trans.get_task_by_mapper(dep.targettask.mapper.base_mapper(), True) + return targettask in cycles and (proctask is not None and proctask in cycles) + + # organize all original UOWDependencyProcessors by their target task + deps_by_targettask = {} + for t in cycles: + for task in t.polymorphic_tasks(): + for dep in task.dependencies: + if not dependency_in_cycles(dep): + extradeplist.append(dep) + for t in dep.targettask.polymorphic_tasks(): + l = deps_by_targettask.setdefault(t, []) + l.append(dep) + + object_to_original_task = {} + + for t in cycles: + for task in t.polymorphic_tasks(): + for taskelement in task.get_elements(polymorphic=False): + obj = taskelement.obj + object_to_original_task[obj] = task + + for dep in deps_by_targettask.get(task, []): + # is this dependency involved in one of the cycles ? + if not dependency_in_cycles(dep): + continue + (processor, targettask) = (dep.processor, dep.targettask) + isdelete = taskelement.isdelete + + # list of dependent objects from this object + childlist = dep.get_object_dependencies(obj, trans, passive=True) + if childlist is None: + continue + # the task corresponding to saving/deleting of those dependent objects + childtask = trans.get_task_by_mapper(processor.mapper.primary_mapper()) + + childlist = childlist.added_items() + childlist.unchanged_items() + childlist.deleted_items() + + for o in childlist: + + # other object is None. this can occur if the relationship is many-to-one + # or one-to-one, and None was set. the "removed" object will be picked + # up in this iteration via the deleted_items() part of the collection. + if o is None: + continue + + # the other object is not in the UOWTransaction ! but if we are many-to-one, + # we need a task in order to attach dependency operations, so establish a "listonly" + # task + if not childtask.contains_object(o, polymorphic=True): + childtask.append(o, listonly=True) + object_to_original_task[o] = childtask + + # create a tuple representing the "parent/child" + whosdep = dep.whose_dependent_on_who(obj, o) + if whosdep is not None: + # append the tuple to the partial ordering. + tuples.append(whosdep) + + # create a UOWDependencyProcessor representing this pair of objects. + # append it to a UOWTask + if whosdep[0] is obj: + get_dependency_task(whosdep[0], dep).append(whosdep[0], isdelete=isdelete) + else: + get_dependency_task(whosdep[0], dep).append(whosdep[1], isdelete=isdelete) + else: + get_dependency_task(obj, dep).append(obj, isdelete=isdelete) + + #print "TUPLES", tuples + head = DependencySorter(tuples, allobjects).sort() + if head is None: + return None + + #print str(head) + + # create a tree of UOWTasks corresponding to the tree of object instances + # created by the DependencySorter + def make_task_tree(node, parenttask, nexttasks): + originating_task = object_to_original_task[node.item] + t = nexttasks.get(originating_task, None) + if t is None: + t = UOWTask(self.uowtransaction, originating_task.mapper, circular_parent=self) + nexttasks[originating_task] = t + parenttask.append(None, listonly=False, isdelete=originating_task.objects[node.item].isdelete, childtask=t) + t.append(node.item, originating_task.objects[node.item].listonly, isdelete=originating_task.objects[node.item].isdelete) + + if dependencies.has_key(node.item): + for depprocessor, deptask in dependencies[node.item].iteritems(): + t.cyclical_dependencies.add(depprocessor.branch(deptask)) + nd = {} + for n in node.children: + t2 = make_task_tree(n, t, nd) + return t + + # this is the new "circular" UOWTask which will execute in place of "self" + t = UOWTask(self.uowtransaction, self.mapper, circular_parent=self) + + # stick the non-circular dependencies and child tasks onto the new + # circular UOWTask + [t.dependencies.add(d) for d in extradeplist] + t.childtasks = self.childtasks + make_task_tree(head, t, {}) + #print t.dump() + return t + + def dump(self): + buf = StringIO.StringIO() + import uowdumper + uowdumper.UOWDumper(self, buf) + return buf.getvalue() + + + def __repr__(self): + if self.mapper is not None: + if self.mapper.__class__.__name__ == 'Mapper': + name = self.mapper.class_.__name__ + "/" + self.mapper.local_table.name + else: + name = repr(self.mapper) + else: + name = '(none)' + return ("UOWTask(%d) Mapper: '%s'" % (id(self), name)) + +class UOWTaskElement(object): + """an element within a UOWTask. corresponds to a single object instance + to be saved, deleted, or just part of the transaction as a placeholder for + further dependencies (i.e. 'listonly'). + in the case of self-referential mappers, may also store a list of childtasks, + further UOWTasks containing objects dependent on this element's object instance.""" + def __init__(self, obj): + self.obj = obj + self.__listonly = True + self.childtasks = [] + self.__isdelete = False + self.__preprocessed = {} + def _get_listonly(self): + return self.__listonly + def _set_listonly(self, value): + """set_listonly is a one-way setter, will only go from True to False.""" + if not value and self.__listonly: + self.__listonly = False + self.clear_preprocessed() + def _get_isdelete(self): + return self.__isdelete + def _set_isdelete(self, value): + if self.__isdelete is not value: + self.__isdelete = value + self.clear_preprocessed() + listonly = property(_get_listonly, _set_listonly) + isdelete = property(_get_isdelete, _set_isdelete) + + def mark_preprocessed(self, processor): + """marks this element as "preprocessed" by a particular UOWDependencyProcessor. preprocessing is the step + which sweeps through all the relationships on all the objects in the flush transaction and adds other objects + which are also affected, In some cases it can switch an object from "tosave" to "todelete". changes to the state + of this UOWTaskElement will reset all "preprocessed" flags, causing it to be preprocessed again. When all UOWTaskElements + have been fully preprocessed by all UOWDependencyProcessors, then the topological sort can be done.""" + self.__preprocessed[processor] = True + def is_preprocessed(self, processor): + return self.__preprocessed.get(processor, False) + def clear_preprocessed(self): + self.__preprocessed.clear() + def __repr__(self): + return "UOWTaskElement/%d: %s/%d %s" % (id(self), self.obj.__class__.__name__, id(self.obj), (self.listonly and 'listonly' or (self.isdelete and 'delete' or 'save')) ) + +class UOWDependencyProcessor(object): + """in between the saving and deleting of objects, process "dependent" data, such as filling in + a foreign key on a child item from a new primary key, or deleting association rows before a + delete. This object acts as a proxy to a DependencyProcessor.""" + def __init__(self, processor, targettask): + self.processor = processor + self.targettask = targettask + def __eq__(self, other): + return other.processor is self.processor and other.targettask is self.targettask + def __hash__(self): + return hash((self.processor, self.targettask)) + + def preexecute(self, trans): + """traverses all objects handled by this dependency processor and locates additional objects which should be + part of the transaction, such as those affected deletes, orphans to be deleted, etc. Returns True if any + objects were preprocessed, or False if no objects were preprocessed.""" + def getobj(elem): + elem.mark_preprocessed(self) + return elem.obj + + ret = False + elements = [getobj(elem) for elem in self.targettask.polymorphic_tosave_elements if elem.obj is not None and not elem.is_preprocessed(self)] + if len(elements): + ret = True + self.processor.preprocess_dependencies(self.targettask, elements, trans, delete=False) + + elements = [getobj(elem) for elem in self.targettask.polymorphic_todelete_elements if elem.obj is not None and not elem.is_preprocessed(self)] + if len(elements): + ret = True + self.processor.preprocess_dependencies(self.targettask, elements, trans, delete=True) + return ret + + def execute(self, trans, delete): + if not delete: + self.processor.process_dependencies(self.targettask, [elem.obj for elem in self.targettask.polymorphic_tosave_elements if elem.obj is not None], trans, delete=False) + else: + self.processor.process_dependencies(self.targettask, [elem.obj for elem in self.targettask.polymorphic_todelete_elements if elem.obj is not None], trans, delete=True) + + def get_object_dependencies(self, obj, trans, passive): + return self.processor.get_object_dependencies(obj, trans, passive=passive) + + def whose_dependent_on_who(self, obj, o): + return self.processor.whose_dependent_on_who(obj, o) + + def branch(self, task): + return UOWDependencyProcessor(self.processor, task) + +class UOWExecutor(object): + """encapsulates the execution traversal of a UOWTransaction structure.""" + def execute(self, trans, task, isdelete=None): + if isdelete is not True: + self.execute_save_steps(trans, task) + if isdelete is not False: + self.execute_delete_steps(trans, task) + + def save_objects(self, trans, task): + task._save_objects(trans) + + def delete_objects(self, trans, task): + task._delete_objects(trans) + + def execute_dependency(self, trans, dep, isdelete): + dep.execute(trans, isdelete) + + def execute_save_steps(self, trans, task): + if task.circular is not None: + self.execute_save_steps(trans, task.circular) + else: + self.save_objects(trans, task) + self.execute_cyclical_dependencies(trans, task, False) + self.execute_per_element_childtasks(trans, task, False) + self.execute_dependencies(trans, task, False) + self.execute_dependencies(trans, task, True) + self.execute_childtasks(trans, task, False) + + def execute_delete_steps(self, trans, task): + if task.circular is not None: + self.execute_delete_steps(trans, task.circular) + else: + self.execute_cyclical_dependencies(trans, task, True) + self.execute_childtasks(trans, task, True) + self.execute_per_element_childtasks(trans, task, True) + self.delete_objects(trans, task) + + def execute_dependencies(self, trans, task, isdelete=None): + alltasks = list(task.polymorphic_tasks()) + if isdelete is not True: + for task in alltasks: + for dep in task.dependencies: + self.execute_dependency(trans, dep, False) + if isdelete is not False: + alltasks.reverse() + for task in alltasks: + for dep in task.dependencies: + self.execute_dependency(trans, dep, True) + + def execute_childtasks(self, trans, task, isdelete=None): + for polytask in task.polymorphic_tasks(): + for child in polytask.childtasks: + self.execute(trans, child, isdelete) + + def execute_cyclical_dependencies(self, trans, task, isdelete): + for polytask in task.polymorphic_tasks(): + for dep in polytask.cyclical_dependencies: + self.execute_dependency(trans, dep, isdelete) + + def execute_per_element_childtasks(self, trans, task, isdelete): + for polytask in task.polymorphic_tasks(): + for element in polytask.tosave_elements + polytask.todelete_elements: + self.execute_element_childtasks(trans, element, isdelete) + + def execute_element_childtasks(self, trans, element, isdelete): + for child in element.childtasks: + self.execute(trans, child, isdelete) + + +class DependencySorter(topological.QueueDependencySorter): + pass + +attribute_manager = UOWAttributeManager() + diff --git a/spyce-2.1/sqlalchemy/orm/unitofwork.pyc b/spyce-2.1/sqlalchemy/orm/unitofwork.pyc new file mode 100644 index 0000000000000000000000000000000000000000..368a2a3c2b4a90c95168f8a30bdbcaa32a21b15a Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/unitofwork.pyc differ diff --git a/spyce-2.1/sqlalchemy/orm/uowdumper.py b/spyce-2.1/sqlalchemy/orm/uowdumper.py new file mode 100755 index 0000000000000000000000000000000000000000..e569d0d9aeafa8f875e8777aec878363555e9a19 --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/uowdumper.py @@ -0,0 +1,198 @@ +from sqlalchemy.orm import unitofwork + +"""dumps out a string representation of a UOWTask structure""" + +class UOWDumper(unitofwork.UOWExecutor): + def __init__(self, task, buf, verbose=False): + self.verbose = verbose + self.indent = 0 + self.task = task + self.buf = buf + self.starttask = task + self.headers = {} + self.execute(None, task) + + def execute(self, trans, task, isdelete=None): + oldstarttask = self.starttask + oldheaders = self.headers + self.starttask = task + self.headers = {} + try: + i = self._indent() + if len(i): + i += "-" + #i = i[0:-1] + "-" + if task.circular is not None: + self.buf.write(self._indent() + "\n") + self.buf.write(i + " " + self._repr_task(task)) + self.buf.write(" (contains cyclical sub-tasks)") + else: + self.buf.write(self._indent() + "\n") + self.buf.write(i + " " + self._repr_task(task)) + self.buf.write(" (" + (isdelete and "delete " or "save/update ") + "phase) \n") + self.indent += 1 + super(UOWDumper, self).execute(trans, task, isdelete) + finally: + self.indent -= 1 + if self.starttask.is_empty(): + self.buf.write(self._indent() + " |- (empty task)\n") + else: + self.buf.write(self._indent() + " |----\n") + + self.buf.write(self._indent() + "\n") + self.starttask = oldstarttask + self.headers = oldheaders + + def save_objects(self, trans, task): + # sort elements to be inserted by insert order + def comparator(a, b): + if a.obj is None: + x = None + elif not hasattr(a.obj, '_sa_insert_order'): + x = None + else: + x = a.obj._sa_insert_order + if b.obj is None: + y = None + elif not hasattr(b.obj, '_sa_insert_order'): + y = None + else: + y = b.obj._sa_insert_order + return cmp(x, y) + + l = list(task.polymorphic_tosave_elements) + l.sort(comparator) + for rec in l: + if rec.listonly: + continue + self.header("Save elements"+ self._inheritance_tag(task)) + self.buf.write(self._indent() + "- " + self._repr_task_element(rec) + "\n") + self.closeheader() + + def delete_objects(self, trans, task): + for rec in task.polymorphic_todelete_elements: + if rec.listonly: + continue + self.header("Delete elements"+ self._inheritance_tag(task)) + self.buf.write(self._indent() + "- " + self._repr_task_element(rec) + "\n") + self.closeheader() + + def _inheritance_tag(self, task): + if not self.verbose: + return "" + elif task is not self.starttask: + return (" (inheriting task %s)" % self._repr_task(task)) + else: + return "" + + def header(self, text): + """write a given header just once""" + if not self.verbose: + return + try: + self.headers[text] + except KeyError: + self.buf.write(self._indent() + "- " + text + "\n") + self.headers[text] = True + + def closeheader(self): + if not self.verbose: + return + self.buf.write(self._indent() + "- ------\n") + + def execute_dependency(self, transaction, dep, isdelete): + self._dump_processor(dep, isdelete) + + def execute_save_steps(self, trans, task): + super(UOWDumper, self).execute_save_steps(trans, task) + + def execute_delete_steps(self, trans, task): + super(UOWDumper, self).execute_delete_steps(trans, task) + + def execute_dependencies(self, trans, task, isdelete=None): + super(UOWDumper, self).execute_dependencies(trans, task, isdelete) + + def execute_childtasks(self, trans, task, isdelete=None): + self.header("Child tasks" + self._inheritance_tag(task)) + super(UOWDumper, self).execute_childtasks(trans, task, isdelete) + self.closeheader() + + def execute_cyclical_dependencies(self, trans, task, isdelete): + self.header("Cyclical %s dependencies" % (isdelete and "delete" or "save")) + super(UOWDumper, self).execute_cyclical_dependencies(trans, task, isdelete) + self.closeheader() + + def execute_per_element_childtasks(self, trans, task, isdelete): + super(UOWDumper, self).execute_per_element_childtasks(trans, task, isdelete) + + def execute_element_childtasks(self, trans, element, isdelete): + self.header("%s subelements of UOWTaskElement(%s)" % ((isdelete and "Delete" or "Save"), hex(id(element)))) + super(UOWDumper, self).execute_element_childtasks(trans, element, isdelete) + self.closeheader() + + def _dump_processor(self, proc, deletes): + if deletes: + val = proc.targettask.polymorphic_todelete_elements + else: + val = proc.targettask.polymorphic_tosave_elements + + if self.verbose: + self.buf.write(self._indent() + " |- %s attribute on %s (UOWDependencyProcessor(%d) processing %s)\n" % ( + repr(proc.processor.key), + ("%s's to be %s" % (self._repr_task_class(proc.targettask), deletes and "deleted" or "saved")), + hex(id(proc)), + self._repr_task(proc.targettask)) + ) + elif False: + self.buf.write(self._indent() + " |- %s attribute on %s\n" % ( + repr(proc.processor.key), + ("%s's to be %s" % (self._repr_task_class(proc.targettask), deletes and "deleted" or "saved")), + ) + ) + + if len(val) == 0: + if self.verbose: + self.buf.write(self._indent() + " |- " + "(no objects)\n") + for v in val: + self.buf.write(self._indent() + " |- " + self._repr_task_element(v, proc.processor.key, process=True) + "\n") + + def _repr_task_element(self, te, attribute=None, process=False): + if te.obj is None: + objid = "(placeholder)" + else: + if attribute is not None: + objid = "%s(%s).%s" % (te.obj.__class__.__name__, hex(id(te.obj)), attribute) + else: + objid = "%s(%s)" % (te.obj.__class__.__name__, hex(id(te.obj))) + if self.verbose: + return "%s (UOWTaskElement(%s, %s))" % (objid, hex(id(te)), (te.listonly and 'listonly' or (te.isdelete and 'delete' or 'save'))) + elif process: + return "Process %s" % (objid) + else: + return "%s %s" % ((te.isdelete and "Delete" or "Save"), objid) + + def _repr_task(self, task): + if task.mapper is not None: + if task.mapper.__class__.__name__ == 'Mapper': + name = task.mapper.class_.__name__ + "/" + task.mapper.local_table.name + "/" + str(task.mapper.entity_name) + else: + name = repr(task.mapper) + else: + name = '(none)' + if task.circular_parent: + return ("UOWTask(%s->%s, %s)" % (hex(id(task.circular_parent)), hex(id(task)), name)) + else: + return ("UOWTask(%s, %s)" % (hex(id(task)), name)) + + def _repr_task_class(self, task): + if task.mapper is not None and task.mapper.__class__.__name__ == 'Mapper': + return task.mapper.class_.__name__ + else: + return '(none)' + + def _repr(self, obj): + return "%s(%s)" % (obj.__class__.__name__, hex(id(obj))) + + def _indent(self): + return " |" * self.indent + diff --git a/spyce-2.1/sqlalchemy/orm/util.py b/spyce-2.1/sqlalchemy/orm/util.py new file mode 100755 index 0000000000000000000000000000000000000000..d90a0a1d8d529a7030b770f0e73c86226e3d5889 --- /dev/null +++ b/spyce-2.1/sqlalchemy/orm/util.py @@ -0,0 +1,99 @@ +# mapper/util.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from sqlalchemy import sql, util + +class CascadeOptions(object): + """keeps track of the options sent to relation().cascade""" + def __init__(self, arg=""): + values = util.Set([c.strip() for c in arg.split(',')]) + self.delete_orphan = "delete-orphan" in values + self.delete = "delete" in values or self.delete_orphan or "all" in values + self.save_update = "save-update" in values or "all" in values + self.merge = "merge" in values or "all" in values + self.expunge = "expunge" in values or "all" in values + # refresh_expire not really implemented as of yet + #self.refresh_expire = "refresh-expire" in values or "all" in values + def __contains__(self, item): + return getattr(self, item.replace("-", "_"), False) + + +def polymorphic_union(table_map, typecolname, aliasname='p_union'): + """create a UNION statement used by a polymorphic mapper. + + See the SQLAlchemy advanced mapping docs for an example of how this is used.""" + colnames = util.Set() + colnamemaps = {} + types = {} + for key in table_map.keys(): + table = table_map[key] + + # mysql doesnt like selecting from a select; make it an alias of the select + if isinstance(table, sql.Select): + table = table.alias() + table_map[key] = table + + m = {} + for c in table.c: + colnames.add(c.name) + m[c.name] = c + types[c.name] = c.type + colnamemaps[table] = m + + def col(name, table): + try: + return colnamemaps[table][name] + except KeyError: + return sql.cast(sql.null(), types[name]).label(name) + + result = [] + for type, table in table_map.iteritems(): + if typecolname is not None: + result.append(sql.select([col(name, table) for name in colnames] + [sql.column("'%s'" % type).label(typecolname)], from_obj=[table])) + else: + result.append(sql.select([col(name, table) for name in colnames], from_obj=[table])) + return sql.union_all(*result).alias(aliasname) + +class TranslatingDict(dict): + """a dictionary that stores ColumnElement objects as keys. incoming ColumnElement + keys are translated against those of an underling FromClause for all operations. + This way the columns from any Selectable that is derived from or underlying this + TranslatingDict's selectable can be used as keys.""" + def __init__(self, selectable): + super(TranslatingDict, self).__init__() + self.selectable = selectable + def __translate_col(self, col): + ourcol = self.selectable.corresponding_column(col, keys_ok=False, raiseerr=False) + #if col is not ourcol: + # print "TD TRANSLATING ", col, "TO", ourcol + if ourcol is None: + return col + else: + return ourcol + def __getitem__(self, col): + return super(TranslatingDict, self).__getitem__(self.__translate_col(col)) + def has_key(self, col): + return super(TranslatingDict, self).has_key(self.__translate_col(col)) + def __setitem__(self, col, value): + return super(TranslatingDict, self).__setitem__(self.__translate_col(col), value) + def __contains__(self, col): + return self.has_key(col) + def setdefault(self, col, value): + return super(TranslatingDict, self).setdefault(self.__translate_col(col), value) + +class BinaryVisitor(sql.ClauseVisitor): + def __init__(self, func): + self.func = func + def visit_binary(self, binary): + self.func(binary) + +def instance_str(instance): + """return a string describing an instance""" + return instance.__class__.__name__ + "@" + hex(id(instance)) + +def attribute_str(instance, attribute): + return instance_str(instance) + "." + attribute + \ No newline at end of file diff --git a/spyce-2.1/sqlalchemy/orm/util.pyc b/spyce-2.1/sqlalchemy/orm/util.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0de290d5f7f0cc6b636392f8116f2c97de2d180 Binary files /dev/null and b/spyce-2.1/sqlalchemy/orm/util.pyc differ diff --git a/spyce-2.1/sqlalchemy/pool.py b/spyce-2.1/sqlalchemy/pool.py new file mode 100755 index 0000000000000000000000000000000000000000..99504e1368e795811d18e29ae4922809f483987d --- /dev/null +++ b/spyce-2.1/sqlalchemy/pool.py @@ -0,0 +1,486 @@ +# pool.py - Connection pooling for SQLAlchemy +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +"""provides a connection pool implementation, which optionally manages connections +on a thread local basis. Also provides a DBAPI2 transparency layer so that pools can +be managed automatically, based on module type and connect arguments, + simply by calling regular DBAPI connect() methods.""" + +import weakref, string, time, sys +try: + import cPickle as pickle +except: + import pickle + +from sqlalchemy import util, exceptions, logging +from sqlalchemy import queue as Queue + +try: + import thread +except: + import dummy_thread as thread + +proxies = {} + +def manage(module, **params): + """given a DBAPI2 module and pool management parameters, returns a proxy for the module + that will automatically pool connections, creating new connection pools for each + distinct set of connection arguments sent to the decorated module's connect() function. + + Arguments: + module : a DBAPI2 database module. + + poolclass=QueuePool : the class used by the pool module to provide pooling. + + Options: + See Pool for options. + """ + try: + return proxies[module] + except KeyError: + return proxies.setdefault(module, _DBProxy(module, **params)) + +def clear_managers(): + """removes all current DBAPI2 managers. all pools and connections are disposed.""" + for manager in proxies.values(): + manager.close() + proxies.clear() + +class Pool(object): + """Base Pool class. This is an abstract class, which is implemented by various subclasses + including: + + QueuePool - pools multiple connections using Queue.Queue + + SingletonThreadPool - stores a single connection per execution thread + + NullPool - doesnt do any pooling; opens and closes connections + + AssertionPool - stores only one connection, and asserts that only one connection is checked out at a time. + + the main argument, "creator", is a callable function that returns a newly connected DBAPI connection + object. + + Options that are understood by Pool are: + + echo=False : if set to True, connections being pulled and retrieved from/to the pool will + be logged to the standard output, as well as pool sizing information. Echoing can also + be achieved by enabling logging for the "sqlalchemy.pool" namespace. + + use_threadlocal=True : if set to True, repeated calls to connect() within the same + application thread will be guaranteed to return the same connection object, if one has + already been retrieved from the pool and has not been returned yet. This allows code to + retrieve a connection from the pool, and then while still holding on to that connection, + to call other functions which also ask the pool for a connection of the same arguments; + those functions will act upon the same connection that the calling method is using. + + recycle=-1 : if set to non -1, a number of seconds between connection recycling, which + means upon checkout, if this timeout is surpassed the connection will be closed and replaced + with a newly opened connection. + + auto_close_cursors = True : cursors, returned by connection.cursor(), are tracked and are + automatically closed when the connection is returned to the pool. some DBAPIs like MySQLDB + become unstable if cursors remain open. + + disallow_open_cursors = False : if auto_close_cursors is False, and disallow_open_cursors is True, + will raise an exception if an open cursor is detected upon connection checkin. + + If auto_close_cursors and disallow_open_cursors are both False, then no cursor processing + occurs upon checkin. + + """ + def __init__(self, creator, recycle=-1, echo=None, use_threadlocal=False, auto_close_cursors=True, disallow_open_cursors=False): + self.logger = logging.instance_logger(self) + self._threadconns = weakref.WeakValueDictionary() + self._creator = creator + self._recycle = recycle + self._use_threadlocal = use_threadlocal + self.auto_close_cursors = auto_close_cursors + self.disallow_open_cursors = disallow_open_cursors + self.echo = echo + echo = logging.echo_property() + + def unique_connection(self): + return _ConnectionFairy(self).checkout() + + def create_connection(self): + return _ConnectionRecord(self) + + def connect(self): + if not self._use_threadlocal: + return _ConnectionFairy(self).checkout() + + try: + return self._threadconns[thread.get_ident()].connfairy().checkout() + except KeyError: + agent = _ConnectionFairy(self).checkout() + self._threadconns[thread.get_ident()] = agent._threadfairy + return agent + + def return_conn(self, agent): + self.do_return_conn(agent._connection_record) + + def get(self): + return self.do_get() + + def do_get(self): + raise NotImplementedError() + + def do_return_conn(self, conn): + raise NotImplementedError() + + def status(self): + raise NotImplementedError() + + def log(self, msg): + self.logger.info(msg) + + def dispose(self): + raise NotImplementedError() + + +class _ConnectionRecord(object): + def __init__(self, pool): + self.__pool = pool + self.connection = self.__connect() + def close(self): + self.__pool.log("Closing connection %s" % repr(self.connection)) + self.connection.close() + def invalidate(self): + self.__pool.log("Invalidate connection %s" % repr(self.connection)) + self.__close() + self.connection = None + def get_connection(self): + if self.connection is None: + self.connection = self.__connect() + elif (self.__pool._recycle > -1 and time.time() - self.starttime > self.__pool._recycle): + self.__pool.log("Connection %s exceeded timeout; recycling" % repr(self.connection)) + self.__close() + self.connection = self.__connect() + return self.connection + def __close(self): + try: + self.__pool.log("Closing connection %s" % (repr(self.connection))) + self.connection.close() + except Exception, e: + self.__pool.log("Connection %s threw an error on close: %s" % (repr(self.connection), str(e))) + def __connect(self): + try: + self.starttime = time.time() + connection = self.__pool._creator() + self.__pool.log("Created new connection %s" % repr(connection)) + return connection + except Exception, e: + self.__pool.log("Error on connect(): %s" % (str(e))) + raise + +class _ThreadFairy(object): + """marks a thread identifier as owning a connection, for a thread local pool.""" + def __init__(self, connfairy): + self.connfairy = weakref.ref(connfairy) + +class _ConnectionFairy(object): + """proxies a DBAPI connection object and provides return-on-dereference support""" + def __init__(self, pool): + self._threadfairy = _ThreadFairy(self) + self.cursors = weakref.WeakKeyDictionary() + self.__pool = pool + self.__counter = 0 + try: + self._connection_record = pool.get() + self.connection = self._connection_record.get_connection() + except: + self.connection = None # helps with endless __getattr__ loops later on + self._connection_record = None + raise + if self.__pool.echo: + self.__pool.log("Connection %s checked out from pool" % repr(self.connection)) + def invalidate(self): + if self.connection is None: + raise exceptions.InvalidRequestError("This connection is closed") + self._connection_record.invalidate() + self.connection = None + self.cursors = None + self._close() + def cursor(self, *args, **kwargs): + try: + return _CursorFairy(self, self.connection.cursor(*args, **kwargs)) + except Exception, e: + self.invalidate() + raise + def __getattr__(self, key): + return getattr(self.connection, key) + def checkout(self): + if self.connection is None: + raise exceptions.InvalidRequestError("This connection is closed") + self.__counter +=1 + return self + def close_open_cursors(self): + for c in list(self.cursors): + c.close() + def close(self): + self.__counter -=1 + if self.__counter == 0: + self._close() + def __del__(self): + self._close() + def _close(self): + if self.cursors is not None: + # cursors should be closed before connection is returned to the pool. some dbapis like + # mysql have real issues if they are not. + if self.__pool.auto_close_cursors: + self.close_open_cursors() + elif self.__pool.disallow_open_cursors: + if len(self.cursors): + raise exceptions.InvalidRequestError("This connection still has %d open cursors" % len(self.cursors)) + if self.connection is not None: + try: + self.connection.rollback() + except: + # damn mysql -- (todo look for NotSupportedError) + pass + if self._connection_record is not None: + if self.__pool.echo: + self.__pool.log("Connection %s being returned to pool" % repr(self.connection)) + self.__pool.return_conn(self) + self._connection_record = None + self._threadfairy = None + +class _CursorFairy(object): + def __init__(self, parent, cursor): + self.__parent = parent + self.__parent.cursors[self]=True + self.cursor = cursor + def close(self): + if self in self.__parent.cursors: + del self.__parent.cursors[self] + self.cursor.close() + def __getattr__(self, key): + return getattr(self.cursor, key) + +class SingletonThreadPool(Pool): + """Maintains one connection per each thread, never moving a connection to a thread + other than the one which it was created in. + + this is used for SQLite, which both does not handle multithreading by default, + and also requires a singleton connection if a :memory: database is being used. + + options are the same as those of Pool, as well as: + + pool_size=5 - the number of threads in which to maintain connections at once.""" + def __init__(self, creator, pool_size=5, **params): + params['use_threadlocal'] = True + Pool.__init__(self, creator, **params) + self._conns = {} + self.size = pool_size + + def dispose(self): + for key, conn in self._conns.items(): + try: + conn.close() + except: + # sqlite won't even let you close a conn from a thread that didn't create it + pass + del self._conns[key] + + def dispose_local(self): + try: + del self._conns[thread.get_ident()] + except KeyError: + pass + + def cleanup(self): + for key in self._conns.keys(): + try: + del self._conns[key] + except KeyError: + pass + if len(self._conns) <= self.size: + return + + def status(self): + return "SingletonThreadPool id:%d thread:%d size: %d" % (id(self), thread.get_ident(), len(self._conns)) + + def do_return_conn(self, conn): + pass + + def do_get(self): + try: + return self._conns[thread.get_ident()] + except KeyError: + c = self.create_connection() + self._conns[thread.get_ident()] = c + if len(self._conns) > self.size: + self.cleanup() + return c + +class QueuePool(Pool): + """uses Queue.Queue to maintain a fixed-size list of connections. + + Arguments include all those used by the base Pool class, as well as: + + pool_size=5 : the size of the pool to be maintained. This is the + largest number of connections that will be kept persistently in the pool. Note that the + pool begins with no connections; once this number of connections is requested, that + number of connections will remain. + + max_overflow=10 : the maximum overflow size of the pool. When the number of checked-out + connections reaches the size set in pool_size, additional connections will be returned up + to this limit. When those additional connections are returned to the pool, they are + disconnected and discarded. It follows then that the total number of simultaneous + connections the pool will allow is pool_size + max_overflow, and the total number of + "sleeping" connections the pool will allow is pool_size. max_overflow can be set to -1 to + indicate no overflow limit; no limit will be placed on the total number of concurrent + connections. + + timeout=30 : the number of seconds to wait before giving up on returning a connection + """ + def __init__(self, creator, pool_size = 5, max_overflow = 10, timeout=30, **params): + Pool.__init__(self, creator, **params) + self._pool = Queue.Queue(pool_size) + self._overflow = 0 - pool_size + self._max_overflow = max_overflow + self._timeout = timeout + + def do_return_conn(self, conn): + try: + self._pool.put(conn, False) + except Queue.Full: + self._overflow -= 1 + + def do_get(self): + try: + return self._pool.get(self._max_overflow > -1 and self._overflow >= self._max_overflow, self._timeout) + except Queue.Empty: + if self._max_overflow > -1 and self._overflow >= self._max_overflow: + raise exceptions.TimeoutError("QueuePool limit of size %d overflow %d reached, connection timed out" % (self.size(), self.overflow())) + self._overflow += 1 + return self.create_connection() + + def dispose(self): + while True: + try: + conn = self._pool.get(False) + conn.close() + except Queue.Empty: + break + + def status(self): + tup = (self.size(), self.checkedin(), self.overflow(), self.checkedout()) + return "Pool size: %d Connections in pool: %d Current Overflow: %d Current Checked out connections: %d" % tup + + def size(self): + return self._pool.maxsize + + def checkedin(self): + return self._pool.qsize() + + def overflow(self): + return self._overflow + + def checkedout(self): + return self._pool.maxsize - self._pool.qsize() + self._overflow + +class NullPool(Pool): + """a Pool implementation which does not pool connections; instead + it literally opens and closes the underlying DBAPI connection per each connection open/close.""" + def status(self): + return "NullPool" + + def do_return_conn(self, conn): + conn.close() + + def do_return_invalid(self, conn): + pass + + def do_get(self): + return self.create_connection() + +class AssertionPool(Pool): + """a Pool implementation which will raise an exception + if more than one connection is checked out at a time. Useful for debugging + code that is using more connections than desired. + + TODO: modify this to handle an arbitrary connection count.""" + def __init__(self, creator, **params): + Pool.__init__(self, creator, **params) + self.connection = _ConnectionRecord(self) + self._conn = self.connection + + def status(self): + return "AssertionPool" + + def create_connection(self): + raise "Invalid" + + def do_return_conn(self, conn): + assert conn is self._conn and self.connection is None + self.connection = conn + + def do_return_invalid(self, conn): + raise "Invalid" + + def do_get(self): + assert self.connection is not None + c = self.connection + self.connection = None + return c + +class _DBProxy(object): + """proxies a DBAPI2 connect() call to a pooled connection keyed to the specific connect + parameters. other attributes are proxied through via __getattr__.""" + + def __init__(self, module, poolclass = QueuePool, **params): + """ + module is a DBAPI2 module + poolclass is a Pool class, defaulting to QueuePool. + other parameters are sent to the Pool object's constructor. + """ + self.module = module + self.params = params + self.poolclass = poolclass + self.pools = {} + + def close(self): + for key in self.pools.keys(): + del self.pools[key] + + def __del__(self): + self.close() + + def __getattr__(self, key): + return getattr(self.module, key) + + def get_pool(self, *args, **params): + key = self._serialize(*args, **params) + try: + return self.pools[key] + except KeyError: + pool = self.poolclass(lambda: self.module.connect(*args, **params), **self.params) + self.pools[key] = pool + return pool + + def connect(self, *args, **params): + """connects to a database using this DBProxy's module and the given connect + arguments. if the arguments match an existing pool, the connection will be returned + from the pool's current thread-local connection instance, or if there is no + thread-local connection instance it will be checked out from the set of pooled + connections. If the pool has no available connections and allows new connections to + be created, a new database connection will be made.""" + return self.get_pool(*args, **params).connect() + + def dispose(self, *args, **params): + """disposes the connection pool referenced by the given connect arguments.""" + key = self._serialize(*args, **params) + try: + del self.pools[key] + except KeyError: + pass + + def _serialize(self, *args, **params): + return pickle.dumps([args, params]) + diff --git a/spyce-2.1/sqlalchemy/pool.pyc b/spyce-2.1/sqlalchemy/pool.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f0a2cd2043457a266cf716a1b24d2e22fbbfe88 Binary files /dev/null and b/spyce-2.1/sqlalchemy/pool.pyc differ diff --git a/spyce-2.1/sqlalchemy/queue.py b/spyce-2.1/sqlalchemy/queue.py new file mode 100755 index 0000000000000000000000000000000000000000..49bb4badf3865e52edc9d2218cfd0343f851f059 --- /dev/null +++ b/spyce-2.1/sqlalchemy/queue.py @@ -0,0 +1,182 @@ +"""an adaptation of Py2.3/2.4's Queue module which supports reentrant behavior, +using RLock instead of Lock for its mutex object. +this is to support the connection pool's usage of __del__ to return connections +to the underlying Queue, which can apparently in extremely rare cases be invoked +within the get() method of the Queue itself, producing a put() inside the get() +and therefore a reentrant condition.""" + +from time import time as _time + +try: + # py2.4 deque class + from collections import deque +except: + # roll our own... + class deque(list): + def popleft(self): + return self.pop(0) + +__all__ = ['Empty', 'Full', 'Queue'] + +class Empty(Exception): + "Exception raised by Queue.get(block=0)/get_nowait()." + pass + +class Full(Exception): + "Exception raised by Queue.put(block=0)/put_nowait()." + pass + +class Queue: + def __init__(self, maxsize=0): + """Initialize a queue object with a given maximum size. + + If maxsize is <= 0, the queue size is infinite. + """ + try: + import threading + except ImportError: + import dummy_threading as threading + self._init(maxsize) + # mutex must be held whenever the queue is mutating. All methods + # that acquire mutex must release it before returning. mutex + # is shared between the two conditions, so acquiring and + # releasing the conditions also acquires and releases mutex. + self.mutex = threading.RLock() + # Notify not_empty whenever an item is added to the queue; a + # thread waiting to get is notified then. + self.not_empty = threading.Condition(self.mutex) + # Notify not_full whenever an item is removed from the queue; + # a thread waiting to put is notified then. + self.not_full = threading.Condition(self.mutex) + + def qsize(self): + """Return the approximate size of the queue (not reliable!).""" + self.mutex.acquire() + n = self._qsize() + self.mutex.release() + return n + + def empty(self): + """Return True if the queue is empty, False otherwise (not reliable!).""" + self.mutex.acquire() + n = self._empty() + self.mutex.release() + return n + + def full(self): + """Return True if the queue is full, False otherwise (not reliable!).""" + self.mutex.acquire() + n = self._full() + self.mutex.release() + return n + + def put(self, item, block=True, timeout=None): + """Put an item into the queue. + + If optional args 'block' is true and 'timeout' is None (the default), + block if necessary until a free slot is available. If 'timeout' is + a positive number, it blocks at most 'timeout' seconds and raises + the Full exception if no free slot was available within that time. + Otherwise ('block' is false), put an item on the queue if a free slot + is immediately available, else raise the Full exception ('timeout' + is ignored in that case). + """ + self.not_full.acquire() + try: + if not block: + if self._full(): + raise Full + elif timeout is None: + while self._full(): + self.not_full.wait() + else: + if timeout < 0: + raise ValueError("'timeout' must be a positive number") + endtime = _time() + timeout + while self._full(): + remaining = endtime - _time() + if remaining <= 0.0: + raise Full + self.not_full.wait(remaining) + self._put(item) + self.not_empty.notify() + finally: + self.not_full.release() + + def put_nowait(self, item): + """Put an item into the queue without blocking. + + Only enqueue the item if a free slot is immediately available. + Otherwise raise the Full exception. + """ + return self.put(item, False) + + def get(self, block=True, timeout=None): + """Remove and return an item from the queue. + + If optional args 'block' is true and 'timeout' is None (the default), + block if necessary until an item is available. If 'timeout' is + a positive number, it blocks at most 'timeout' seconds and raises + the Empty exception if no item was available within that time. + Otherwise ('block' is false), return an item if one is immediately + available, else raise the Empty exception ('timeout' is ignored + in that case). + """ + self.not_empty.acquire() + try: + if not block: + if self._empty(): + raise Empty + elif timeout is None: + while self._empty(): + self.not_empty.wait() + else: + if timeout < 0: + raise ValueError("'timeout' must be a positive number") + endtime = _time() + timeout + while self._empty(): + remaining = endtime - _time() + if remaining <= 0.0: + raise Empty + self.not_empty.wait(remaining) + item = self._get() + self.not_full.notify() + return item + finally: + self.not_empty.release() + + def get_nowait(self): + """Remove and return an item from the queue without blocking. + + Only get an item if one is immediately available. Otherwise + raise the Empty exception. + """ + return self.get(False) + + # Override these methods to implement other queue organizations + # (e.g. stack or priority queue). + # These will only be called with appropriate locks held + + # Initialize the queue representation + def _init(self, maxsize): + self.maxsize = maxsize + self.queue = deque() + + def _qsize(self): + return len(self.queue) + + # Check whether the queue is empty + def _empty(self): + return not self.queue + + # Check whether the queue is full + def _full(self): + return self.maxsize > 0 and len(self.queue) == self.maxsize + + # Put a new item in the queue + def _put(self, item): + self.queue.append(item) + + # Get an item from the queue + def _get(self): + return self.queue.popleft() diff --git a/spyce-2.1/sqlalchemy/queue.pyc b/spyce-2.1/sqlalchemy/queue.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81b103976c33834dd8eb8e3a7aab9f65a62b0ebc Binary files /dev/null and b/spyce-2.1/sqlalchemy/queue.pyc differ diff --git a/spyce-2.1/sqlalchemy/schema.py b/spyce-2.1/sqlalchemy/schema.py new file mode 100755 index 0000000000000000000000000000000000000000..68022f70b6a0399d14bc21fd775ed8cc7229cf0a --- /dev/null +++ b/spyce-2.1/sqlalchemy/schema.py @@ -0,0 +1,974 @@ +# schema.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""the schema module provides the building blocks for database metadata. This means +all the entities within a SQL database that we might want to look at, modify, or create +and delete are described by these objects, in a database-agnostic way. + +A structure of SchemaItems also provides a "visitor" interface which is the primary +method by which other methods operate upon the schema. The SQL package extends this +structure with its own clause-specific objects as well as the visitor interface, so that +the schema package "plugs in" to the SQL package. + +""" +from sqlalchemy import sql, types, exceptions,util +import sqlalchemy +import copy, re, string + +__all__ = ['SchemaItem', 'Table', 'Column', 'ForeignKey', 'Sequence', 'Index', 'ForeignKeyConstraint', + 'PrimaryKeyConstraint', 'CheckConstraint', 'UniqueConstraint', 'DefaultGenerator', 'Constraint', + 'MetaData', 'BoundMetaData', 'DynamicMetaData', 'SchemaVisitor', 'PassiveDefault', 'ColumnDefault'] + +class SchemaItem(object): + """base class for items that define a database schema.""" + def _init_items(self, *args): + """initialize the list of child items for this SchemaItem""" + for item in args: + if item is not None: + item._set_parent(self) + def _get_parent(self): + raise NotImplementedError() + def _set_parent(self, parent): + """associate with this SchemaItem's parent object.""" + raise NotImplementedError() + def __repr__(self): + return "%s()" % self.__class__.__name__ + def _derived_metadata(self): + """return the the MetaData to which this item is bound""" + return None + def _get_engine(self): + """return the engine or None if no engine""" + return self._derived_metadata().engine + def get_engine(self): + """return the engine or raise an error if no engine""" + e = self._get_engine() + if e is not None: + return e + else: + raise exceptions.InvalidRequestError("This SchemaItem is not connected to any Engine") + + def _set_casing_strategy(self, name, kwargs, keyname='case_sensitive'): + """set the "case_sensitive" argument sent via keywords to the item's constructor. + + for the purposes of Table's 'schema' property, the name of the variable is + optionally configurable.""" + setattr(self, '_%s_setting' % keyname, kwargs.pop(keyname, None)) + def _determine_case_sensitive(self, name, keyname='case_sensitive'): + """determine the "case_sensitive" value for this item. + + for the purposes of Table's 'schema' property, the name of the variable is + optionally configurable. + + a local non-None value overrides all others. after that, the parent item + (i.e. Column for a Sequence, Table for a Column, MetaData for a Table) is + searched for a non-None setting, traversing each parent until none are found. + finally, case_sensitive is set to True if and only if the name of this item + is not all lowercase. + """ + local = getattr(self, '_%s_setting' % keyname, None) + if local is not None: + return local + parent = self + while parent is not None: + parent = parent._get_parent() + if parent is not None: + parentval = getattr(parent, '_case_sensitive_setting', None) + if parentval is not None: + return parentval + return name is not None and name.lower() != name + def _get_case_sensitive(self): + try: + return self.__case_sensitive + except AttributeError: + self.__case_sensitive = self._determine_case_sensitive(self.name) + return self.__case_sensitive + case_sensitive = property(_get_case_sensitive) + + engine = property(lambda s:s._get_engine()) + metadata = property(lambda s:s._derived_metadata()) + +def _get_table_key(name, schema): + if schema is None: + return name + else: + return schema + "." + name + +class _TableSingleton(type): + """a metaclass used by the Table object to provide singleton behavior.""" + def __call__(self, name, metadata, *args, **kwargs): + if isinstance(metadata, sql.Executor): + # backwards compatibility - get a BoundSchema associated with the engine + engine = metadata + if not hasattr(engine, '_legacy_metadata'): + engine._legacy_metadata = BoundMetaData(engine) + metadata = engine._legacy_metadata + elif metadata is not None and not isinstance(metadata, MetaData): + # they left MetaData out, so assume its another SchemaItem, add it to *args + args = list(args) + args.insert(0, metadata) + metadata = None + + if metadata is None: + metadata = default_metadata + + name = str(name) # in case of incoming unicode + schema = kwargs.get('schema', None) + autoload = kwargs.pop('autoload', False) + autoload_with = kwargs.pop('autoload_with', False) + mustexist = kwargs.pop('mustexist', False) + useexisting = kwargs.pop('useexisting', False) + key = _get_table_key(name, schema) + try: + table = metadata.tables[key] + if len(args): + if not useexisting: + raise exceptions.ArgumentError("Table '%s.%s' is already defined for this MetaData instance." % (schema, name)) + return table + except KeyError: + if mustexist: + raise exceptions.ArgumentError("Table '%s.%s' not defined" % (schema, name)) + table = type.__call__(self, name, metadata, **kwargs) + table._set_parent(metadata) + # load column definitions from the database if 'autoload' is defined + # we do it after the table is in the singleton dictionary to support + # circular foreign keys + if autoload: + try: + if autoload_with: + autoload_with.reflecttable(table) + else: + metadata.get_engine().reflecttable(table) + except exceptions.NoSuchTableError: + del metadata.tables[key] + raise + # initialize all the column, etc. objects. done after + # reflection to allow user-overrides + table._init_items(*args) + return table + + +class Table(SchemaItem, sql.TableClause): + """represents a relational database table. This subclasses sql.TableClause to provide + a table that is "wired" to an engine. Whereas TableClause represents a table as its + used in a SQL expression, Table represents a table as its created in the database. + + Be sure to look at sqlalchemy.sql.TableImpl for additional methods defined on a Table.""" + __metaclass__ = _TableSingleton + + def __init__(self, name, metadata, **kwargs): + """Construct a Table. + + Table objects can be constructed directly. The init method is actually called via + the TableSingleton metaclass. Arguments are: + + name : the name of this table, exactly as it appears, or will appear, in the database. + This property, along with the "schema", indicates the "singleton identity" of this table. + Further tables constructed with the same name/schema combination will return the same + Table instance. + + *args : should contain a listing of the Column objects for this table. + + **kwargs : options include: + + schema=None : the "schema name" for this table, which is required if the table resides in a + schema other than the default selected schema for the engine's database connection. + + autoload=False : the Columns for this table should be reflected from the database. Usually + there will be no Column objects in the constructor if this property is set. + + mustexist=False : indicates that this Table must already have been defined elsewhere in the application, + else an exception is raised. + + useexisting=False : indicates that if this Table was already defined elsewhere in the application, disregard + the rest of the constructor arguments. If this flag and the "redefine" flag are not set, constructing + the same table twice will result in an exception. + + owner=None : optional owning user of this table. useful for databases such as Oracle to aid in table + reflection. + + quote=False : indicates that the Table identifier must be properly escaped and quoted before being sent + to the database. This flag overrides all other quoting behavior. + + quote_schema=False : indicates that the Namespace identifier must be properly escaped and quoted before being sent + to the database. This flag overrides all other quoting behavior. + + case_sensitive=True : indicates that the identifier should be interpreted by the database in the natural case for identifiers. + Mixed case is not sufficient to cause this identifier to be quoted; it must contain an illegal character. + + case_sensitive_schema=True : indicates that the identifier should be interpreted by the database in the natural case for identifiers. + Mixed case is not sufficient to cause this identifier to be quoted; it must contain an illegal character. + """ + super(Table, self).__init__(name) + self._metadata = metadata + self.schema = kwargs.pop('schema', None) + self.indexes = util.Set() + self.constraints = util.Set() + self.primary_key = PrimaryKeyConstraint() + self.quote = kwargs.get('quote', False) + self.quote_schema = kwargs.get('quote_schema', False) + if self.schema is not None: + self.fullname = "%s.%s" % (self.schema, self.name) + else: + self.fullname = self.name + self.owner = kwargs.pop('owner', None) + + self._set_casing_strategy(name, kwargs) + self._set_casing_strategy(self.schema or '', kwargs, keyname='case_sensitive_schema') + self.kwargs = kwargs + + def _get_case_sensitive_schema(self): + try: + return getattr(self, '_case_sensitive_schema') + except AttributeError: + setattr(self, '_case_sensitive_schema', self._determine_case_sensitive(self.schema or '', keyname='case_sensitive_schema')) + return getattr(self, '_case_sensitive_schema') + case_sensitive_schema = property(_get_case_sensitive_schema) + + def _set_primary_key(self, pk): + if getattr(self, '_primary_key', None) in self.constraints: + self.constraints.remove(self._primary_key) + self._primary_key = pk + self.constraints.add(pk) + primary_key = property(lambda s:s._primary_key, _set_primary_key) + + def _derived_metadata(self): + return self._metadata + def __repr__(self): + return "Table(%s)" % string.join( + [repr(self.name)] + [repr(self.metadata)] + + [repr(x) for x in self.columns] + + ["%s=%s" % (k, repr(getattr(self, k))) for k in ['schema']] + , ',\n') + + def __str__(self): + return _get_table_key(self.name, self.schema) + + def append_column(self, column): + """append a Column to this Table.""" + column._set_parent(self) + def append_constraint(self, constraint): + """append a Constraint to this Table.""" + constraint._set_parent(self) + + def _get_parent(self): + return self._metadata + def _set_parent(self, metadata): + metadata.tables[_get_table_key(self.name, self.schema)] = self + self._metadata = metadata + def accept_schema_visitor(self, visitor, traverse=True): + if traverse: + for c in self.columns: + c.accept_schema_visitor(visitor, True) + return visitor.visit_table(self) + + def exists(self, connectable=None): + """return True if this table exists.""" + if connectable is None: + connectable = self.get_engine() + + def do(conn): + e = conn.engine + return e.dialect.has_table(conn, self.name) + return connectable.run_callable(do) + + def create(self, connectable=None, checkfirst=False): + """issue a CREATE statement for this table. + + see also metadata.create_all().""" + self.metadata.create_all(connectable=connectable, checkfirst=checkfirst, tables=[self]) + def drop(self, connectable=None, checkfirst=False): + """issue a DROP statement for this table. + + see also metadata.drop_all().""" + self.metadata.drop_all(connectable=connectable, checkfirst=checkfirst, tables=[self]) + def tometadata(self, metadata, schema=None): + """return a copy of this Table associated with a different MetaData.""" + try: + if schema is None: + schema = self.schema + key = _get_table_key(self.name, schema) + return metadata.tables[key] + except KeyError: + args = [] + for c in self.columns: + args.append(c.copy()) + for c in self.constraints: + args.append(c.copy()) + return Table(self.name, metadata, schema=schema, *args) + +class Column(SchemaItem, sql._ColumnClause): + """represents a column in a database table. this is a subclass of sql.ColumnClause and + represents an actual existing table in the database, in a similar fashion as TableClause/Table.""" + def __init__(self, name, type, *args, **kwargs): + """constructs a new Column object. Arguments are: + + name : the name of this column. this should be the identical name as it appears, + or will appear, in the database. + + type: the TypeEngine for this column. + This can be any subclass of types.AbstractType, including the database-agnostic types defined + in the types module, database-specific types defined within specific database modules, or user-defined types. + + *args: Constraint, ForeignKey, ColumnDefault and Sequence objects should be added as list values. + + **kwargs : keyword arguments include: + + key=None : an optional "alias name" for this column. The column will then be identified everywhere + in an application, including the column list on its Table, by this key, and not the given name. + Generated SQL, however, will still reference the column by its actual name. + + primary_key=False : True if this column is a primary key column. Multiple columns can have this flag + set to specify composite primary keys. As an alternative, the primary key of a Table can be specified + via an explicit PrimaryKeyConstraint instance appended to the Table's list of objects. + + nullable=True : True if this column should allow nulls. Defaults to True unless this column is a primary + key column. + + default=None : a scalar, python callable, or ClauseElement representing the "default value" for this column, + which will be invoked upon insert if this column is not present in the insert list or is given a value + of None. The default expression will be converted into a ColumnDefault object upon initialization. + + _is_oid=False : used internally to indicate that this column is used as the quasi-hidden "oid" column + + index=False : Indicates that this column is + indexed. The name of the index is autogenerated. + to specify indexes with explicit names or indexes that contain multiple + columns, use the Index construct instead. + + unique=False : Indicates that this column + contains a unique constraint, or if index=True as well, indicates + that the Index should be created with the unique flag. + To specify multiple columns in the constraint/index or to specify an + explicit name, use the UniqueConstraint or Index constructs instead. + + autoincrement=True : Indicates that integer-based primary key columns should have autoincrementing behavior, + if supported by the underlying database. This will affect CREATE TABLE statements such that they will + use the databases "auto-incrementing" keyword (such as SERIAL for postgres, AUTO_INCREMENT for mysql) and will + also affect the behavior of some dialects during INSERT statement execution such that they will assume primary + key values are created in this manner. If a Column has an explicit ColumnDefault object (such as via the + "default" keyword, or a Sequence or PassiveDefault), then the value of autoincrement is ignored and is assumed + to be False. autoincrement value is only significant for a column with a type or subtype of Integer. + + quote=False : indicates that the Column identifier must be properly escaped and quoted before being sent + to the database. This flag should normally not be required as dialects can auto-detect conditions where quoting + is required. + + case_sensitive=True : indicates that the identifier should be interpreted by the database in the natural case for identifiers. + Mixed case is not sufficient to cause this identifier to be quoted; it must contain an illegal character. + """ + name = str(name) # in case of incoming unicode + super(Column, self).__init__(name, None, type) + self.args = args + self.key = kwargs.pop('key', name) + self._primary_key = kwargs.pop('primary_key', False) + self.nullable = kwargs.pop('nullable', not self.primary_key) + self._is_oid = kwargs.pop('_is_oid', False) + self.default = kwargs.pop('default', None) + self.index = kwargs.pop('index', None) + self.unique = kwargs.pop('unique', None) + self.quote = kwargs.pop('quote', False) + self._set_casing_strategy(name, kwargs) + self.onupdate = kwargs.pop('onupdate', None) + self.autoincrement = kwargs.pop('autoincrement', True) + self.constraints = util.Set() + self.__originating_column = self + self._foreign_keys = util.OrderedSet() + if len(kwargs): + raise exceptions.ArgumentError("Unknown arguments passed to Column: " + repr(kwargs.keys())) + + primary_key = util.SimpleProperty('_primary_key') + foreign_keys = util.SimpleProperty('_foreign_keys') + columns = property(lambda self:[self]) + + def __str__(self): + if self.table is not None: + if self.table.named_with_column(): + return self.table.name + "." + self.name + else: + return self.name + return self.name + else: + return self.name + + def _derived_metadata(self): + return self.table.metadata + def _get_engine(self): + return self.table.engine + + def append_foreign_key(self, fk): + fk._set_parent(self) + + def __repr__(self): + return "Column(%s)" % string.join( + [repr(self.name)] + [repr(self.type)] + + [repr(x) for x in self.foreign_keys if x is not None] + + ["%s=%s" % (k, repr(getattr(self, k))) for k in ['key', 'primary_key', 'nullable', 'default', 'onupdate']] + , ',') + + def _get_parent(self): + return self.table + + def _set_parent(self, table): + if getattr(self, 'table', None) is not None: + raise exceptions.ArgumentError("this Column already has a table!") + if not self._is_oid: + table._columns.add(self) + if self.primary_key: + table.primary_key.add(self) + self.table = table + + if self.index: + if isinstance(self.index, str): + raise exceptions.ArgumentError("The 'index' keyword argument on Column is boolean only. To create indexes with a specific name, append an explicit Index object to the Table's list of elements.") + Index('ix_%s' % self._label, self, unique=self.unique) + elif self.unique: + if isinstance(self.unique, str): + raise exceptions.ArgumentError("The 'unique' keyword argument on Column is boolean only. To create unique constraints or indexes with a specific name, append an explicit UniqueConstraint or Index object to the Table's list of elements.") + table.append_constraint(UniqueConstraint(self.key)) + + toinit = list(self.args) + if self.default is not None: + toinit.append(ColumnDefault(self.default)) + if self.onupdate is not None: + toinit.append(ColumnDefault(self.onupdate, for_update=True)) + self._init_items(*toinit) + self.args = None + + def copy(self): + """creates a copy of this Column, unitialized. this is used in Table.tometadata.""" + return Column(self.name, self.type, self.default, key = self.key, primary_key = self.primary_key, nullable = self.nullable, _is_oid = self._is_oid, case_sensitive=self._case_sensitive_setting, quote=self.quote) + + def _make_proxy(self, selectable, name = None): + """create a "proxy" for this column. + + This is a copy of this Column referenced + by a different parent (such as an alias or select statement)""" + fk = [ForeignKey(f._colspec) for f in self.foreign_keys] + c = Column(name or self.name, self.type, self.default, key = name or self.key, primary_key = self.primary_key, nullable = self.nullable, _is_oid = self._is_oid, quote=self.quote, *fk) + c.table = selectable + c.orig_set = self.orig_set + c.__originating_column = self.__originating_column + if not c._is_oid: + selectable.columns.add(c) + if self.primary_key: + selectable.primary_key.add(c) + [c._init_items(f) for f in fk] + return c + + def _case_sens(self): + """redirect the 'case_sensitive' accessor to use the ultimate parent column which created + this one.""" + return self.__originating_column._get_case_sensitive() + case_sensitive = property(_case_sens) + + def accept_schema_visitor(self, visitor, traverse=True): + """traverses the given visitor to this Column's default and foreign key object, + then calls visit_column on the visitor.""" + if traverse: + if self.default is not None: + self.default.accept_schema_visitor(visitor, traverse=True) + if self.onupdate is not None: + self.onupdate.accept_schema_visitor(visitor, traverse=True) + for f in self.foreign_keys: + f.accept_schema_visitor(visitor, traverse=True) + for constraint in self.constraints: + constraint.accept_schema_visitor(visitor, traverse=True) + visitor.visit_column(self) + + +class ForeignKey(SchemaItem): + """defines a column-level ForeignKey constraint between two columns. + + ForeignKey is specified as an argument to a Column object. + + One or more ForeignKey objects are used within a ForeignKeyConstraint + object which represents the table-level constraint definition.""" + def __init__(self, column, constraint=None, use_alter=False, name=None): + """Construct a new ForeignKey object. + + "column" can be a schema.Column object representing the relationship, + or just its string name given as "tablename.columnname". schema can be + specified as "schema.tablename.columnname" + + "constraint" is the owning ForeignKeyConstraint object, if any. if not given, + then a ForeignKeyConstraint will be automatically created and added to the parent table. + """ + if isinstance(column, unicode): + column = str(column) + self._colspec = column + self._column = None + self.constraint = constraint + self.use_alter = use_alter + self.name = name + + def __repr__(self): + return "ForeignKey(%s)" % repr(self._get_colspec()) + + def copy(self): + """produce a copy of this ForeignKey object.""" + return ForeignKey(self._get_colspec()) + + def _get_colspec(self): + if isinstance(self._colspec, str): + return self._colspec + elif self._colspec.table.schema is not None: + return "%s.%s.%s" % (self._colspec.table.schema, self._colspec.table.name, self._colspec.key) + else: + return "%s.%s" % (self._colspec.table.name, self._colspec.key) + + def references(self, table): + """returns True if the given table is referenced by this ForeignKey.""" + return table.corresponding_column(self.column, False) is not None + + def _init_column(self): + # ForeignKey inits its remote column as late as possible, so tables can + # be defined without dependencies + if self._column is None: + if isinstance(self._colspec, str): + # locate the parent table this foreign key is attached to. + # we use the "original" column which our parent column represents + # (its a list of columns/other ColumnElements if the parent table is a UNION) + for c in self.parent.orig_set: + if isinstance(c, Column): + parenttable = c.table + break + else: + raise exceptions.ArgumentError("Parent column '%s' does not descend from a table-attached Column" % str(self.parent)) + m = re.match(r"^([\w_-]+)(?:\.([\w_-]+))?(?:\.([\w_-]+))?$", self._colspec) + if m is None: + raise exceptions.ArgumentError("Invalid foreign key column specification: " + self._colspec) + if m.group(3) is None: + (tname, colname) = m.group(1, 2) + schema = parenttable.schema + else: + (schema,tname,colname) = m.group(1,2,3) + table = Table(tname, parenttable.metadata, mustexist=True, schema=schema) + try: + if colname is None: + key = self.parent + self._column = table.c[self.parent.key] + else: + self._column = table.c[colname] + except KeyError, e: + raise exceptions.ArgumentError("Could not create ForeignKey '%s' on table '%s': table '%s' has no column named '%s'" % (self._colspec, parenttable.name, table.name, e.args[0])) + else: + self._column = self._colspec + # propigate TypeEngine to parent if it didnt have one + if self.parent.type is types.NULLTYPE: + self.parent.type = self._column.type + return self._column + + column = property(lambda s: s._init_column()) + + def accept_schema_visitor(self, visitor, traverse=True): + """calls the visit_foreign_key method on the given visitor.""" + visitor.visit_foreign_key(self) + + def _get_parent(self): + return self.parent + def _set_parent(self, column): + self.parent = column + + if self.constraint is None and isinstance(self.parent.table, Table): + self.constraint = ForeignKeyConstraint([],[], use_alter=self.use_alter, name=self.name) + self.parent.table.append_constraint(self.constraint) + self.constraint._append_fk(self) + + self.parent.foreign_keys.add(self) + self.parent.table.foreign_keys.add(self) + +class DefaultGenerator(SchemaItem): + """Base class for column "default" values.""" + def __init__(self, for_update=False, metadata=None): + self.for_update = for_update + self._metadata = metadata + def _derived_metadata(self): + try: + return self.column.table.metadata + except AttributeError: + return self._metadata + def _get_parent(self): + return getattr(self, 'column', None) + def _set_parent(self, column): + self.column = column + self._metadata = self.column.table.metadata + if self.for_update: + self.column.onupdate = self + else: + self.column.default = self + def execute(self, **kwargs): + return self.get_engine().execute_default(self, **kwargs) + def __repr__(self): + return "DefaultGenerator()" + +class PassiveDefault(DefaultGenerator): + """a default that takes effect on the database side""" + def __init__(self, arg, **kwargs): + super(PassiveDefault, self).__init__(**kwargs) + self.arg = arg + def accept_schema_visitor(self, visitor, traverse=True): + return visitor.visit_passive_default(self) + def __repr__(self): + return "PassiveDefault(%s)" % repr(self.arg) + +class ColumnDefault(DefaultGenerator): + """A plain default value on a column. this could correspond to a constant, + a callable function, or a SQL clause.""" + def __init__(self, arg, **kwargs): + super(ColumnDefault, self).__init__(**kwargs) + self.arg = arg + def accept_schema_visitor(self, visitor, traverse=True): + """calls the visit_column_default method on the given visitor.""" + if self.for_update: + return visitor.visit_column_onupdate(self) + else: + return visitor.visit_column_default(self) + def __repr__(self): + return "ColumnDefault(%s)" % repr(self.arg) + +class Sequence(DefaultGenerator): + """represents a sequence, which applies to Oracle and Postgres databases.""" + def __init__(self, name, start = None, increment = None, optional=False, quote=False, **kwargs): + super(Sequence, self).__init__(**kwargs) + self.name = name + self.start = start + self.increment = increment + self.optional=optional + self.quote = quote + self._set_casing_strategy(name, kwargs) + def __repr__(self): + return "Sequence(%s)" % string.join( + [repr(self.name)] + + ["%s=%s" % (k, repr(getattr(self, k))) for k in ['start', 'increment', 'optional']] + , ',') + def _set_parent(self, column): + super(Sequence, self)._set_parent(column) + column.sequence = self + def create(self): + self.get_engine().create(self) + return self + def drop(self): + self.get_engine().drop(self) + def accept_schema_visitor(self, visitor, traverse=True): + """calls the visit_seauence method on the given visitor.""" + return visitor.visit_sequence(self) + +class Constraint(SchemaItem): + """represents a table-level Constraint such as a composite primary key, foreign key, or unique constraint. + + Implements a hybrid of dict/setlike behavior with regards to the list of underying columns""" + def __init__(self, name=None): + self.name = name + self.columns = sql.ColumnCollection() + def __contains__(self, x): + return x in self.columns + def keys(self): + return self.columns.keys() + def __add__(self, other): + return self.columns + other + def __iter__(self): + return iter(self.columns) + def __len__(self): + return len(self.columns) + def copy(self): + raise NotImplementedError() + def _get_parent(self): + return getattr(self, 'table', None) + +class CheckConstraint(Constraint): + def __init__(self, sqltext, name=None): + super(CheckConstraint, self).__init__(name) + self.sqltext = sqltext + def accept_schema_visitor(self, visitor, traverse=True): + visitor.visit_check_constraint(self) + def _set_parent(self, parent): + self.parent = parent + parent.constraints.add(self) + +class ForeignKeyConstraint(Constraint): + """table-level foreign key constraint, represents a colleciton of ForeignKey objects.""" + def __init__(self, columns, refcolumns, name=None, onupdate=None, ondelete=None, use_alter=False): + super(ForeignKeyConstraint, self).__init__(name) + self.__colnames = columns + self.__refcolnames = refcolumns + self.elements = util.OrderedSet() + self.onupdate = onupdate + self.ondelete = ondelete + if self.name is None and use_alter: + raise exceptions.ArgumentError("Alterable ForeignKey/ForeignKeyConstraint requires a name") + self.use_alter = use_alter + def _set_parent(self, table): + self.table = table + table.constraints.add(self) + for (c, r) in zip(self.__colnames, self.__refcolnames): + self.append_element(c,r) + def accept_schema_visitor(self, visitor, traverse=True): + visitor.visit_foreign_key_constraint(self) + def append_element(self, col, refcol): + fk = ForeignKey(refcol, constraint=self) + fk._set_parent(self.table.c[col]) + self._append_fk(fk) + def _append_fk(self, fk): + self.columns.add(self.table.c[fk.parent.key]) + self.elements.add(fk) + def copy(self): + return ForeignKeyConstraint([x.parent.name for x in self.elements], [x._get_colspec() for x in self.elements], name=self.name, onupdate=self.onupdate, ondelete=self.ondelete) + +class PrimaryKeyConstraint(Constraint): + def __init__(self, *columns, **kwargs): + super(PrimaryKeyConstraint, self).__init__(name=kwargs.pop('name', None)) + self.__colnames = list(columns) + def _set_parent(self, table): + self.table = table + table.primary_key = self + for c in self.__colnames: + self.append_column(table.c[c]) + def accept_schema_visitor(self, visitor, traverse=True): + visitor.visit_primary_key_constraint(self) + def add(self, col): + self.append_column(col) + def append_column(self, col): + self.columns.add(col) + col.primary_key=True + def copy(self): + return PrimaryKeyConstraint(name=self.name, *[c.key for c in self]) + def __eq__(self, other): + return self.columns == other + +class UniqueConstraint(Constraint): + def __init__(self, *columns, **kwargs): + super(UniqueConstraint, self).__init__(name=kwargs.pop('name', None)) + self.__colnames = list(columns) + def _set_parent(self, table): + self.table = table + table.constraints.add(self) + for c in self.__colnames: + self.append_column(table.c[c]) + def append_column(self, col): + self.columns.add(col) + def accept_schema_visitor(self, visitor, traverse=True): + visitor.visit_unique_constraint(self) + +class Index(SchemaItem): + """Represents an index of columns from a database table + """ + def __init__(self, name, *columns, **kwargs): + """Constructs an index object. Arguments are: + + name : the name of the index + + *columns : columns to include in the index. All columns must belong to + the same table, and no column may appear more than once. + + **kw : keyword arguments include: + + unique=True : create a unique index + """ + self.name = name + self.columns = [] + self.table = None + self.unique = kwargs.pop('unique', False) + self._init_items(*columns) + + def _derived_metadata(self): + return self.table.metadata + def _init_items(self, *args): + for column in args: + self.append_column(column) + def _get_parent(self): + return self.table + def _set_parent(self, table): + self.table = table + table.indexes.add(self) + + def append_column(self, column): + # make sure all columns are from the same table + # and no column is repeated + if self.table is None: + self._set_parent(column.table) + elif column.table != self.table: + # all columns muse be from same table + raise exceptions.ArgumentError("All index columns must be from same table. " + "%s is from %s not %s" % (column, + column.table, + self.table)) + elif column.name in [ c.name for c in self.columns ]: + raise exceptions.ArgumentError("A column may not appear twice in the " + "same index (%s already has column %s)" + % (self.name, column)) + self.columns.append(column) + + def create(self, connectable=None): + if connectable is not None: + connectable.create(self) + else: + self.get_engine().create(self) + return self + def drop(self, connectable=None): + if connectable is not None: + connectable.drop(self) + else: + self.get_engine().drop(self) + def accept_schema_visitor(self, visitor, traverse=True): + visitor.visit_index(self) + def __str__(self): + return repr(self) + def __repr__(self): + return 'Index("%s", %s%s)' % (self.name, + ', '.join([repr(c) + for c in self.columns]), + (self.unique and ', unique=True') or '') + +class MetaData(SchemaItem): + """represents a collection of Tables and their associated schema constructs.""" + def __init__(self, name=None, **kwargs): + self.tables = {} + self.name = name + self._set_casing_strategy(name, kwargs) + def is_bound(self): + return False + def clear(self): + self.tables.clear() + + def table_iterator(self, reverse=True, tables=None): + import sqlalchemy.sql_util + if tables is None: + tables = self.tables.values() + else: + tables = util.Set(tables).intersection(self.tables.values()) + sorter = sqlalchemy.sql_util.TableCollection(list(tables)) + return iter(sorter.sort(reverse=reverse)) + def _get_parent(self): + return None + def create_all(self, connectable=None, tables=None, checkfirst=True): + """create all tables stored in this metadata. + + This will conditionally create tables depending on if they do not yet + exist in the database. + + connectable - a Connectable used to access the database; or use the engine + bound to this MetaData. + + tables - optional list of tables, which is a subset of the total + tables in the MetaData (others are ignored)""" + if connectable is None: + connectable = self.get_engine() + connectable.create(self, checkfirst=checkfirst, tables=tables) + + def drop_all(self, connectable=None, tables=None, checkfirst=True): + """drop all tables stored in this metadata. + + This will conditionally drop tables depending on if they currently + exist in the database. + + connectable - a Connectable used to access the database; or use the engine + bound to this MetaData. + + tables - optional list of tables, which is a subset of the total + tables in the MetaData (others are ignored) + """ + if connectable is None: + connectable = self.get_engine() + connectable.drop(self, checkfirst=checkfirst, tables=tables) + + + def accept_schema_visitor(self, visitor, traverse=True): + visitor.visit_metadata(self) + + def _derived_metadata(self): + return self + def _get_engine(self): + if not self.is_bound(): + return None + return self._engine + +class BoundMetaData(MetaData): + """builds upon MetaData to provide the capability to bind to an Engine implementation.""" + def __init__(self, engine_or_url, name=None, **kwargs): + super(BoundMetaData, self).__init__(name, **kwargs) + if isinstance(engine_or_url, str): + self._engine = sqlalchemy.create_engine(engine_or_url, **kwargs) + else: + self._engine = engine_or_url + def is_bound(self): + return True + +class DynamicMetaData(MetaData): + """builds upon MetaData to provide the capability to bind to multiple Engine implementations + on a dynamically alterable, thread-local basis.""" + def __init__(self, name=None, threadlocal=True, **kwargs): + super(DynamicMetaData, self).__init__(name, **kwargs) + if threadlocal: + self.context = util.ThreadLocal() + else: + self.context = self + self.__engines = {} + def connect(self, engine_or_url, **kwargs): + if isinstance(engine_or_url, str): + try: + self.context._engine = self.__engines[engine_or_url] + except KeyError: + e = sqlalchemy.create_engine(engine_or_url, **kwargs) + self.__engines[engine_or_url] = e + self.context._engine = e + else: + if not self.__engines.has_key(engine_or_url): + self.__engines[engine_or_url] = engine_or_url + self.context._engine = engine_or_url + def is_bound(self): + return hasattr(self.context, '_engine') and self.context._engine is not None + def dispose(self): + """disposes all Engines to which this DynamicMetaData has been connected.""" + for e in self.__engines.values(): + e.dispose() + def _get_engine(self): + if hasattr(self.context, '_engine'): + return self.context._engine + else: + return None + engine=property(_get_engine) + +class SchemaVisitor(sql.ClauseVisitor): + """defines the visiting for SchemaItem objects""" + def visit_schema(self, schema): + """visit a generic SchemaItem""" + pass + def visit_table(self, table): + """visit a Table.""" + pass + def visit_column(self, column): + """visit a Column.""" + pass + def visit_foreign_key(self, join): + """visit a ForeignKey.""" + pass + def visit_index(self, index): + """visit an Index.""" + pass + def visit_passive_default(self, default): + """visit a passive default""" + pass + def visit_column_default(self, default): + """visit a ColumnDefault.""" + pass + def visit_column_onupdate(self, onupdate): + """visit a ColumnDefault with the "for_update" flag set.""" + pass + def visit_sequence(self, sequence): + """visit a Sequence.""" + pass + def visit_primary_key_constraint(self, constraint): + pass + def visit_foreign_key_constraint(self, constraint): + pass + def visit_unique_constraint(self, constraint): + pass + def visit_check_constraint(self, constraint): + pass + +default_metadata = DynamicMetaData('default') + + diff --git a/spyce-2.1/sqlalchemy/schema.pyc b/spyce-2.1/sqlalchemy/schema.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e164a3d20b1133d74fc5906faf3411b7d6c07e26 Binary files /dev/null and b/spyce-2.1/sqlalchemy/schema.pyc differ diff --git a/spyce-2.1/sqlalchemy/sql.py b/spyce-2.1/sqlalchemy/sql.py new file mode 100755 index 0000000000000000000000000000000000000000..ce33810a523eb007d03607c27262e8e65710be10 --- /dev/null +++ b/spyce-2.1/sqlalchemy/sql.py @@ -0,0 +1,1668 @@ +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""defines the base components of SQL expression trees.""" + +from sqlalchemy import util, exceptions +from sqlalchemy import types as sqltypes +import string, re, random, sets + +__all__ = ['text', 'table', 'column', 'func', 'select', 'update', 'insert', 'delete', 'join', 'and_', 'or_', 'not_', 'between_', 'case', 'cast', 'union', 'union_all', 'null', 'desc', 'asc', 'outerjoin', 'alias', 'subquery', 'literal', 'bindparam', 'exists', 'extract','AbstractDialect', 'ClauseParameters', 'ClauseVisitor', 'Executor', 'Compiled', 'ClauseElement', 'ColumnElement', 'ColumnCollection', 'FromClause', 'TableClause', 'Select', 'Alias', 'CompoundSelect','Join', 'Selectable'] + +def desc(column): + """return a descending ORDER BY clause element, e.g.: + + order_by = [desc(table1.mycol)] + """ + return _CompoundClause(None, column, "DESC") + +def asc(column): + """return an ascending ORDER BY clause element, e.g.: + + order_by = [asc(table1.mycol)] + """ + return _CompoundClause(None, column, "ASC") + +def outerjoin(left, right, onclause=None, **kwargs): + """return an OUTER JOIN clause element. + + left - the left side of the join + right - the right side of the join + onclause - optional criterion for the ON clause, + is derived from foreign key relationships otherwise + + To chain joins together, use the resulting + Join object's "join()" or "outerjoin()" methods.""" + return Join(left, right, onclause, isouter = True, **kwargs) + +def join(left, right, onclause=None, **kwargs): + """return a JOIN clause element (regular inner join). + + left - the left side of the join + right - the right side of the join + onclause - optional criterion for the ON clause, + is derived from foreign key relationships otherwise + + To chain joins together, use the resulting Join object's + "join()" or "outerjoin()" methods.""" + return Join(left, right, onclause, **kwargs) + +def select(columns=None, whereclause = None, from_obj = [], **kwargs): + """returns a SELECT clause element. + + this can also be called via the table's select() method. + + 'columns' is a list of columns and/or selectable items to select columns from + 'whereclause' is a text or ClauseElement expression which will form the WHERE clause + 'from_obj' is an list of additional "FROM" objects, such as Join objects, which will + extend or override the default "from" objects created from the column list and the + whereclause. + **kwargs - additional parameters for the Select object. + """ + return Select(columns, whereclause = whereclause, from_obj = from_obj, **kwargs) + +def subquery(alias, *args, **kwargs): + return Select(*args, **kwargs).alias(alias) + + +def insert(table, values = None, **kwargs): + """returns an INSERT clause element. + + This can also be called from a table directly via the table's insert() method. + + 'table' is the table to be inserted into. + + 'values' is a dictionary which specifies the column specifications of the INSERT, + and is optional. If left as None, the column specifications are determined from the + bind parameters used during the compile phase of the INSERT statement. If the + bind parameters also are None during the compile phase, then the column + specifications will be generated from the full list of table columns. + + If both 'values' and compile-time bind parameters are present, the compile-time + bind parameters override the information specified within 'values' on a per-key basis. + + The keys within 'values' can be either Column objects or their string identifiers. + Each key may reference one of: a literal data value (i.e. string, number, etc.), a Column object, + or a SELECT statement. If a SELECT statement is specified which references this INSERT + statement's table, the statement will be correlated against the INSERT statement. + """ + return _Insert(table, values, **kwargs) + +def update(table, whereclause = None, values = None, **kwargs): + """returns an UPDATE clause element. + + This can also be called from a table directly via the table's update() method. + + 'table' is the table to be updated. + 'whereclause' is a ClauseElement describing the WHERE condition of the UPDATE statement. + 'values' is a dictionary which specifies the SET conditions of the UPDATE, and is + optional. If left as None, the SET conditions are determined from the bind parameters + used during the compile phase of the UPDATE statement. If the bind parameters also are + None during the compile phase, then the SET conditions will be generated from the full + list of table columns. + + If both 'values' and compile-time bind parameters are present, the compile-time bind + parameters override the information specified within 'values' on a per-key basis. + + The keys within 'values' can be either Column objects or their string identifiers. Each + key may reference one of: a literal data value (i.e. string, number, etc.), a Column + object, or a SELECT statement. If a SELECT statement is specified which references this + UPDATE statement's table, the statement will be correlated against the UPDATE statement. + """ + return _Update(table, whereclause, values, **kwargs) + +def delete(table, whereclause = None, **kwargs): + """returns a DELETE clause element. + + This can also be called from a table directly via the table's delete() method. + + 'table' is the table to be updated. + 'whereclause' is a ClauseElement describing the WHERE condition of the UPDATE statement. + """ + return _Delete(table, whereclause, **kwargs) + +def and_(*clauses): + """joins a list of clauses together by the AND operator. the & operator can be used as well.""" + return _compound_clause('AND', *clauses) + +def or_(*clauses): + """joins a list of clauses together by the OR operator. the | operator can be used as well.""" + return _compound_clause('OR', *clauses) + +def not_(clause): + """returns a negation of the given clause, i.e. NOT(clause). the ~ operator can be used as well.""" + return clause._negate() + +def between(ctest, cleft, cright): + """ returns BETWEEN predicate clause (clausetest BETWEEN clauseleft AND clauseright). + + this is better called off a ColumnElement directly, i.e. + + column.between(value1, value2). + """ + return _BooleanExpression(ctest, and_(_check_literal(cleft, ctest.type), _check_literal(cright, ctest.type)), 'BETWEEN') +between_ = between + +def case(whens, value=None, else_=None): + """ SQL CASE statement -- whens are a sequence of pairs to be translated into "when / then" clauses; + optional [value] for simple case statements, and [else_] for case defaults """ + whenlist = [_CompoundClause(None, 'WHEN', c, 'THEN', r) for (c,r) in whens] + if else_: + whenlist.append(_CompoundClause(None, 'ELSE', else_)) + cc = _CalculatedClause(None, 'CASE', value, *whenlist + ['END']) + for c in cc.clauses: + c.parens = False + return cc + +def cast(clause, totype, **kwargs): + """ returns CAST function CAST(clause AS totype) + Use with a sqlalchemy.types.TypeEngine object, i.e + cast(table.c.unit_price * table.c.qty, Numeric(10,4)) + or + cast(table.c.timestamp, DATE) + """ + return _Cast(clause, totype, **kwargs) + +def extract(field, expr): + """return extract(field FROM expr)""" + expr = _BinaryClause(text(field), expr, "FROM") + return func.extract(expr) + +def exists(*args, **params): + params['correlate'] = True + s = select(*args, **params) + return _BooleanExpression(_TextClause("EXISTS"), s, None) + +def union(*selects, **params): + return _compound_select('UNION', *selects, **params) + +def union_all(*selects, **params): + return _compound_select('UNION ALL', *selects, **params) + +def alias(*args, **params): + return Alias(*args, **params) + +def _check_literal(value, type): + if _is_literal(value): + return literal(value, type) + else: + return value + +def literal(value, type=None): + """returns a literal clause, bound to a bind parameter. + + literal clauses are created automatically when used as the right-hand + side of a boolean or math operation against a column object. use this + function when a literal is needed on the left-hand side (and optionally on the right as well). + + the optional type parameter is a sqlalchemy.types.TypeEngine object which indicates bind-parameter + and result-set translation for this literal. + """ + return _BindParamClause('literal', value, type=type) + +def label(name, obj): + """returns a _Label object for the given selectable, used in the column list for a select statement.""" + return _Label(name, obj) + +def column(text, table=None, type=None): + """returns a textual column clause, relative to a table. this is also the primitive version of + a schema.Column which is a subclass. """ + return _ColumnClause(text, table, type) + +def table(name, *columns): + """returns a table clause. this is a primitive version of the schema.Table object, which is a subclass + of this object.""" + return TableClause(name, *columns) + +def bindparam(key, value=None, type=None, shortname=None): + """creates a bind parameter clause with the given key. + + An optional default value can be specified by the value parameter, and the optional type parameter + is a sqlalchemy.types.TypeEngine object which indicates bind-parameter and result-set translation for + this bind parameter.""" + if isinstance(key, _ColumnClause): + return _BindParamClause(key.name, value, type=key.type, shortname=shortname) + else: + return _BindParamClause(key, value, type=type, shortname=shortname) + +def text(text, engine=None, *args, **kwargs): + """creates literal text to be inserted into a query. + + When constructing a query from a select(), update(), insert() or delete(), using + plain strings for argument values will usually result in text objects being created + automatically. Use this function when creating textual clauses outside of other + ClauseElement objects, or optionally wherever plain text is to be used. + + Arguments include: + + text - the text of the SQL statement to be created. use : to specify + bind parameters; they will be compiled to their engine-specific format. + + engine - an optional engine to be used for this text query. + + bindparams - a list of bindparam() instances which can be used to define the + types and/or initial values for the bind parameters within the textual statement; + the keynames of the bindparams must match those within the text of the statement. + The types will be used for pre-processing on bind values. + + typemap - a dictionary mapping the names of columns represented in the SELECT + clause of the textual statement to type objects, which will be used to perform + post-processing on columns within the result set (for textual statements that + produce result sets).""" + return _TextClause(text, engine=engine, *args, **kwargs) + +def null(): + """returns a Null object, which compiles to NULL in a sql statement.""" + return _Null() + +class _FunctionGateway(object): + """returns a callable based on an attribute name, which then returns a _Function + object with that name.""" + def __getattr__(self, name): + return getattr(_FunctionGenerator(), name) +func = _FunctionGateway() + +def _compound_clause(keyword, *clauses): + return _CompoundClause(keyword, *clauses) + +def _compound_select(keyword, *selects, **kwargs): + return CompoundSelect(keyword, *selects, **kwargs) + +def _is_literal(element): + return not isinstance(element, ClauseElement) + +def is_column(col): + return isinstance(col, ColumnElement) + + +class AbstractDialect(object): + """represents the behavior of a particular database. Used by Compiled objects.""" + pass + +class ClauseParameters(dict): + """represents a dictionary/iterator of bind parameter key names/values. + + Tracks the original BindParam objects as well as the keys/position of each + parameter, and can return parameters as a dictionary or a list. + Will process parameter values according to the TypeEngine objects present in + the BindParams. + """ + def __init__(self, dialect, positional=None): + super(ClauseParameters, self).__init__(self) + self.dialect=dialect + self.binds = {} + self.positional = positional or [] + def set_parameter(self, bindparam, value): + self[bindparam.key] = value + self.binds[bindparam.key] = bindparam + def get_original(self, key): + """returns the given parameter as it was originally placed in this ClauseParameters object, without any Type conversion""" + return super(ClauseParameters, self).__getitem__(key) + def __getitem__(self, key): + v = super(ClauseParameters, self).__getitem__(key) + if self.binds.has_key(key): + v = self.binds[key].typeprocess(v, self.dialect) + return v + def get_original_dict(self): + return self.copy() + def get_raw_list(self): + return [self[key] for key in self.positional] + def get_raw_dict(self): + d = {} + for k in self: + d[k] = self[k] + return d + +class ClauseVisitor(object): + """Defines the visiting of ClauseElements.""" + def visit_column(self, column):pass + def visit_table(self, column):pass + def visit_fromclause(self, fromclause):pass + def visit_bindparam(self, bindparam):pass + def visit_textclause(self, textclause):pass + def visit_compound(self, compound):pass + def visit_compound_select(self, compound):pass + def visit_binary(self, binary):pass + def visit_alias(self, alias):pass + def visit_select(self, select):pass + def visit_join(self, join):pass + def visit_null(self, null):pass + def visit_clauselist(self, list):pass + def visit_calculatedclause(self, calcclause):pass + def visit_function(self, func):pass + def visit_cast(self, cast):pass + def visit_label(self, label):pass + def visit_typeclause(self, typeclause):pass + +class Executor(object): + """represents a 'thing that can produce Compiled objects and execute them'.""" + def execute_compiled(self, compiled, parameters, echo=None, **kwargs): + """execute a Compiled object.""" + raise NotImplementedError() + def compiler(self, statement, parameters, **kwargs): + """return a Compiled object for the given statement and parameters.""" + raise NotImplementedError() + +class Compiled(ClauseVisitor): + """represents a compiled SQL expression. the __str__ method of the Compiled object + should produce the actual text of the statement. Compiled objects are specific to the + database library that created them, and also may or may not be specific to the columns + referenced within a particular set of bind parameters. In no case should the Compiled + object be dependent on the actual values of those bind parameters, even though it may + reference those values as defaults.""" + + def __init__(self, dialect, statement, parameters, engine=None): + """construct a new Compiled object. + + statement - ClauseElement to be compiled + + parameters - optional dictionary indicating a set of bind parameters + specified with this Compiled object. These parameters are the "default" + values corresponding to the ClauseElement's _BindParamClauses when the Compiled + is executed. In the case of an INSERT or UPDATE statement, these parameters + will also result in the creation of new _BindParamClause objects for each key + and will also affect the generated column list in an INSERT statement and the SET + clauses of an UPDATE statement. The keys of the parameter dictionary can + either be the string names of columns or _ColumnClause objects. + + engine - optional Engine to compile this statement against""" + self.dialect = dialect + self.statement = statement + self.parameters = parameters + self.engine = engine + + def compile(self): + self.statement.accept_visitor(self) + self.after_compile() + + def __str__(self): + """returns the string text of the generated SQL statement.""" + raise NotImplementedError() + def get_params(self, **params): + """returns the bind params for this compiled object. + + Will start with the default parameters specified when this Compiled object + was first constructed, and will override those values with those sent via + **params, which are key/value pairs. Each key should match one of the + _BindParamClause objects compiled into this object; either the "key" or + "shortname" property of the _BindParamClause. + """ + raise NotImplementedError() + + def execute(self, *multiparams, **params): + """execute this compiled object.""" + e = self.engine + if e is None: + raise exceptions.InvalidRequestError("This Compiled object is not bound to any engine.") + return e.execute_compiled(self, *multiparams, **params) + + def scalar(self, *multiparams, **params): + """execute this compiled object and return the result's scalar value.""" + return self.execute(*multiparams, **params).scalar() + + +class ClauseElement(object): + """base class for elements of a programmatically constructed SQL expression.""" + def _get_from_objects(self): + """returns objects represented in this ClauseElement that should be added to the + FROM list of a query, when this ClauseElement is placed in the column clause of a Select + statement.""" + raise NotImplementedError(repr(self)) + def _process_from_dict(self, data, asfrom): + """given a dictionary attached to a Select object, places the appropriate + FROM objects in the dictionary corresponding to this ClauseElement, + and possibly removes or modifies others.""" + for f in self._get_from_objects(): + data.setdefault(f, f) + if asfrom: + data[self] = self + def compare(self, other): + """compare this ClauseElement to the given ClauseElement. + + Subclasses should override the default behavior, which is a straight + identity comparison.""" + return self is other + + def accept_visitor(self, visitor): + """accept a ClauseVisitor and call the appropriate visit_xxx method.""" + raise NotImplementedError(repr(self)) + + def copy_container(self): + """return a copy of this ClauseElement, iff this ClauseElement contains other ClauseElements. + + If this ClauseElement is not a container, it should return self. This is used to + create copies of expression trees that still reference the same "leaf nodes". The + new structure can then be restructured without affecting the original.""" + return self + + def _find_engine(self): + """default strategy for locating an engine within the clause element. + relies upon a local engine property, or looks in the "from" objects which + ultimately have to contain Tables or TableClauses. """ + try: + if self._engine is not None: + return self._engine + except AttributeError: + pass + for f in self._get_from_objects(): + if f is self: + continue + engine = f.engine + if engine is not None: + return engine + else: + return None + + engine = property(lambda s: s._find_engine(), doc="attempts to locate a Engine within this ClauseElement structure, or returns None if none found.") + + def execute(self, *multiparams, **params): + """compile and execute this ClauseElement.""" + if len(multiparams): + compile_params = multiparams[0] + else: + compile_params = params + return self.compile(engine=self.engine, parameters=compile_params).execute(*multiparams, **params) + + def scalar(self, *multiparams, **params): + """compile and execute this ClauseElement, returning the result's scalar representation.""" + return self.execute(*multiparams, **params).scalar() + + def compile(self, engine=None, parameters=None, compiler=None, dialect=None): + """compile this SQL expression. + + Uses the given Compiler, or the given AbstractDialect or Engine to create a Compiler. If no compiler + arguments are given, tries to use the underlying Engine this ClauseElement is bound + to to create a Compiler, if any. Finally, if there is no bound Engine, uses an ANSIDialect + to create a default Compiler. + + bindparams is a dictionary representing the default bind parameters to be used with + the statement. if the bindparams is a list, it is assumed to be a list of dictionaries + and the first dictionary in the list is used with which to compile against. + The bind parameters can in some cases determine the output of the compilation, such as for UPDATE + and INSERT statements the bind parameters that are present determine the SET and VALUES clause of + those statements. + """ + if (isinstance(parameters, list) or isinstance(parameters, tuple)): + parameters = parameters[0] + + if compiler is None: + if dialect is not None: + compiler = dialect.compiler(self, parameters) + elif engine is not None: + compiler = engine.compiler(self, parameters) + elif self.engine is not None: + compiler = self.engine.compiler(self, parameters) + + if compiler is None: + import sqlalchemy.ansisql as ansisql + compiler = ansisql.ANSIDialect().compiler(self, parameters=parameters) + compiler.compile() + return compiler + + def __str__(self): + return str(self.compile()) + def __and__(self, other): + return and_(self, other) + def __or__(self, other): + return or_(self, other) + def __invert__(self): + return self._negate() + def _negate(self): + self.parens=True + return _BooleanExpression(_TextClause("NOT"), self, None) + +class _CompareMixin(object): + """defines comparison operations for ClauseElements.""" + def __lt__(self, other): + return self._compare('<', other) + def __le__(self, other): + return self._compare('<=', other) + def __eq__(self, other): + return self._compare('=', other) + def __ne__(self, other): + return self._compare('!=', other) + def __gt__(self, other): + return self._compare('>', other) + def __ge__(self, other): + return self._compare('>=', other) + def like(self, other): + return self._compare('LIKE', other) + def in_(self, *other): + if len(other) == 0: + return self.__eq__(None) + elif len(other) == 1 and not hasattr(other[0], '_selectable'): + return self.__eq__(other[0]) + elif _is_literal(other[0]): + return self._compare('IN', ClauseList(parens=True, *[self._bind_param(o) for o in other]), negate='NOT IN') + else: + # assume *other is a list of selects. + # so put them in a UNION. if theres only one, you just get one SELECT + # statement out of it. + return self._compare('IN', union(parens=True, *other), negate='NOT IN') + def startswith(self, other): + return self._compare('LIKE', other + "%") + def endswith(self, other): + return self._compare('LIKE', "%" + other) + def label(self, name): + return _Label(name, self, self.type) + def distinct(self): + return _CompoundClause(None,"DISTINCT", self) + def between(self, cleft, cright): + return _BooleanExpression(self, and_(self._check_literal(cleft), self._check_literal(cright)), 'BETWEEN') + def op(self, operator): + return lambda other: self._compare(operator, other) + # and here come the math operators: + def __add__(self, other): + return self._operate('+', other) + def __sub__(self, other): + return self._operate('-', other) + def __mul__(self, other): + return self._operate('*', other) + def __div__(self, other): + return self._operate('/', other) + def __mod__(self, other): + return self._operate('%', other) + def __truediv__(self, other): + return self._operate('/', other) + def _bind_param(self, obj): + return _BindParamClause('literal', obj, shortname=None, type=self.type) + def _check_literal(self, other): + if _is_literal(other): + return self._bind_param(other) + else: + return other + def _compare(self, operator, obj, negate=None): + if obj is None or isinstance(obj, _Null): + if operator == '=': + return _BooleanExpression(self._compare_self(), null(), 'IS', negate='IS NOT') + elif operator == '!=': + return _BooleanExpression(self._compare_self(), null(), 'IS NOT', negate='IS') + else: + raise exceptions.ArgumentError("Only '='/'!=' operators can be used with NULL") + else: + obj = self._check_literal(obj) + + return _BooleanExpression(self._compare_self(), obj, operator, type=self._compare_type(obj), negate=negate) + def _operate(self, operator, obj): + if _is_literal(obj): + obj = self._bind_param(obj) + return _BinaryExpression(self._compare_self(), obj, operator, type=self._compare_type(obj)) + def _compare_self(self): + """allows ColumnImpl to return its Column object for usage in ClauseElements, all others to + just return self""" + return self + def _compare_type(self, obj): + """allows subclasses to override the type used in constructing _BinaryClause objects. Default return + value is the type of the given object.""" + return obj.type + +class Selectable(ClauseElement): + """represents a column list-holding object.""" + + def _selectable(self): + return self + def accept_visitor(self, visitor): + raise NotImplementedError(repr(self)) + def select(self, whereclauses = None, **params): + return select([self], whereclauses, **params) + def _group_parenthesized(self): + """indicates if this Selectable requires parenthesis when grouped into a compound + statement""" + return True + +class ColumnElement(Selectable, _CompareMixin): + """represents a column element within the list of a Selectable's columns. + A ColumnElement can either be directly associated with a TableClause, or + a free-standing textual column with no table, or is a "proxy" column, indicating + it is placed on a Selectable such as an Alias or Select statement and ultimately corresponds + to a TableClause-attached column (or in the case of a CompositeSelect, a proxy ColumnElement + may correspond to several TableClause-attached columns).""" + + primary_key = property(lambda self:getattr(self, '_primary_key', False), doc="primary key flag. indicates if this Column represents part or whole of a primary key.") + foreign_keys = property(lambda self:getattr(self, '_foreign_keys', []), doc="foreign key accessor. points to a ForeignKey object which represents a Foreign Key placed on this column's ultimate ancestor.") + columns = property(lambda self:[self], doc="Columns accessor which just returns self, to provide compatibility with Selectable objects.") + def _one_fkey(self): + if len(self._foreign_keys): + return list(self._foreign_keys)[0] + else: + return None + foreign_key = property(_one_fkey) + + def _get_orig_set(self): + try: + return self.__orig_set + except AttributeError: + self.__orig_set = util.Set([self]) + return self.__orig_set + def _set_orig_set(self, s): + if len(s) == 0: + s.add(self) + self.__orig_set = s + orig_set = property(_get_orig_set, _set_orig_set,doc="""a Set containing TableClause-bound, non-proxied ColumnElements for which this ColumnElement is a proxy. In all cases except for a column proxied from a Union (i.e. CompoundSelect), this set will be just one element.""") + + def shares_lineage(self, othercolumn): + """returns True if the given ColumnElement has a common ancestor to this ColumnElement.""" + for c in self.orig_set: + if c in othercolumn.orig_set: + return True + else: + return False + def _make_proxy(self, selectable, name=None): + """creates a new ColumnElement representing this ColumnElement as it appears in the select list + of a descending selectable. The default implementation returns a _ColumnClause if a name is given, + else just returns self.""" + if name is not None: + co = _ColumnClause(name, selectable) + co.orig_set = self.orig_set + selectable.columns[name]= co + return co + else: + return self + +class ColumnCollection(util.OrderedProperties): + """an ordered dictionary that stores a list of ColumnElement instances. + + overrides the __eq__() method to produce SQL clauses between sets of + correlated columns.""" + def add(self, column): + """add a column to this collection. + + the key attribute of the column will be used as the hash key for this + dictionary.""" + self[column.key] = column + def __eq__(self, other): + l = [] + for c in other: + for local in self: + if c.shares_lineage(local): + l.append(c==local) + return and_(*l) + +class FromClause(Selectable): + """represents an element that can be used within the FROM clause of a SELECT statement.""" + def __init__(self, name=None): + self.name = name + def _get_from_objects(self): + # this could also be [self], at the moment it doesnt matter to the Select object + return [] + def default_order_by(self): + return [self.oid_column] + def accept_visitor(self, visitor): + visitor.visit_fromclause(self) + def count(self, whereclause=None, **params): + if len(self.primary_key): + col = list(self.primary_key)[0] + else: + col = list(self.columns)[0] + return select([func.count(col).label('tbl_row_count')], whereclause, from_obj=[self], **params) + def join(self, right, *args, **kwargs): + return Join(self, right, *args, **kwargs) + def outerjoin(self, right, *args, **kwargs): + return Join(self, right, isouter=True, *args, **kwargs) + def alias(self, name=None): + return Alias(self, name) + def named_with_column(self): + """True if the name of this FromClause may be prepended to a column in a generated SQL statement""" + return False + def _locate_oid_column(self): + """subclasses override this to return an appropriate OID column""" + return None + def _get_oid_column(self): + if not hasattr(self, '_oid_column'): + self._oid_column = self._locate_oid_column() + return self._oid_column + def corresponding_column(self, column, raiseerr=True, keys_ok=False): + """given a ColumnElement, return the ColumnElement object from this + Selectable which corresponds to that original Column via a proxy relationship.""" + for c in column.orig_set: + try: + return self.original_columns[c] + except KeyError: + pass + else: + if keys_ok: + try: + return self.c[column.key] + except KeyError: + pass + if not raiseerr: + return None + else: + raise exceptions.InvalidRequestError("Given column '%s', attached to table '%s', failed to locate a corresponding column from table '%s'" % (str(column), str(column.table), self.name)) + + def _get_exported_attribute(self, name): + try: + return getattr(self, name) + except AttributeError: + self._export_columns() + return getattr(self, name) + columns = property(lambda s:s._get_exported_attribute('_columns')) + c = property(lambda s:s._get_exported_attribute('_columns')) + primary_key = property(lambda s:s._get_exported_attribute('_primary_key')) + foreign_keys = property(lambda s:s._get_exported_attribute('_foreign_keys')) + original_columns = property(lambda s:s._get_exported_attribute('_orig_cols'), doc="a dictionary mapping an original Table-bound column to a proxied column in this FromClause.") + oid_column = property(_get_oid_column) + + def _export_columns(self): + """initialize column collections. + + the collections include the primary key, foreign keys, list of all columns, as well as + the "_orig_cols" collection which is a dictionary used to match Table-bound columns + to proxied columns in this FromClause. The columns in each collection are "proxied" from + the columns returned by the _exportable_columns method, where a "proxied" column maintains + most or all of the properties of its original column, except its parent Selectable is this FromClause. + """ + if hasattr(self, '_columns'): + # TODO: put a mutex here ? this is a key place for threading probs + return + self._columns = ColumnCollection() + self._primary_key = ColumnCollection() + self._foreign_keys = util.Set() + self._orig_cols = {} + export = self._exportable_columns() + for column in export: + try: + s = column._selectable() + except AttributeError: + continue + for co in s.columns: + cp = self._proxy_column(co) + for ci in cp.orig_set: + self._orig_cols[ci] = cp + if self.oid_column is not None: + for ci in self.oid_column.orig_set: + self._orig_cols[ci] = self.oid_column + def _exportable_columns(self): + return [] + def _proxy_column(self, column): + return column._make_proxy(self) + +class _BindParamClause(ClauseElement, _CompareMixin): + """represents a bind parameter. public constructor is the bindparam() function.""" + def __init__(self, key, value, shortname=None, type=None): + """construct a _BindParamClause. + + key - the key for this bind param. will be used in the generated SQL statement + for dialects that use named parameters. this value may be modified when part of a + compilation operation, if other _BindParamClause objects exist with the same key, or if + its length is too long and truncation is required. + + value - initial value for this bind param. This value may be overridden by the + dictionary of parameters sent to statement compilation/execution. + + shortname - defaults to the key, a 'short name' that will also identify this + bind parameter, similar to an alias. the bind parameter keys sent to a statement + compilation or compiled execution may match either the key or the shortname of the + corresponding _BindParamClause objects. + + type - a TypeEngine object that will be used to pre-process the value corresponding + to this _BindParamClause at execution time.""" + self.key = key + self.value = value + self.shortname = shortname or key + self.type = sqltypes.to_instance(type) + def accept_visitor(self, visitor): + visitor.visit_bindparam(self) + def _get_from_objects(self): + return [] + def copy_container(self): + return _BindParamClause(self.key, self.value, self.shortname, self.type) + def typeprocess(self, value, dialect): + return self.type.dialect_impl(dialect).convert_bind_param(value, dialect) + def compare(self, other): + """compares this _BindParamClause to the given clause. + + Since compare() is meant to compare statement syntax, this method + returns True if the two _BindParamClauses have just the same type.""" + return isinstance(other, _BindParamClause) and other.type.__class__ == self.type.__class__ + def _make_proxy(self, selectable, name = None): + return self +# return self.obj._make_proxy(selectable, name=self.name) + +class _TypeClause(ClauseElement): + """handles a type keyword in a SQL statement. used by the Case statement.""" + def __init__(self, type): + self.type = type + def accept_visitor(self, visitor): + visitor.visit_typeclause(self) + def _get_from_objects(self): + return [] + +class _TextClause(ClauseElement): + """represents literal a SQL text fragment. public constructor is the + text() function. + + """ + def __init__(self, text = "", engine=None, bindparams=None, typemap=None): + self.parens = False + self._engine = engine + self.bindparams = {} + self.typemap = typemap + if typemap is not None: + for key in typemap.keys(): + typemap[key] = sqltypes.to_instance(typemap[key]) + def repl(m): + self.bindparams[m.group(1)] = bindparam(m.group(1)) + return ":%s" % m.group(1) + # scan the string and search for bind parameter names, add them + # to the list of bindparams + self.text = re.compile(r'(? 1: + raise exceptions.ArgumentError("Cant determine join between '%s' and '%s'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly." % (primary.name, secondary.name)) + elif len(crit) == 1: + return (crit[0]) + else: + return and_(*crit) + + def _group_parenthesized(self): + return True + def select(self, whereclauses = None, **params): + return select([self.left, self.right], whereclauses, from_obj=[self], **params) + def accept_visitor(self, visitor): + self.left.accept_visitor(visitor) + self.right.accept_visitor(visitor) + self.onclause.accept_visitor(visitor) + visitor.visit_join(self) + + engine = property(lambda s:s.left.engine or s.right.engine) + + class JoinMarker(FromClause): + def __init__(self, join): + FromClause.__init__(self) + self.join = join + def _exportable_columns(self): + return [] + + def alias(self, name=None): + """creates a Select out of this Join clause and returns an Alias of it. The Select is not correlating.""" + return self.select(use_labels=True, correlate=False).alias(name) + def _process_from_dict(self, data, asfrom): + for f in self.onclause._get_from_objects(): + data[f] = f + for f in self.left._get_from_objects() + self.right._get_from_objects(): + # mark the object as a "blank" "from" that wont be printed + data[f] = Join.JoinMarker(self) + # a JOIN always impacts the final FROM list of a select statement + data[self] = self + + def _get_from_objects(self): + return [self] + self.onclause._get_from_objects() + self.left._get_from_objects() + self.right._get_from_objects() + +class Alias(FromClause): + def __init__(self, selectable, alias = None): + baseselectable = selectable + while isinstance(baseselectable, Alias): + baseselectable = baseselectable.selectable + self.original = baseselectable + self.selectable = selectable + if alias is None: + if self.original.named_with_column(): + alias = getattr(self.original, 'name', None) + if alias is None: + alias = 'anon' + elif len(alias) > 15: + alias = alias[0:15] + alias = alias + "_" + hex(random.randint(0, 65535))[2:] + self.name = alias + self.case_sensitive = getattr(baseselectable, "case_sensitive", alias.lower() != alias) + + def _locate_oid_column(self): + if self.selectable.oid_column is not None: + return self.selectable.oid_column._make_proxy(self) + else: + return None + + def named_with_column(self): + return True + def _exportable_columns(self): + #return self.selectable._exportable_columns() + return self.selectable.columns + + def accept_visitor(self, visitor): + self.selectable.accept_visitor(visitor) + visitor.visit_alias(self) + + def _get_from_objects(self): + return [self] + + def _group_parenthesized(self): + return False + + engine = property(lambda s: s.selectable.engine) + + +class _Label(ColumnElement): + def __init__(self, name, obj, type=None): + self.name = name + while isinstance(obj, _Label): + obj = obj.obj + self.obj = obj + self.case_sensitive = getattr(obj, "case_sensitive", name.lower() != name) + self.type = sqltypes.to_instance(type) + obj.parens=True + key = property(lambda s: s.name) + _label = property(lambda s: s.name) + orig_set = property(lambda s:s.obj.orig_set) + def accept_visitor(self, visitor): + self.obj.accept_visitor(visitor) + visitor.visit_label(self) + def _get_from_objects(self): + return self.obj._get_from_objects() + def _make_proxy(self, selectable, name = None): + return self.obj._make_proxy(selectable, name=self.name) + +legal_characters = util.Set(string.ascii_letters + string.digits + '_') +class _ColumnClause(ColumnElement): + """represents a textual column clause in a SQL statement. May or may not + be bound to an underlying Selectable.""" + def __init__(self, text, selectable=None, type=None, _is_oid=False): + self.key = self.name = text + self.table = selectable + self.type = sqltypes.to_instance(type) + self._is_oid = _is_oid + self.__label = None + def _get_label(self): + if self.__label is None: + if self.table is not None and self.table.named_with_column(): + self.__label = self.table.name + "_" + self.name + if self.table.c.has_key(self.__label) or len(self.__label) >= 30: + self.__label = self.__label[0:24] + "_" + hex(random.randint(0, 65535))[2:] + else: + self.__label = self.name + self.__label = "".join([x for x in self.__label if x in legal_characters]) + return self.__label + _label = property(_get_label) + def accept_visitor(self, visitor): + visitor.visit_column(self) + def to_selectable(self, selectable): + """given a Selectable, returns this column's equivalent in that Selectable, if any. + + for example, this could translate the column "name" from a Table object + to an Alias of a Select off of that Table object.""" + return selectable.corresponding_column(self.original, False) + def _get_from_objects(self): + if self.table is not None: + return [self.table] + else: + return [] + def _bind_param(self, obj): + return _BindParamClause(self._label, obj, shortname = self.name, type=self.type) + def _make_proxy(self, selectable, name = None): + c = _ColumnClause(name or self.name, selectable, _is_oid=self._is_oid, type=self.type) + c.orig_set = self.orig_set + if not self._is_oid: + selectable.columns[c.name] = c + return c + def _compare_type(self, obj): + return self.type + def _group_parenthesized(self): + return False + +class TableClause(FromClause): + def __init__(self, name, *columns): + super(TableClause, self).__init__(name) + self.name = self.fullname = name + self._columns = ColumnCollection() + self._foreign_keys = util.OrderedSet() + self._primary_key = ColumnCollection() + for c in columns: + self.append_column(c) + self._oid_column = _ColumnClause('oid', self, _is_oid=True) + + def named_with_column(self): + return True + def append_column(self, c): + self._columns[c.name] = c + c.table = self + def _locate_oid_column(self): + return self._oid_column + def _orig_columns(self): + try: + return self._orig_cols + except AttributeError: + self._orig_cols= {} + for c in self.columns: + for ci in c.orig_set: + self._orig_cols[ci] = c + return self._orig_cols + original_columns = property(_orig_columns) + + def accept_visitor(self, visitor): + visitor.visit_table(self) + def _exportable_columns(self): + raise NotImplementedError() + def _group_parenthesized(self): + return False + def _process_from_dict(self, data, asfrom): + for f in self._get_from_objects(): + data.setdefault(f, f) + if asfrom: + data[self] = self + def count(self, whereclause=None, **params): + if len(self.primary_key): + col = list(self.primary_key)[0] + else: + col = list(self.columns)[0] + return select([func.count(col).label('tbl_row_count')], whereclause, from_obj=[self], **params) + def join(self, right, *args, **kwargs): + return Join(self, right, *args, **kwargs) + def outerjoin(self, right, *args, **kwargs): + return Join(self, right, isouter = True, *args, **kwargs) + def alias(self, name=None): + return Alias(self, name) + def select(self, whereclause = None, **params): + return select([self], whereclause, **params) + def insert(self, values = None): + return insert(self, values=values) + def update(self, whereclause = None, values = None): + return update(self, whereclause, values) + def delete(self, whereclause = None): + return delete(self, whereclause) + def _get_from_objects(self): + return [self] + +class _SelectBaseMixin(object): + """base class for Select and CompoundSelects""" + def order_by(self, *clauses): + if len(clauses) == 1 and clauses[0] is None: + self.order_by_clause = ClauseList() + elif getattr(self, 'order_by_clause', None): + self.order_by_clause = ClauseList(*(list(self.order_by_clause.clauses) + list(clauses))) + else: + self.order_by_clause = ClauseList(*clauses) + def group_by(self, *clauses): + if len(clauses) == 1 and clauses[0] is None: + self.group_by_clause = ClauseList() + elif getattr(self, 'group_by_clause', None): + self.group_by_clause = ClauseList(*(list(clauses)+list(self.group_by_clause.clauses))) + else: + self.group_by_clause = ClauseList(*clauses) + def select(self, whereclauses = None, **params): + return select([self], whereclauses, **params) + def _get_from_objects(self): + if self.is_where or self._scalar: + return [] + else: + return [self] + +class CompoundSelect(_SelectBaseMixin, FromClause): + def __init__(self, keyword, *selects, **kwargs): + _SelectBaseMixin.__init__(self) + self.keyword = keyword + self.selects = selects + self.use_labels = kwargs.pop('use_labels', False) + self.parens = kwargs.pop('parens', False) + self.correlate = kwargs.pop('correlate', False) + self.for_update = kwargs.pop('for_update', False) + self.nowait = kwargs.pop('nowait', False) + self.limit = kwargs.get('limit', None) + self.offset = kwargs.get('offset', None) + for s in self.selects: + s.group_by(None) + s.order_by(None) + self.group_by(*kwargs.get('group_by', [None])) + self.order_by(*kwargs.get('order_by', [None])) + self._col_map = {} + + name = property(lambda s:s.keyword + " statement") + + def _locate_oid_column(self): + return self.selects[0].oid_column + def _exportable_columns(self): + for s in self.selects: + for c in s.c: + yield c + def _proxy_column(self, column): + if self.use_labels: + col = column._make_proxy(self, name=column._label) + else: + col = column._make_proxy(self, name=column.name) + try: + colset = self._col_map[col.name] + except KeyError: + colset = util.Set() + self._col_map[col.name] = colset + [colset.add(c) for c in col.orig_set] + col.orig_set = colset + return col + + def accept_visitor(self, visitor): + self.order_by_clause.accept_visitor(visitor) + self.group_by_clause.accept_visitor(visitor) + for s in self.selects: + s.accept_visitor(visitor) + visitor.visit_compound_select(self) + def _find_engine(self): + for s in self.selects: + e = s._find_engine() + if e: + return e + else: + return None + +class Select(_SelectBaseMixin, FromClause): + """represents a SELECT statement, with appendable clauses, as well as + the ability to execute itself and return a result set.""" + def __init__(self, columns=None, whereclause = None, from_obj = [], order_by = None, group_by=None, having=None, use_labels = False, distinct=False, for_update=False, nowait=False, engine=None, limit=None, offset=None, scalar=False, correlate=True): + _SelectBaseMixin.__init__(self) + self._froms = util.OrderedDict() + self.use_labels = use_labels + self.whereclause = None + self.having = None + self._engine = engine + self.limit = limit + self.offset = offset + self.for_update = for_update + self.nowait = nowait + + # indicates that this select statement should not expand its columns + # into the column clause of an enclosing select, and should instead + # act like a single scalar column + self._scalar = scalar + + # indicates if this select statement, as a subquery, should correlate + # its FROM clause to that of an enclosing select statement + self.correlate = correlate + + # indicates if this select statement is a subquery inside another query + self.issubquery = False + + # indicates if this select statement is a subquery as a criterion + # inside of a WHERE clause + self.is_where = False + + self.distinct = distinct + self._text = None + self._raw_columns = [] + self._correlated = None + self._correlator = Select._CorrelatedVisitor(self, False) + self._wherecorrelator = Select._CorrelatedVisitor(self, True) + + + self.group_by(*(group_by or [None])) + self.order_by(*(order_by or [None])) + + if columns is not None: + for c in columns: + self.append_column(c) + + if whereclause is not None: + self.append_whereclause(whereclause) + if having is not None: + self.append_having(having) + + for f in from_obj: + self.append_from(f) + + def _foo(self): + raise "this is a temporary assertion while we refactor SQL to not call 'name' on non-table Selectables" + name = property(lambda s:s._foo()) #"SELECT statement") + + class _CorrelatedVisitor(ClauseVisitor): + """visits a clause, locates any Select clauses, and tells them that they should + correlate their FROM list to that of their parent.""" + def __init__(self, select, is_where): + self.select = select + self.is_where = is_where + def visit_compound_select(self, cs): + self.visit_select(cs) + for s in cs.selects: + s.parens = False + def visit_column(self, c):pass + def visit_table(self, c):pass + def visit_select(self, select): + if select is self.select: + return + select.is_where = self.is_where + select.issubquery = True + select.parens = True + if not select.correlate: + return + if getattr(select, '_correlated', None) is None: + select._correlated = self.select._froms + + def append_column(self, column): + if _is_literal(column): + column = _ColumnClause(str(column), self) + + self._raw_columns.append(column) + + # if the column is a Select statement itself, + # accept visitor + column.accept_visitor(self._correlator) + + # visit the FROM objects of the column looking for more Selects + for f in column._get_from_objects(): + f.accept_visitor(self._correlator) + column._process_from_dict(self._froms, False) + def _exportable_columns(self): + return self._raw_columns + def _proxy_column(self, column): + if self.use_labels: + return column._make_proxy(self, name=column._label) + else: + return column._make_proxy(self, name=column.name) + def append_whereclause(self, whereclause): + self._append_condition('whereclause', whereclause) + def append_having(self, having): + self._append_condition('having', having) + def _append_condition(self, attribute, condition): + if type(condition) == str: + condition = _TextClause(condition) + condition.accept_visitor(self._wherecorrelator) + condition._process_from_dict(self._froms, False) + if getattr(self, attribute) is not None: + setattr(self, attribute, and_(getattr(self, attribute), condition)) + else: + setattr(self, attribute, condition) + + def clear_from(self, from_obj): + self._froms[from_obj] = FromClause() + + def append_from(self, fromclause): + if type(fromclause) == str: + fromclause = _TextClause(fromclause) + fromclause.accept_visitor(self._correlator) + fromclause._process_from_dict(self._froms, True) + def _locate_oid_column(self): + for f in self._froms.values(): + if f is self: + # we might be in our own _froms list if a column with us as the parent is attached, + # which includes textual columns. + continue + oid = f.oid_column + if oid is not None: + return oid + else: + return None + def _get_froms(self): + return [f for f in self._froms.values() if f is not self and (self._correlated is None or not self._correlated.has_key(f))] + froms = property(lambda s: s._get_froms()) + + def accept_visitor(self, visitor): + # TODO: add contextual visit_ methods + # visit_select_whereclause, visit_select_froms, visit_select_orderby, etc. + # which will allow the compiler to set contextual flags before traversing + # into each thing. + for f in self._get_froms(): + f.accept_visitor(visitor) + if self.whereclause is not None: + self.whereclause.accept_visitor(visitor) + if self.having is not None: + self.having.accept_visitor(visitor) + self.order_by_clause.accept_visitor(visitor) + self.group_by_clause.accept_visitor(visitor) + visitor.visit_select(self) + + def union(self, other, **kwargs): + return union(self, other, **kwargs) + def union_all(self, other, **kwargs): + return union_all(self, other, **kwargs) + def _find_engine(self): + """tries to return a Engine, either explicitly set in this object, or searched + within the from clauses for one""" + + if self._engine is not None: + return self._engine + for f in self._froms.values(): + if f is self: + continue + e = f.engine + if e is not None: + self._engine = e + return e + # look through the columns (largely synomous with looking + # through the FROMs except in the case of _CalculatedClause/_Function) + for cc in self._raw_columns: + for c in cc.columns: + if getattr(c, 'table', None) is self: + continue + e = c.engine + if e is not None: + self._engine = e + return e + return None + +class _UpdateBase(ClauseElement): + """forms the base for INSERT, UPDATE, and DELETE statements.""" + def _process_colparams(self, parameters): + """receives the "values" of an INSERT or UPDATE statement and constructs + appropriate bind parameters.""" + if parameters is None: + return None + + if isinstance(parameters, list) or isinstance(parameters, tuple): + pp = {} + i = 0 + for c in self.table.c: + pp[c.key] = parameters[i] + i +=1 + parameters = pp + + for key in parameters.keys(): + value = parameters[key] + if isinstance(value, Select): + value.clear_from(self.table) + elif _is_literal(value): + if _is_literal(key): + col = self.table.c[key] + else: + col = key + try: + parameters[key] = bindparam(col, value) + except KeyError: + del parameters[key] + return parameters + def _find_engine(self): + return self.table.engine + +class _Insert(_UpdateBase): + def __init__(self, table, values=None): + self.table = table + self.select = None + self.parameters = self._process_colparams(values) + + def accept_visitor(self, visitor): + if self.select is not None: + self.select.accept_visitor(visitor) + + visitor.visit_insert(self) + +class _Update(_UpdateBase): + def __init__(self, table, whereclause, values=None): + self.table = table + self.whereclause = whereclause + self.parameters = self._process_colparams(values) + + def accept_visitor(self, visitor): + if self.whereclause is not None: + self.whereclause.accept_visitor(visitor) + visitor.visit_update(self) + +class _Delete(_UpdateBase): + def __init__(self, table, whereclause, **params): + self.table = table + self.whereclause = whereclause + + def accept_visitor(self, visitor): + if self.whereclause is not None: + self.whereclause.accept_visitor(visitor) + visitor.visit_delete(self) + diff --git a/spyce-2.1/sqlalchemy/sql.pyc b/spyce-2.1/sqlalchemy/sql.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cefc7c61b75fdfa177d830d8c4ebd74c6508e28f Binary files /dev/null and b/spyce-2.1/sqlalchemy/sql.pyc differ diff --git a/spyce-2.1/sqlalchemy/sql_util.py b/spyce-2.1/sqlalchemy/sql_util.py new file mode 100755 index 0000000000000000000000000000000000000000..bfbcff5541331b726c9f58ae50a91406301f7083 --- /dev/null +++ b/spyce-2.1/sqlalchemy/sql_util.py @@ -0,0 +1,109 @@ +from sqlalchemy import sql, util, schema, topological + +"""utility functions that build upon SQL and Schema constructs""" + + +class TableCollection(object): + def __init__(self, tables=None): + self.tables = tables or [] + def __len__(self): + return len(self.tables) + def __getitem__(self, i): + return self.tables[i] + def __iter__(self): + return iter(self.tables) + def __contains__(self, obj): + return obj in self.tables + def __add__(self, obj): + return self.tables + list(obj) + def add(self, table): + self.tables.append(table) + if hasattr(self, '_sorted'): + del self._sorted + def sort(self, reverse=False): + try: + sorted = self._sorted + except AttributeError, e: + self._sorted = self._do_sort() + sorted = self._sorted + if reverse: + x = sorted[:] + x.reverse() + return x + else: + return sorted + + def _do_sort(self): + tuples = [] + class TVisitor(schema.SchemaVisitor): + def visit_foreign_key(_self, fkey): + if fkey.use_alter: + return + parent_table = fkey.column.table + if parent_table in self: + child_table = fkey.parent.table + tuples.append( ( parent_table, child_table ) ) + vis = TVisitor() + for table in self.tables: + table.accept_schema_visitor(vis) + sorter = topological.QueueDependencySorter( tuples, self.tables ) + head = sorter.sort() + sequence = [] + def to_sequence( node, seq=sequence): + seq.append( node.item ) + for child in node.children: + to_sequence( child ) + if head is not None: + to_sequence( head ) + return sequence + + +class TableFinder(TableCollection, sql.ClauseVisitor): + """given a Clause, locates all the Tables within it into a list.""" + def __init__(self, table, check_columns=False): + TableCollection.__init__(self) + self.check_columns = check_columns + if table is not None: + table.accept_visitor(self) + def visit_table(self, table): + self.tables.append(table) + def visit_column(self, column): + if self.check_columns: + column.table.accept_visitor(self) + +class ColumnFinder(sql.ClauseVisitor): + def __init__(self): + self.columns = util.Set() + def visit_column(self, c): + self.columns.add(c) + def __iter__(self): + return iter(self.columns) + +class Aliasizer(sql.ClauseVisitor): + """converts a table instance within an expression to be an alias of that table.""" + def __init__(self, *tables, **kwargs): + self.tables = {} + self.aliases = kwargs.get('aliases', {}) + for t in tables: + self.tables[t] = t + if not self.aliases.has_key(t): + self.aliases[t] = sql.alias(t) + if isinstance(t, sql.Join): + for t2 in t.columns: + self.tables[t2.table] = t2 + self.aliases[t2.table] = self.aliases[t] + self.binary = None + def get_alias(self, table): + return self.aliases[table] + def visit_compound(self, compound): + self.visit_clauselist(compound) + def visit_clauselist(self, clist): + for i in range(0, len(clist.clauses)): + if isinstance(clist.clauses[i], schema.Column) and self.tables.has_key(clist.clauses[i].table): + orig = clist.clauses[i] + clist.clauses[i] = self.get_alias(clist.clauses[i].table).corresponding_column(clist.clauses[i]) + def visit_binary(self, binary): + if isinstance(binary.left, schema.Column) and self.tables.has_key(binary.left.table): + binary.left = self.get_alias(binary.left.table).corresponding_column(binary.left) + if isinstance(binary.right, schema.Column) and self.tables.has_key(binary.right.table): + binary.right = self.get_alias(binary.right.table).corresponding_column(binary.right) diff --git a/spyce-2.1/sqlalchemy/sql_util.pyc b/spyce-2.1/sqlalchemy/sql_util.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b1bcad65bcffc9c465c3c86952994889baea043 Binary files /dev/null and b/spyce-2.1/sqlalchemy/sql_util.pyc differ diff --git a/spyce-2.1/sqlalchemy/topological.py b/spyce-2.1/sqlalchemy/topological.py new file mode 100755 index 0000000000000000000000000000000000000000..5bd691f311c421d3cd7ccf60c22403b8546eef50 --- /dev/null +++ b/spyce-2.1/sqlalchemy/topological.py @@ -0,0 +1,269 @@ +# topological.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""topological sorting algorithms. the key to the unit of work is to assemble a list +of dependencies amongst all the different mappers that have been defined for classes. +Related tables with foreign key constraints have a definite insert order, deletion order, +objects need dependent properties from parent objects set up before saved, etc. +These are all encoded as dependencies, in the form "mapper X is dependent on mapper Y", +meaning mapper Y's objects must be saved before those of mapper X, and mapper X's objects +must be deleted before those of mapper Y. + +The topological sort is an algorithm that receives this list of dependencies as a "partial +ordering", that is a list of pairs which might say, "X is dependent on Y", "Q is dependent +on Z", but does not necessarily tell you anything about Q being dependent on X. Therefore, +its not a straight sort where every element can be compared to another...only some of the +elements have any sorting preference, and then only towards just some of the other elements. +For a particular partial ordering, there can be many possible sorts that satisfy the +conditions. + +An intrinsic "gotcha" to this algorithm is that since there are many possible outcomes +to sorting a partial ordering, the algorithm can return any number of different results for the +same input; just running it on a different machine architecture, or just random differences +in the ordering of dictionaries, can change the result that is returned. While this result +is guaranteed to be true to the incoming partial ordering, if the partial ordering itself +does not properly represent the dependencies, code that works fine will suddenly break, then +work again, then break, etc. Most of the bugs I've chased down while developing the "unit of work" +have been of this nature - very tricky to reproduce and track down, particularly before I +realized this characteristic of the algorithm. +""" +import string, StringIO +from sqlalchemy import util +from sqlalchemy.exceptions import * + +class _Node(object): + """represents each item in the sort. While the topological sort + produces a straight ordered list of items, _Node ultimately stores a tree-structure + of those items which are organized so that non-dependent nodes are siblings.""" + def __init__(self, item): + self.item = item + self.dependencies = util.Set() + self.children = [] + self.cycles = None + def __str__(self): + return self.safestr() + def safestr(self, indent=0): + return (' ' * indent * 2) + \ + str(self.item) + \ + (self.cycles is not None and (" (cycles: " + repr([x for x in self.cycles]) + ")") or "") + \ + "\n" + \ + string.join([n.safestr(indent + 1) for n in self.children], '') + def __repr__(self): + return "%s" % (str(self.item)) + def is_dependent(self, child): + if self.cycles is not None: + for c in self.cycles: + if child in c.dependencies: + return True + if child.cycles is not None: + for c in child.cycles: + if c in self.dependencies: + return True + return child in self.dependencies + +class _EdgeCollection(object): + """a collection of directed edges.""" + def __init__(self): + self.parent_to_children = {} + self.child_to_parents = {} + def add(self, edge): + """add an edge to this collection.""" + (parentnode, childnode) = edge + if not self.parent_to_children.has_key(parentnode): + self.parent_to_children[parentnode] = util.Set() + self.parent_to_children[parentnode].add(childnode) + if not self.child_to_parents.has_key(childnode): + self.child_to_parents[childnode] = util.Set() + self.child_to_parents[childnode].add(parentnode) + parentnode.dependencies.add(childnode) + def remove(self, edge): + """remove an edge from this collection. return the childnode if it has no other parents""" + (parentnode, childnode) = edge + self.parent_to_children[parentnode].remove(childnode) + self.child_to_parents[childnode].remove(parentnode) + if len(self.child_to_parents[childnode]) == 0: + return childnode + else: + return None + def has_parents(self, node): + return self.child_to_parents.has_key(node) and len(self.child_to_parents[node]) > 0 + def edges_by_parent(self, node): + if self.parent_to_children.has_key(node): + return [(node, child) for child in self.parent_to_children[node]] + else: + return [] + def get_parents(self): + return self.parent_to_children.keys() + def pop_node(self, node): + """remove all edges where the given node is a parent. + + returns the collection of all nodes which were children of the given node, and have + no further parents.""" + children = self.parent_to_children.pop(node, None) + if children is not None: + for child in children: + self.child_to_parents[child].remove(node) + if not len(self.child_to_parents[child]): + yield child + def __len__(self): + return sum([len(x) for x in self.parent_to_children.values()]) + def __iter__(self): + for parent, children in self.parent_to_children.iteritems(): + for child in children: + yield (parent, child) + def __str__(self): + return repr(list(self)) + + +class QueueDependencySorter(object): + """topological sort adapted from wikipedia's article on the subject. it creates a straight-line + list of elements, then a second pass groups non-dependent actions together to build + more of a tree structure with siblings.""" + + def __init__(self, tuples, allitems): + self.tuples = tuples + self.allitems = allitems + + def sort(self, allow_self_cycles=True, allow_all_cycles=False): + (tuples, allitems) = (self.tuples, self.allitems) + #print "\n---------------------------------\n" + #print repr([t for t in tuples]) + #print repr([a for a in allitems]) + #print "\n---------------------------------\n" + + nodes = {} + edges = _EdgeCollection() + for item in allitems + [t[0] for t in tuples] + [t[1] for t in tuples]: + if not nodes.has_key(item): + node = _Node(item) + nodes[item] = node + + for t in tuples: + if t[0] is t[1]: + if allow_self_cycles: + n = nodes[t[0]] + n.cycles = util.Set([n]) + continue + else: + raise FlushError("Self-referential dependency detected " + repr(t)) + childnode = nodes[t[1]] + parentnode = nodes[t[0]] + edges.add((parentnode, childnode)) + + queue = [] + for n in nodes.values(): + if not edges.has_parents(n): + queue.append(n) + cycles = {} + output = [] + while len(nodes) > 0: + if len(queue) == 0: + # edges remain but no edgeless nodes to remove; this indicates + # a cycle + if allow_all_cycles: + x = self._find_cycles(edges) + for cycle in self._find_cycles(edges): + lead = cycle[0][0] + lead.cycles = util.Set() + for edge in cycle: + n = edges.remove(edge) + lead.cycles.add(edge[0]) + lead.cycles.add(edge[1]) + if n is not None: + queue.append(n) + for n in lead.cycles: + if n is not lead: + n._cyclical = True + for (n,k) in list(edges.edges_by_parent(n)): + edges.add((lead, k)) + edges.remove((n,k)) + continue + else: + # long cycles not allowed + raise FlushError("Circular dependency detected " + repr(edges) + repr(queue)) + node = queue.pop() + if not hasattr(node, '_cyclical'): + output.append(node) + del nodes[node.item] + for childnode in edges.pop_node(node): + queue.append(childnode) + return self._create_batched_tree(output) + + + def _create_batched_tree(self, nodes): + """given a list of nodes from a topological sort, organizes the nodes into a tree structure, + with as many non-dependent nodes set as silbings to each other as possible.""" + def sort(index=None, l=None): + if index is None: + index = 0 + + if index >= len(nodes): + return None + + node = nodes[index] + l2 = [] + sort(index + 1, l2) + for n in l2: + if l is None or search_dep(node, n): + node.children.append(n) + else: + l.append(n) + if l is not None: + l.append(node) + return node + + def search_dep(parent, child): + if child is None: + return False + elif parent.is_dependent(child): + return True + else: + for c in child.children: + x = search_dep(parent, c) + if x is True: + return True + else: + return False + return sort() + + def _find_cycles(self, edges): + involved_in_cycles = util.Set() + cycles = {} + def traverse(node, goal=None, cycle=None): + if goal is None: + goal = node + cycle = [] + elif node is goal: + return True + + for (n, key) in edges.edges_by_parent(node): + if key in cycle: + continue + cycle.append(key) + if traverse(key, goal, cycle): + cycset = util.Set(cycle) + for x in cycle: + involved_in_cycles.add(x) + if cycles.has_key(x): + existing_set = cycles[x] + [existing_set.add(y) for y in cycset] + for y in existing_set: + cycles[y] = existing_set + cycset = existing_set + else: + cycles[x] = cycset + cycle.pop() + + for parent in edges.get_parents(): + traverse(parent) + + for cycle in dict([(id(s), s) for s in cycles.values()]).values(): + edgecollection = [] + for edge in edges: + if edge[0] in cycle and edge[1] in cycle: + edgecollection.append(edge) + yield edgecollection + diff --git a/spyce-2.1/sqlalchemy/topological.pyc b/spyce-2.1/sqlalchemy/topological.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da5681827f5d701f1edd6ff695b0f98762aae2ca Binary files /dev/null and b/spyce-2.1/sqlalchemy/topological.pyc differ diff --git a/spyce-2.1/sqlalchemy/types.py b/spyce-2.1/sqlalchemy/types.py new file mode 100755 index 0000000000000000000000000000000000000000..d7e9d8ce6b96bfee2169cf65b7934a9336b27531 --- /dev/null +++ b/spyce-2.1/sqlalchemy/types.py @@ -0,0 +1,305 @@ +# types.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +__all__ = [ 'TypeEngine', 'TypeDecorator', 'NullTypeEngine', + 'INT', 'CHAR', 'VARCHAR', 'NCHAR', 'TEXT', 'FLOAT', 'DECIMAL', + 'TIMESTAMP', 'DATETIME', 'CLOB', 'BLOB', 'BOOLEAN', 'String', 'Integer', 'Smallinteger', + 'Numeric', 'Float', 'DateTime', 'Date', 'Time', 'Binary', 'Boolean', 'Unicode', 'PickleType', 'NULLTYPE', + 'SMALLINT', 'DATE', 'TIME' + ] + +from sqlalchemy import util, exceptions +try: + import cPickle as pickle +except: + import pickle + +class AbstractType(object): + def _get_impl_dict(self): + try: + return self._impl_dict + except AttributeError: + self._impl_dict = {} + return self._impl_dict + impl_dict = property(_get_impl_dict) + + def copy_value(self, value): + return value + def compare_values(self, x, y): + return x is y + def is_mutable(self): + return False + def get_dbapi_type(self, dbapi): + """return the corresponding type object from the underlying DBAPI, if any. + + this can be useful for calling setinputsizes(), for example.""" + return None + +class TypeEngine(AbstractType): + def __init__(self, *args, **params): + pass + def engine_impl(self, engine): + """deprecated; call dialect_impl with a dialect directly.""" + return self.dialect_impl(engine.dialect) + def dialect_impl(self, dialect): + try: + return self.impl_dict[dialect] + except KeyError: + return self.impl_dict.setdefault(dialect, dialect.type_descriptor(self)) + def _get_impl(self): + if hasattr(self, '_impl'): + return self._impl + else: + return NULLTYPE + def _set_impl(self, impl): + self._impl = impl + impl = property(_get_impl, _set_impl) + def get_col_spec(self): + raise NotImplementedError() + def convert_bind_param(self, value, dialect): + return value + def convert_result_value(self, value, dialect): + return value + def adapt(self, cls): + return cls() + + +class TypeDecorator(AbstractType): + def __init__(self, *args, **kwargs): + if not hasattr(self.__class__, 'impl'): + raise exceptions.AssertionError("TypeDecorator implementations require a class-level variable 'impl' which refers to the class of type being decorated") + self.impl = self.__class__.impl(*args, **kwargs) + def engine_impl(self, engine): + return self.dialect_impl(engine.dialect) + def dialect_impl(self, dialect): + try: + return self.impl_dict[dialect] + except: + typedesc = dialect.type_descriptor(self.impl) + tt = self.copy() + if not isinstance(tt, self.__class__): + raise exceptions.AssertionError("Type object %s does not properly implement the copy() method, it must return an object of type %s" % (self, self.__class__)) + tt.impl = typedesc + self.impl_dict[dialect] = tt + return tt + def __getattr__(self, key): + """proxy all other undefined accessors to the underlying implementation.""" + return getattr(self.impl, key) + def get_col_spec(self): + return self.impl.get_col_spec() + def convert_bind_param(self, value, dialect): + return self.impl.convert_bind_param(value, dialect) + def convert_result_value(self, value, dialect): + return self.impl.convert_result_value(value, dialect) + def copy(self): + instance = self.__class__.__new__(self.__class__) + instance.__dict__.update(self.__dict__) + return instance + def get_dbapi_type(self, dbapi): + return self.impl.get_dbapi_type(dbapi) + def copy_value(self, value): + return self.impl.copy_value(value) + def compare_values(self, x, y): + return self.impl.compare_values(x,y) + def is_mutable(self): + return self.impl.is_mutable() + +class MutableType(object): + """a mixin that marks a Type as holding a mutable object""" + def is_mutable(self): + return True + def copy_value(self, value): + raise NotImplementedError() + def compare_values(self, x, y): + return x == y + +def to_instance(typeobj): + if typeobj is None: + return NULLTYPE + elif isinstance(typeobj, type): + return typeobj() + else: + return typeobj +def adapt_type(typeobj, colspecs): + if isinstance(typeobj, type): + typeobj = typeobj() + + for t in typeobj.__class__.__mro__[0:-1]: + try: + impltype = colspecs[t] + break + except KeyError: + pass + else: + # couldnt adapt...raise exception ? + return typeobj + # if we adapted the given generic type to a database-specific type, + # but it turns out the originally given "generic" type + # is actually a subclass of our resulting type, then we were already + # were given a more specific type than that required; so use that. + if (issubclass(typeobj.__class__, impltype)): + return typeobj + return typeobj.adapt(impltype) + +class NullTypeEngine(TypeEngine): + def get_col_spec(self): + raise NotImplementedError() + def convert_bind_param(self, value, dialect): + return value + def convert_result_value(self, value, dialect): + return value + +class String(TypeEngine): + def __new__(cls, *args, **kwargs): + if cls is not String or len(args) > 0 or kwargs.has_key('length'): + return super(String, cls).__new__(cls, *args, **kwargs) + else: + return super(String, TEXT).__new__(TEXT, *args, **kwargs) + def __init__(self, length = None): + self.length = length + def adapt(self, impltype): + return impltype(length=self.length) + def convert_bind_param(self, value, dialect): + if not dialect.convert_unicode or value is None or not isinstance(value, unicode): + return value + else: + return value.encode(dialect.encoding) + def convert_result_value(self, value, dialect): + if not dialect.convert_unicode or value is None or isinstance(value, unicode): + return value + else: + return value.decode(dialect.encoding) + def get_dbapi_type(self, dbapi): + return dbapi.STRING + def compare_values(self, x, y): + return x == y + +class Unicode(TypeDecorator): + impl = String + def convert_bind_param(self, value, dialect): + if value is not None and isinstance(value, unicode): + return value.encode(dialect.encoding) + else: + return value + def convert_result_value(self, value, dialect): + if value is not None and not isinstance(value, unicode): + return value.decode(dialect.encoding) + else: + return value + +class Integer(TypeEngine): + """integer datatype""" + def get_dbapi_type(self, dbapi): + return dbapi.NUMBER + +class SmallInteger(Integer): + """ smallint datatype """ + pass +Smallinteger = SmallInteger + +class Numeric(TypeEngine): + def __init__(self, precision = 10, length = 2): + self.precision = precision + self.length = length + def adapt(self, impltype): + return impltype(precision=self.precision, length=self.length) + def get_dbapi_type(self, dbapi): + return dbapi.NUMBER + +class Float(Numeric): + def __init__(self, precision = 10): + self.precision = precision + def adapt(self, impltype): + return impltype(precision=self.precision) + +class DateTime(TypeEngine): + """implements a type for datetime.datetime() objects""" + def __init__(self, timezone=True): + self.timezone = timezone + def adapt(self, impltype): + return impltype(timezone=self.timezone) + def get_dbapi_type(self, dbapi): + return dbapi.DATETIME + +class Date(TypeEngine): + """implements a type for datetime.date() objects""" + def get_dbapi_type(self, dbapi): + return dbapi.DATETIME + +class Time(TypeEngine): + """implements a type for datetime.time() objects""" + def __init__(self, timezone=True): + self.timezone = timezone + def adapt(self, impltype): + return impltype(timezone=self.timezone) + def get_dbapi_type(self, dbapi): + return dbapi.DATETIME + +class Binary(TypeEngine): + def __init__(self, length=None): + self.length = length + def convert_bind_param(self, value, dialect): + if value is not None: + return dialect.dbapi().Binary(value) + else: + return None + def convert_result_value(self, value, dialect): + return value + def adapt(self, impltype): + return impltype(length=self.length) + def get_dbapi_type(self, dbapi): + return dbapi.BINARY + +class PickleType(MutableType, TypeDecorator): + impl = Binary + def __init__(self, protocol=pickle.HIGHEST_PROTOCOL, pickler=None, mutable=True): + self.protocol = protocol + self.pickler = pickler or pickle + self.mutable = mutable + super(PickleType, self).__init__() + def convert_result_value(self, value, dialect): + if value is None: + return None + buf = self.impl.convert_result_value(value, dialect) + return self.pickler.loads(str(buf)) + def convert_bind_param(self, value, dialect): + if value is None: + return None + return self.impl.convert_bind_param(self.pickler.dumps(value, self.protocol), dialect) + def copy_value(self, value): + if self.mutable: + return self.pickler.loads(self.pickler.dumps(value, self.protocol)) + else: + return value + def compare_values(self, x, y): + if self.mutable: + return self.pickler.dumps(x, self.protocol) == self.pickler.dumps(y, self.protocol) + else: + return x is y + def is_mutable(self): + return self.mutable + +class Boolean(TypeEngine): + pass + +class FLOAT(Float):pass +class TEXT(String):pass +class DECIMAL(Numeric):pass +class INT(Integer):pass +INTEGER = INT +class SMALLINT(Smallinteger):pass +class TIMESTAMP(DateTime): pass +class DATETIME(DateTime): pass +class DATE(Date): pass +class TIME(Time): pass +class CLOB(String): pass +class VARCHAR(String): pass +class CHAR(String):pass +class NCHAR(Unicode):pass +class BLOB(Binary): pass +class BOOLEAN(Boolean): pass + +NULLTYPE = NullTypeEngine() diff --git a/spyce-2.1/sqlalchemy/types.pyc b/spyce-2.1/sqlalchemy/types.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f54efc116be71ebcfc51200977dcd92efd5e7f9 Binary files /dev/null and b/spyce-2.1/sqlalchemy/types.pyc differ diff --git a/spyce-2.1/sqlalchemy/util.py b/spyce-2.1/sqlalchemy/util.py new file mode 100755 index 0000000000000000000000000000000000000000..bd40039d4e88b79772d7f1733f05d478d9441ef7 --- /dev/null +++ b/spyce-2.1/sqlalchemy/util.py @@ -0,0 +1,301 @@ +# util.py +# Copyright (C) 2005,2006 Michael Bayer mike_mp@zzzcomputing.com +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +import weakref, UserList, time, string, inspect, sys, sets +try: + import thread, threading +except ImportError: + import dummy_thread as thread + import dummy_threading as threading + +from sqlalchemy.exceptions import * +import __builtin__ + +try: + Set = set +except: + Set = sets.Set + +def to_list(x): + if x is None: + return None + if not isinstance(x, list) and not isinstance(x, tuple): + return [x] + else: + return x + +def to_set(x): + if x is None: + return Set() + if not isinstance(x, Set): + return Set(to_list(x)) + else: + return x + +def reversed(seq): + try: + return __builtin__.reversed(seq) + except: + def rev(): + i = len(seq) -1 + while i >= 0: + yield seq[i] + i -= 1 + raise StopIteration() + return rev() + +class ArgSingleton(type): + instances = {} + def __call__(self, *args): + hashkey = (self, args) + try: + return ArgSingleton.instances[hashkey] + except KeyError: + instance = type.__call__(self, *args) + ArgSingleton.instances[hashkey] = instance + return instance + +def get_cls_kwargs(cls): + """return the full set of legal kwargs for the given cls""" + kw = [] + for c in cls.__mro__: + cons = c.__init__ + if hasattr(cons, 'func_code'): + for vn in cons.func_code.co_varnames: + if vn != 'self': + kw.append(vn) + return kw + +class SimpleProperty(object): + """a "default" property accessor.""" + def __init__(self, key): + self.key = key + def __set__(self, obj, value): + setattr(obj, self.key, value) + def __delete__(self, obj): + delattr(obj, self.key) + def __get__(self, obj, owner): + if obj is None: + return self + else: + return getattr(obj, self.key) + +class OrderedProperties(object): + """ + An object that maintains the order in which attributes are set upon it. + also provides an iterator and a very basic getitem/setitem interface to those attributes. + + (Not really a dict, since it iterates over values, not keys. Not really + a list, either, since each value must have a key associated; hence there is + no append or extend.) + """ + def __init__(self): + self.__dict__['_OrderedProperties__data'] = OrderedDict() + def __len__(self): + return len(self.__data) + def __iter__(self): + return self.__data.itervalues() + def __add__(self, other): + return list(self) + list(other) + def __setitem__(self, key, object): + self.__data[key] = object + def __getitem__(self, key): + return self.__data[key] + def __delitem__(self, key): + del self.__data[key] + def __setattr__(self, key, object): + self.__data[key] = object + def __getattr__(self, key): + try: + return self.__data[key] + except KeyError: + raise AttributeError(key) + def __contains__(self, key): + return key in self.__data + def get(self, key, default=None): + if self.has_key(key): + return self[key] + else: + return default + def keys(self): + return self.__data.keys() + def has_key(self, key): + return self.__data.has_key(key) + def clear(self): + self.__data.clear() + +class OrderedDict(dict): + """A Dictionary that returns keys/values/items in the order they were added""" + def __init__(self, d=None, **kwargs): + self._list = [] + self.update(d, **kwargs) + def keys(self): + return list(self._list) + def clear(self): + self._list = [] + dict.clear(self) + def update(self, d=None, **kwargs): + # d can be a dict or sequence of keys/values + if d: + if hasattr(d, 'iteritems'): + seq = d.iteritems() + else: + seq = d + for key, value in seq: + self.__setitem__(key, value) + if kwargs: + self.update(kwargs) + def setdefault(self, key, value): + if not self.has_key(key): + self.__setitem__(key, value) + return value + else: + return self.__getitem__(key) + def values(self): + return [self[key] for key in self._list] + def __iter__(self): + return iter(self._list) + def itervalues(self): + return iter([self[key] for key in self._list]) + def iterkeys(self): + return self.__iter__() + def iteritems(self): + return iter([(key, self[key]) for key in self.keys()]) + def __delitem__(self, key): + try: + del self._list[self._list.index(key)] + except ValueError: + raise KeyError(key) + dict.__delitem__(self, key) + def __setitem__(self, key, object): + if not self.has_key(key): + self._list.append(key) + dict.__setitem__(self, key, object) + def __getitem__(self, key): + return dict.__getitem__(self, key) + +class ThreadLocal(object): + """an object in which attribute access occurs only within the context of the current thread""" + def __init__(self): + self.__dict__['_tdict'] = {} + def __delattr__(self, key): + try: + del self._tdict["%d_%s" % (thread.get_ident(), key)] + except KeyError: + raise AttributeError(key) + def __getattr__(self, key): + try: + return self._tdict["%d_%s" % (thread.get_ident(), key)] + except KeyError: + raise AttributeError(key) + def __setattr__(self, key, value): + self._tdict["%d_%s" % (thread.get_ident(), key)] = value + +class DictDecorator(dict): + """a Dictionary that delegates items not found to a second wrapped dictionary.""" + def __init__(self, decorate): + self.decorate = decorate + def __getitem__(self, key): + try: + return dict.__getitem__(self, key) + except KeyError: + return self.decorate[key] + def __repr__(self): + return dict.__repr__(self) + repr(self.decorate) + +class OrderedSet(sets.Set): + def __init__(self, iterable=None): + """Construct a set from an optional iterable.""" + self._data = OrderedDict() + if iterable is not None: + self._update(iterable) + +class UniqueAppender(object): + def __init__(self, data): + self.data = data + if hasattr(data, 'append'): + self._data_appender = data.append + elif hasattr(data, 'add'): + self._data_appender = data.add + self.set = Set() + def append(self, item): + if item not in self.set: + self.set.add(item) + self._data_appender(item) + +class ScopedRegistry(object): + """a Registry that can store one or multiple instances of a single class + on a per-thread scoped basis, or on a customized scope + + createfunc - a callable that returns a new object to be placed in the registry + scopefunc - a callable that will return a key to store/retrieve an object, + defaults to thread.get_ident for thread-local objects. use a value like + lambda: True for application scope. + """ + def __init__(self, createfunc, scopefunc=None): + self.createfunc = createfunc + if scopefunc is None: + self.scopefunc = thread.get_ident + else: + self.scopefunc = scopefunc + self.registry = {} + def __call__(self): + key = self._get_key() + try: + return self.registry[key] + except KeyError: + return self.registry.setdefault(key, self.createfunc()) + def set(self, obj): + self.registry[self._get_key()] = obj + def clear(self): + try: + del self.registry[self._get_key()] + except KeyError: + pass + def _get_key(self): + return self.scopefunc() + + +def constructor_args(instance, **kwargs): + """given an object instance and keyword arguments, inspects the + argument signature of the instance's __init__ method and returns + a tuple of list and keyword arguments, suitable for creating a new + instance of the class. The returned arguments are drawn from the + given keyword dictionary, or if not found are drawn from the + corresponding attributes of the original instance.""" + classobj = instance.__class__ + + argspec = inspect.getargspec(classobj.__init__.im_func) + + argnames = argspec[0] or [] + defaultvalues = argspec[3] or [] + + (requiredargs, namedargs) = ( + argnames[0:len(argnames) - len(defaultvalues)], + argnames[len(argnames) - len(defaultvalues):] + ) + + newparams = {} + + for arg in requiredargs: + if arg == 'self': + continue + elif kwargs.has_key(arg): + newparams[arg] = kwargs[arg] + else: + newparams[arg] = getattr(instance, arg) + + for arg in namedargs: + if kwargs.has_key(arg): + newparams[arg] = kwargs[arg] + else: + if hasattr(instance, arg): + newparams[arg] = getattr(instance, arg) + else: + raise AssertionError("instance has no attribute '%s'" % arg) + + return newparams + diff --git a/spyce-2.1/sqlalchemy/util.pyc b/spyce-2.1/sqlalchemy/util.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca8dabc694edc184cacf21dad29466949cebbfa2 Binary files /dev/null and b/spyce-2.1/sqlalchemy/util.pyc differ diff --git a/spyce-2.1/tags/_coreutil.py b/spyce-2.1/tags/_coreutil.py new file mode 100755 index 0000000000000000000000000000000000000000..c1d7687983fa080e8f570b82da19ad3fe51a0191 --- /dev/null +++ b/spyce-2.1/tags/_coreutil.py @@ -0,0 +1,83 @@ +import cPickle as pickle +import spyceUtil, spyceLock +from spyceModule import moduleFinder +from session import newtoken + +def logout(api): + api.cookie.delete('_spy_login') + api.redirect.external(api.request.uri()) + api.response.end() + +def login_from_cookie(request): + import sys + sys.stderr.write('_cookie!') + from spyceConfig import login_storage + cookie = request._api.getModule('cookie') + if '_spy_login' in cookie: + uid, token = pickle.loads(cookie['_spy_login']) + if login_storage.validate(uid, token): + return uid + return None + +def login_stub(request, validator, login, password, rememberme=False): + try: + uid = validator(login, password) + except TypeError: + raise 'invalid login_validator function in spyceconf' + if uid is not None: + from spyceConfig import login_storage + mf = moduleFinder(request._api) + token = newtoken(mf) + login_storage.set(uid, token) + if rememberme: + expires = 3600 * 24 * 365 * 10 # 10 years + else: + expires = None + mf.cookie.set('_spy_login', pickle.dumps((uid, token)), expires) + return uid + +def login_perform(request, validator): + return login_stub(request, validator, + request.getpost1('_spy_login_user'), + request.getpost1('_spy_login_password'), + request.getpost1('_spy_login_rememberme')) + +def login_pending(request): + return request.getpost('_spy_login_user') + +# storage objects must have "set" and "validate" methods +# with the signatures demonstrated here. +class FileStorage: + def __init__(self, path): + self.path = path + def set(self, uid, token): + p = self._path(uid) + L = FileStorage._getLock(p) + L.acquire() + try: + open(p, 'w').write(token) + finally: + L.release() + def validate(self, uid, token): + p = self._path(uid) + L = FileStorage._getLock(p) + L.acquire() + try: + try: + return token == open(p).read() + except IOError: + return False + finally: + L.release() + def _path(self, uid): + import spyce, os.path + return os.path.join(self.path, 'spytoken-%s' % uid) + # cache locks for speed + locks = {} + def _getLock(cls, path): + # it's okay if we generate multiple locks for the same path + # so we don't bother with thread-level locking here + if path not in FileStorage.locks: + FileStorage.locks[path] = spyceLock.fileLock(path) + return FileStorage.locks[path] + _getLock = classmethod(_getLock) diff --git a/spyce-2.1/tags/_coreutil.pyc b/spyce-2.1/tags/_coreutil.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e794a8a1314751f73ad35eba48a97b2312707cc9 Binary files /dev/null and b/spyce-2.1/tags/_coreutil.pyc differ diff --git a/spyce-2.1/tags/_formhelper.py b/spyce-2.1/tags/_formhelper.py new file mode 100755 index 0000000000000000000000000000000000000000..6ea23ccc6958f8d8c0813b340b7cbc1fe324a4a9 --- /dev/null +++ b/spyce-2.1/tags/_formhelper.py @@ -0,0 +1,47 @@ +def formatArgs(kwargs): + if not kwargs: + return '' + L = [] + for key in kwargs.keys(): + if kwargs[key] is None: + L.append(key) + else: + L.append('%s="%s"' % (key, kwargs[key])) + return ' ' + ' '.join(L) + +def find_selected(tag, name, selected, default): + if selected is None: + selected = tag._api.getModule('request').getpost(name) + if selected is None and default is not None: + selected = default + if selected is None: + return [] + if isinstance(selected, basestring) and selected.startswith('='): + selected = tag.eval(selected) + if isinstance(selected, basestring) or not hasattr(selected, '__contains__'): + selected = (selected,) + return selected + +def escape_dq(value): + return str(value).replace('"', '\\"') + +def render_radio(tagname, name, label, value, checked, id=None, kwargs=None): + if id is None: + id = name + checkedstr = checked and ' CHECKED' or '' + baseradio = '' \ + % (tagname, name, id, escape_dq(value), checkedstr, formatArgs(kwargs)) + if label is not None: + baseradio += ' ' % (id, label) + return baseradio + +def render_option(value, text, selected): + if selected: + selectedtxt = ' selected' + else: + selectedtxt = '' + return '' % (escape_dq(value), selectedtxt, text) + +def maybe_emit_label(tag, id, label): + if label is not None: + tag.getOut().write(' ' % (id, label)) diff --git a/spyce-2.1/tags/_formhelper.pyc b/spyce-2.1/tags/_formhelper.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9001c5260f1d0bd72efb97cab517f27432ee9baa Binary files /dev/null and b/spyce-2.1/tags/_formhelper.pyc differ diff --git a/spyce-2.1/tags/core.py b/spyce-2.1/tags/core.py new file mode 100755 index 0000000000000000000000000000000000000000..38770f57f4378a7703a7251aa5146f928af7dbf4 --- /dev/null +++ b/spyce-2.1/tags/core.py @@ -0,0 +1,143 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: core.py 1172 2006-08-29 16:59:46Z ellisj $ +################################################## + +__doc__ = '''Core Spyce tags.''' + +import os.path +from spyceTag import spyceTagLibrary, spyceTagPlus, spyceTag, invokeSingleton +from spyceException import spyceDone +import spyce, spyceUtil +from _formhelper import formatArgs + +_parent_files = {} +class tag_parent(spyceTagPlus): + name = 'parent' + def syntax(self): + self.syntaxSingleOnly() + def filename(self, uri): + return spyceUtil.url2file(uri, self.getModule('request').filename()) + def find_parent(self, src): + if not src: + # check for parent file in current directory + # (we don't climb further up the path tree, though; that doesn't make + # sense for CGI-based installations) + immediateparent = self.filename('parent.spi') + if os.path.exists(immediateparent) \ + and immediateparent != self.getModule('request').filename(): + src = 'parent.spi' + else: + src = spyce.getServer().config.defaultparent + if not src: + raise 'src not specified, no parent.spi found in current directory, and defaultparent not configured' + return src + def begin(self, src=None, **kwargs): + key = (self.getModule('request').filename(), src) + try: + pfile = _parent_files[key] + except KeyError: + pfile = _parent_files[key] = self.filename(self.find_parent(src)) + if self._api._parent: + spyce.DEBUG('warning: replacing old parent %s with %s' % (self._api._parent, src)) + self._api._parent = (pfile, kwargs) + + +# most of the work here is special-cased by spyceCompile +class tag_login_required(spyceTagPlus): + name = 'login_required' + def syntax(self): + self.syntaxSingleOnly() + def begin(self, validator=None): + import spyceConfig + invokeSingleton(self._api, spyceConfig.loginrequired_render) + raise spyceDone() + + +# most of the work here is special-cased by spyceCompile +class tag_login(spyceTagPlus): + name = 'login' + def syntax(self): + self.syntaxSingleOnly() + def begin(self, validator=None): + self.parentRequired('form') + import spyceConfig + invokeSingleton(self._api, spyceConfig.login_render) + + +# modified from code generated by spyceCompile from the following: +# [[.begin name=logout singleton=True]] +# +# [[.end]] +class tag_logout(spyceTagPlus): + name='logout' + def syntax(self): + self.syntaxSingleOnly() + def begin(self): + self.parentRequired('form') + + request = self._api.getModule('request') + taglib = self._api.getModules()['taglib'] + taglib.load('form','form.py','f') + + taglib.tagPush('f','submit','_spy_logout',locals(),{'handler':'_coreutil.logout','value':'Log out'},False) + try: + taglib.tagBegin() + taglib.tagBody() + taglib.tagEnd() + finally:taglib.tagPop() + + # TODO spoof line numbers in classcode better + handlers = {'_spy_logout': ['_coreutil.logout']} + + +class tag_list(spyceTagPlus): + def begin(self, data, **kwargs): + self.getOut().write('<%s%s>' % (self.name, formatArgs(kwargs))) + for item in self.eval(data): + self.getOut().write('
  • %s
  • ' % item) + def end(self): + self.getOut().write('' % self.name) +class tag_ul(tag_list): + name = 'ul' +class tag_ol(tag_list): + name = 'ol' + +class tag_dl(spyceTagPlus): + name = 'dl' + def begin(self, data, **kwargs): + self.getOut().write('' % formatArgs(kwargs)) + for term, desc in self.eval(data): + self.getOut().write('
    %s
    %s
    ' % (term, desc)) + def end(self): + self.getOut().write('') + +class tag_table(spyceTagPlus): + name = 'table' + def begin(self, data, **kwargs): + self.getOut().write('' % formatArgs(kwargs)) + for row in self.eval(data): + self.getOut().write('') + for item in row: + self.getOut().write('%s' % item) + self.getOut().write('') + def end(self): + self.getOut().write('') + +class core(spyceTagLibrary): + tags = [ + tag_parent, + + tag_login_required, + tag_login, + tag_logout, + + tag_ul, + tag_ol, + tag_dl, + tag_table, + ] + diff --git a/spyce-2.1/tags/core.pyc b/spyce-2.1/tags/core.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12b494aa1a118693e7a6ba3df419f51c9196c824 Binary files /dev/null and b/spyce-2.1/tags/core.pyc differ diff --git a/spyce-2.1/tags/form.py b/spyce-2.1/tags/form.py new file mode 100755 index 0000000000000000000000000000000000000000..e5c612c45ea70458cfb4a8b488af42d4ba54d239 --- /dev/null +++ b/spyce-2.1/tags/form.py @@ -0,0 +1,227 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +################################################## + +__doc__ = '''Spyce tags for create data-populated forms.''' + +from spyceTag import spyceTagLibrary, spyceTagPlus, spyceTagSyntaxException +from _formhelper import * +import spyceUtil, spyceConfig +import string, urllib, re + + +class form_form(spyceTagPlus): + name = 'form' + mustend = 1 + def syntax(self): + self.syntaxPairOnly() + def begin(self, method='POST', action=None, **kwargs): + if self.getParent('form'): + raise 'Nested form tags are not allowed' + id = self._context['_current_form_id'] + err = self._context.get('_validation_error', None) + if err and id in err: + invokeSingleton(self._api, spyceConfig.validation_render, err=err[id]) + method = string.upper(method) + if method not in ['GET', 'POST']: + raise 'invalid method attribute value: '+method + if action is None: + action = self.getModule('request').uri_path() + self.getOut().write('
    ' % ( + method, action, formatArgs(kwargs)) ) + def end(self): + self.getOut().write('
    ') + +class form_submit(spyceTagPlus): + name = 'submit' + handlers = {} # non-None so spyceCompile will handle handler attr + def syntax(self): + self.syntaxSingleOnly() + def begin(self, withx='button', **kwargs): + self.parentRequired('form') + if 'name' in kwargs: + raise "invalid submit tag attribute 'name' (use handlers to set different actions for different submit buttons)" + id = '_submit' + self.getFullId() + if withx == 'button': + html = '' % (id, id, formatArgs(kwargs),) + else: + # *** confirm needs work + js = kwargs['onclick'] + js += '' # *** create hidden element w/ submit ID + if 'value' in kwargs: + v = kwargs['value'] + else: + v = 'Submit' + html = '%s' % (id, js, v) + self.getOut().write(html) + +class form_hidden(spyceTagPlus): + name = 'hidden' + def syntax(self): + self.syntaxSingleOnly() + def begin(self, name, value=None, default=None, _input=None, **kwargs): + if not _input: + _input = self.name + if value is None: + value = self.getModule('request').getpost1(name, default) or '' + self.getOut().write('' % ( + _input, name, name, escape_dq(value), formatArgs(kwargs)) ) + +class form_text(form_hidden): + name = 'text' + def begin(self, name, value=None, default=None, label=None, **kwargs): + maybe_emit_label(self, name, label) + form_hidden.begin(self, name, value, default, **kwargs) + +class form_password(form_text): + name = 'password' + +date_img = 0 +class form_date(form_text): + name='date' + def begin(self, name, value=None, default=None, size=0, format='MM/dd/yyyy', label=None, **kwargs): + maybe_emit_label(self, name, label) + if not hasattr(self.getModule('request'), '_calendarjs'): + self.getOut().write(''' + + +''') + self.getModule('request')._calendarjs = True + # textbox, leveraging form_hidden + if not size: + size = len(format) + kwargs['maxlength'] = len(format) + kwargs['size'] = size + form_hidden.begin(self, name, value, default, 'text', **kwargs) + # thread safety isn't a concern here, this is only used clientside + global date_img + i = date_img + date_img += 1 + # calendar icon + self.getOut().write('''''' % locals()) + +class form_textarea(spyceTagPlus): + name = 'textarea' + buffer = 1 + def syntax(self): + # TODO fix this + self.syntaxPairOnly() + def begin(self, name, default=None, value=None, rows=None, cols=None, label=None, **kwargs): + maybe_emit_label(self, name, label) + self._name = name + if value is None: + if default is not None: + value = default + else: + value = self.getModule('request').getpost1(self._name) + self._value = value or '' + if rows!=None: + kwargs['rows'] = rows + if cols!=None: + kwargs['cols'] = cols + self._args = kwargs + def body(self, _contents): + self.getOut().write('' % ( + self._name, self._name, formatArgs(self._args), self._value, _contents)) + +class form_radio(spyceTagPlus): + name = 'radio' + def syntax(self): + self.syntaxSingleOnly() + def begin(self, name, value, checked=0, default=0, label=None, **kwargs): + if checked is None: checked=1 + if default is None: default=1 + if not checked: + checkedValues = self.getModule('request').getpost(name) + if checkedValues is not None: + checked = value in checkedValues + else: + checked = default + tag_id = 'id' in kwargs and kwargs['id'] or name + self.getOut().write(render_radio(self.name, name, label, value, checked, tag_id, kwargs=kwargs)) + +class form_checkbox(form_radio): + name = 'checkbox' + +class form_radiolist(spyceTagPlus): + name = 'radiolist' + def syntax(self): + self.syntaxSingleOnly() + def begin(self, name, data, selected=None, default=None, **kwargs): + radiotype = self.name[:-4] # strip off "list" + selected = find_selected(self, name, selected, default) + self.getOut().write('
    ' % (name, formatArgs(kwargs))) + for i, (option_text, option_value) in enumerate(self.eval(data)): + sel = option_value in selected + self.getOut().write('

    %s

    ' % render_radio(radiotype, name, option_text, option_value, sel, name + str(i))) + self.getOut().write('
    ') + +class form_checkboxlist(form_radiolist): + name = 'checkboxlist' + +class form_select(spyceTagPlus): + name = 'select' + def begin(self, name, multiple=0, data=None, selected=None, default=None, label=None, **kwargs): + maybe_emit_label(self, name, label) + self.varname = name + selected = find_selected(self, name, selected, default) + if multiple is None: + multiple = 1 + multiplestr = multiple and ' MULTIPLE' or '' + self.getOut().write('') + +class form_option(spyceTagPlus): + name = 'option' + def begin(self, text=None, value=None, selected=None, default=None, **kwargs): + self.text = text + if value is None: + valuestr = '' + else: + valuestr = ' value="%s"' % escape_dq(value) + selectTag = self.parentRequired('select') + if value and not selected: + selectedValues = self.getModule('request').getpost(selectTag.varname) + if selectedValues is not None: + selected = value in selectedValues + else: + selected = default + selectedstr = selected and ' SELECTED' or '' + self.getOut().write('') + +class form(spyceTagLibrary): + tags = [ + form_form, + form_submit, + form_hidden, + form_text, + form_password, + form_textarea, + form_radio, + form_checkbox, + form_select, + form_option, + form_date, + form_checkboxlist, + form_radiolist + ] diff --git a/spyce-2.1/tags/form.pyc b/spyce-2.1/tags/form.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c2f8131507ab85c4eb2bc4618ce1d500f76ad01 Binary files /dev/null and b/spyce-2.1/tags/form.pyc differ diff --git a/spyce-2.1/tags/render.spi b/spyce-2.1/tags/render.spi new file mode 100755 index 0000000000000000000000000000000000000000..0c3a6f7cfb916ba8db42988d01e239fb33c2e7df --- /dev/null +++ b/spyce-2.1/tags/render.spi @@ -0,0 +1,54 @@ +[[.tagcollection]] + +[[.begin name=login_required singleton=True]] +[[ title = 'This page requires that you log in to continue' ]] + + + [[= title ]] + + + [[-- this is about the only time you should have a form definition in + an Active Tag --]] +
    [[= title ]]
    +
    + + [[ if '_spy_login_user' in request.post():{ ]] + + [[ } ]] +   +   + + +
    Invalid user / password!
    Login:
    Password:
    Remember this login
    +
    + + +[[.end]] + + +[[.begin name=login singleton=True]] + + [[ if '_spy_login_user' in request.post():{ ]] + + [[ } ]] +   +   + + +
    Invalid user / password!
    Login:
    Password:
    Remember this login
    +[[.end]] + + +[[.begin name=validation singleton=True]] +[[.attr name=err ]] + [[\ + if hasattr(err, 'sub_errors'): + data = [(ve.element, ve.description) for ve in err.sub_errors] + else: + data = [(err.element, err.description)] + ]] +
    + Please correct the following: + +
    +[[.end]] diff --git a/spyce-2.1/tags/render.spic b/spyce-2.1/tags/render.spic new file mode 100644 index 0000000000000000000000000000000000000000..33bad79b4f1d1c889f48b1890fc14e0e452a2097 Binary files /dev/null and b/spyce-2.1/tags/render.spic differ diff --git a/spyce-2.1/tree.py b/spyce-2.1/tree.py new file mode 100755 index 0000000000000000000000000000000000000000..d4465c031e101417818b5f66de4fd180288c8ed7 --- /dev/null +++ b/spyce-2.1/tree.py @@ -0,0 +1,70 @@ +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: tree.py 235 2002-11-14 11:48:25Z batripler $ +################################################## + +import string + +class tree: + def __init__(self, data): + self.data = data + self.parent = None + self.children = [] + self.next = self.prev = None + self.depth = 0 + def append(self, data): + node = tree(data) + self.children.append(node) + node.parent = self + node.depth = self.depth+1 + return node + def delete(self): + for c in self.children: + c.delete() + if self.parent: + self.parent.children.remove(self) + self.parent = None + def __repr__(self): + return '%s [%s]' % (self.data, string.join(map(str, self.children),', ')) + def postWalk(self, f): + for c in self.children: + c.postWalk(f) + f(self) + def preWalk(self, f): + f(self) + for c in self.children: + c.preWalk(f) + def computePreChain(self): + prev = [None] + def walker(node, prev=prev): + node.prev = prev[0] + if prev[0]: + node.prev.next = node + prev[0] = node + self.preWalk(walker) + def __cmp__(self, o): + try: + x = not self.data == o.data + if x: return x + x = not self.children == o.children + if x: return x + except: + return 1 + return 0 + +if __name__=='__main__': + root = tree('1') + n = root.append('1.1') + n.append('1.1.1') + n = root.append('1.2') + n.append('1.2.1') + root.computePreChain() + n = root + while(n): + print n.data + n = n.next + root.delete() + diff --git a/spyce-2.1/tree.pyc b/spyce-2.1/tree.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8cdd8d78f8bbed81f01cb0f091fdf1d57db8cf6f Binary files /dev/null and b/spyce-2.1/tree.pyc differ diff --git a/spyce-2.1/util/form_calendar.gif b/spyce-2.1/util/form_calendar.gif new file mode 100755 index 0000000000000000000000000000000000000000..2ae947fb21f30fd9a571f46d727b51ab2c572258 Binary files /dev/null and b/spyce-2.1/util/form_calendar.gif differ diff --git a/spyce-2.1/util/form_calendar.js b/spyce-2.1/util/form_calendar.js new file mode 100755 index 0000000000000000000000000000000000000000..394556aa14e016e42a723081829c23fde412227f --- /dev/null +++ b/spyce-2.1/util/form_calendar.js @@ -0,0 +1,1464 @@ +// =================================================================== +// Author: Matt Kruse +// WWW: http://www.mattkruse.com/ +// +// NOTICE: You may use this code for any purpose, commercial or +// private, without any further permission from the author. You may +// remove this notice from your final code if you wish, however it is +// appreciated by the author if at least my web site address is kept. +// +// You may *NOT* re-distribute this code in any way except through its +// use. That means, you can include it in your product, or your web +// site, or any other form where the code is actually being used. You +// may not put the plain javascript up on your site for download or +// include it in your javascript libraries for download. +// If you wish to share this code with others, please just point them +// to the URL instead. +// Please DO NOT link directly to my .js files from your site. Copy +// the files to your server and use them there. Thank you. +// +// (Redistribution in Spyce is with permission.) +// =================================================================== + + +/* SOURCE FILE: AnchorPosition.js */ + +/* +AnchorPosition.js +Author: Matt Kruse +Last modified: 5/24/04 + +DESCRIPTION: These functions find the position of an tag in a document, +so other elements can be positioned relative to it. + +COMPATABILITY: Netscape 4.x,6.x,Mozilla, IE 5.x,6.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. + +FUNCTIONS: +getAnchorPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor. Position is relative to the PAGE. + +getAnchorWindowPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor, relative to the WHOLE SCREEN. + +NOTES: + +1) For popping up separate browser windows, use getAnchorWindowPosition. + Otherwise, use getAnchorPosition + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. +*/ + +// getAnchorPosition(anchorname) +// This function returns an object having .x and .y properties which are the coordinates +// of the named anchor, relative to the page. +function getAnchorPosition(anchorname) { + // This function will return an Object with x and y properties + var useWindow=false; + var coordinates=new Object(); + var x=0,y=0; + // Browser capability sniffing + var use_gebi=false, use_css=false, use_layers=false; + if (document.getElementById) { use_gebi=true; } + else if (document.all) { use_css=true; } + else if (document.layers) { use_layers=true; } + // Logic to find position + if (use_gebi && document.all) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_gebi) { + var o=document.getElementById(anchorname); + x=AnchorPosition_getPageOffsetLeft(o); + y=AnchorPosition_getPageOffsetTop(o); + } + else if (use_css) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_layers) { + var found=0; + for (var i=0; i9?"":"0")+x} + +// ------------------------------------------------------------------ +// isDate ( date_string, format_string ) +// Returns true if date string matches format of format string and +// is a valid date. Else returns false. +// It is recommended that you trim whitespace around the value before +// passing it to this function, as whitespace is NOT ignored! +// ------------------------------------------------------------------ +function isDate(val,format) { + var date=getDateFromFormat(val,format); + if (date==0) { return false; } + return true; + } + +// ------------------------------------------------------------------- +// compareDates(date1,date1format,date2,date2format) +// Compare two date strings to see which is greater. +// Returns: +// 1 if date1 is greater than date2 +// 0 if date2 is greater than date1 of if they are the same +// -1 if either of the dates is in an invalid format +// ------------------------------------------------------------------- +function compareDates(date1,dateformat1,date2,dateformat2) { + var d1=getDateFromFormat(date1,dateformat1); + var d2=getDateFromFormat(date2,dateformat2); + if (d1==0 || d2==0) { + return -1; + } + else if (d1 > d2) { + return 1; + } + return 0; + } + +// ------------------------------------------------------------------ +// formatDate (date_object, format) +// Returns a date in the output format specified. +// The format string uses the same abbreviations as in getDateFromFormat() +// ------------------------------------------------------------------ +function formatDate(date,format) { + format=format+""; + var result=""; + var i_format=0; + var c=""; + var token=""; + var y=date.getYear()+""; + var M=date.getMonth()+1; + var d=date.getDate(); + var E=date.getDay(); + var H=date.getHours(); + var m=date.getMinutes(); + var s=date.getSeconds(); + var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k; + // Convert real date parts into formatted versions + var value=new Object(); + if (y.length < 4) {y=""+(y-0+1900);} + value["y"]=""+y; + value["yyyy"]=y; + value["yy"]=y.substring(2,4); + value["M"]=M; + value["MM"]=LZ(M); + value["MMM"]=MONTH_NAMES[M-1]; + value["NNN"]=MONTH_NAMES[M+11]; + value["d"]=d; + value["dd"]=LZ(d); + value["E"]=DAY_NAMES[E+7]; + value["EE"]=DAY_NAMES[E]; + value["H"]=H; + value["HH"]=LZ(H); + if (H==0){value["h"]=12;} + else if (H>12){value["h"]=H-12;} + else {value["h"]=H;} + value["hh"]=LZ(value["h"]); + if (H>11){value["K"]=H-12;} else {value["K"]=H;} + value["k"]=H+1; + value["KK"]=LZ(value["K"]); + value["kk"]=LZ(value["k"]); + if (H > 11) { value["a"]="PM"; } + else { value["a"]="AM"; } + value["m"]=m; + value["mm"]=LZ(m); + value["s"]=s; + value["ss"]=LZ(s); + while (i_format < format.length) { + c=format.charAt(i_format); + token=""; + while ((format.charAt(i_format)==c) && (i_format < format.length)) { + token += format.charAt(i_format++); + } + if (value[token] != null) { result=result + value[token]; } + else { result=result + token; } + } + return result; + } + +// ------------------------------------------------------------------ +// Utility functions for parsing in getDateFromFormat() +// ------------------------------------------------------------------ +function _isInteger(val) { + var digits="1234567890"; + for (var i=0; i < val.length; i++) { + if (digits.indexOf(val.charAt(i))==-1) { return false; } + } + return true; + } +function _getInt(str,i,minlength,maxlength) { + for (var x=maxlength; x>=minlength; x--) { + var token=str.substring(i,i+x); + if (token.length < minlength) { return null; } + if (_isInteger(token)) { return token; } + } + return null; + } + +// ------------------------------------------------------------------ +// getDateFromFormat( date_string , format_string ) +// +// This function takes a date string and a format string. It matches +// If the date string matches the format string, it returns the +// getTime() of the date. If it does not match, it returns 0. +// ------------------------------------------------------------------ +function getDateFromFormat(val,format) { + val=val+""; + format=format+""; + var i_val=0; + var i_format=0; + var c=""; + var token=""; + var token2=""; + var x,y; + var now=new Date(); + var year=now.getYear(); + var month=now.getMonth()+1; + var date=1; + var hh=now.getHours(); + var mm=now.getMinutes(); + var ss=now.getSeconds(); + var ampm=""; + + while (i_format < format.length) { + // Get next token from format string + c=format.charAt(i_format); + token=""; + while ((format.charAt(i_format)==c) && (i_format < format.length)) { + token += format.charAt(i_format++); + } + // Extract contents of value based on format token + if (token=="yyyy" || token=="yy" || token=="y") { + if (token=="yyyy") { x=4;y=4; } + if (token=="yy") { x=2;y=2; } + if (token=="y") { x=2;y=4; } + year=_getInt(val,i_val,x,y); + if (year==null) { return 0; } + i_val += year.length; + if (year.length==2) { + if (year > 70) { year=1900+(year-0); } + else { year=2000+(year-0); } + } + } + else if (token=="MMM"||token=="NNN"){ + month=0; + for (var i=0; i11)) { + month=i+1; + if (month>12) { month -= 12; } + i_val += month_name.length; + break; + } + } + } + if ((month < 1)||(month>12)){return 0;} + } + else if (token=="EE"||token=="E"){ + for (var i=0; i12)){return 0;} + i_val+=month.length;} + else if (token=="dd"||token=="d") { + date=_getInt(val,i_val,token.length,2); + if(date==null||(date<1)||(date>31)){return 0;} + i_val+=date.length;} + else if (token=="hh"||token=="h") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<1)||(hh>12)){return 0;} + i_val+=hh.length;} + else if (token=="HH"||token=="H") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<0)||(hh>23)){return 0;} + i_val+=hh.length;} + else if (token=="KK"||token=="K") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<0)||(hh>11)){return 0;} + i_val+=hh.length;} + else if (token=="kk"||token=="k") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<1)||(hh>24)){return 0;} + i_val+=hh.length;hh--;} + else if (token=="mm"||token=="m") { + mm=_getInt(val,i_val,token.length,2); + if(mm==null||(mm<0)||(mm>59)){return 0;} + i_val+=mm.length;} + else if (token=="ss"||token=="s") { + ss=_getInt(val,i_val,token.length,2); + if(ss==null||(ss<0)||(ss>59)){return 0;} + i_val+=ss.length;} + else if (token=="a") { + if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";} + else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";} + else {return 0;} + i_val+=2;} + else { + if (val.substring(i_val,i_val+token.length)!=token) {return 0;} + else {i_val+=token.length;} + } + } + // If there are any trailing characters left in the value, it doesn't match + if (i_val != val.length) { return 0; } + // Is date valid for month? + if (month==2) { + // Check for leap year + if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year + if (date > 29){ return 0; } + } + else { if (date > 28) { return 0; } } + } + if ((month==4)||(month==6)||(month==9)||(month==11)) { + if (date > 30) { return 0; } + } + // Correct hours value + if (hh<12 && ampm=="PM") { hh=hh-0+12; } + else if (hh>11 && ampm=="AM") { hh-=12; } + var newdate=new Date(year,month-1,date,hh,mm,ss); + return newdate.getTime(); + } + +// ------------------------------------------------------------------ +// parseDate( date_string [, prefer_euro_format] ) +// +// This function takes a date string and tries to match it to a +// number of possible date formats to get the value. It will try to +// match against the following international formats, in this order: +// y-M-d MMM d, y MMM d,y y-MMM-d d-MMM-y MMM d +// M/d/y M-d-y M.d.y MMM-d M/d M-d +// d/M/y d-M-y d.M.y d-MMM d/M d-M +// A second argument may be passed to instruct the method to search +// for formats like d/M/y (european format) before M/d/y (American). +// Returns a Date object or null if no patterns match. +// ------------------------------------------------------------------ +function parseDate(val) { + var preferEuro=(arguments.length==2)?arguments[1]:false; + generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d'); + monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d'); + dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M'); + var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst'); + var d=null; + for (var i=0; i tags may cause errors. + +USAGE: +// Create an object for a WINDOW popup +var win = new PopupWindow(); + +// Create an object for a DIV window using the DIV named 'mydiv' +var win = new PopupWindow('mydiv'); + +// Set the window to automatically hide itself when the user clicks +// anywhere else on the page except the popup +win.autoHide(); + +// Show the window relative to the anchor name passed in +win.showPopup(anchorname); + +// Hide the popup +win.hidePopup(); + +// Set the size of the popup window (only applies to WINDOW popups +win.setSize(width,height); + +// Populate the contents of the popup window that will be shown. If you +// change the contents while it is displayed, you will need to refresh() +win.populate(string); + +// set the URL of the window, rather than populating its contents +// manually +win.setUrl("http://www.site.com/"); + +// Refresh the contents of the popup +win.refresh(); + +// Specify how many pixels to the right of the anchor the popup will appear +win.offsetX = 50; + +// Specify how many pixels below the anchor the popup will appear +win.offsetY = 100; + +NOTES: +1) Requires the functions in AnchorPosition.js + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. + +4) When a PopupWindow object is created, a handler for 'onmouseup' is + attached to any event handler you may have already defined. Do NOT define + an event handler for 'onmouseup' after you define a PopupWindow object or + the autoHide() will not work correctly. +*/ + +// Set the position of the popup window based on the anchor +function PopupWindow_getXYPosition(anchorname) { + var coordinates; + if (this.type == "WINDOW") { + coordinates = getAnchorWindowPosition(anchorname); + } + else { + coordinates = getAnchorPosition(anchorname); + } + this.x = coordinates.x; + this.y = coordinates.y; + } +// Set width/height of DIV/popup window +function PopupWindow_setSize(width,height) { + this.width = width; + this.height = height; + } +// Fill the window with contents +function PopupWindow_populate(contents) { + this.contents = contents; + this.populated = false; + } +// Set the URL to go to +function PopupWindow_setUrl(url) { + this.url = url; + } +// Set the window popup properties +function PopupWindow_setWindowProperties(props) { + this.windowProperties = props; + } +// Refresh the displayed contents of the popup +function PopupWindow_refresh() { + if (this.divName != null) { + // refresh the DIV object + if (this.use_gebi) { + document.getElementById(this.divName).innerHTML = this.contents; + } + else if (this.use_css) { + document.all[this.divName].innerHTML = this.contents; + } + else if (this.use_layers) { + var d = document.layers[this.divName]; + d.document.open(); + d.document.writeln(this.contents); + d.document.close(); + } + } + else { + if (this.popupWindow != null && !this.popupWindow.closed) { + if (this.url!="") { + this.popupWindow.location.href=this.url; + } + else { + this.popupWindow.document.open(); + this.popupWindow.document.writeln(this.contents); + this.popupWindow.document.close(); + } + this.popupWindow.focus(); + } + } + } +// Position and show the popup, relative to an anchor object +function PopupWindow_showPopup(anchorname) { + this.getXYPosition(anchorname); + this.x += this.offsetX; + this.y += this.offsetY; + if (!this.populated && (this.contents != "")) { + this.populated = true; + this.refresh(); + } + if (this.divName != null) { + // Show the DIV object + if (this.use_gebi) { + document.getElementById(this.divName).style.left = this.x + "px"; + document.getElementById(this.divName).style.top = this.y + "px"; + document.getElementById(this.divName).style.visibility = "visible"; + } + else if (this.use_css) { + document.all[this.divName].style.left = this.x; + document.all[this.divName].style.top = this.y; + document.all[this.divName].style.visibility = "visible"; + } + else if (this.use_layers) { + document.layers[this.divName].left = this.x; + document.layers[this.divName].top = this.y; + document.layers[this.divName].visibility = "visible"; + } + } + else { + if (this.popupWindow == null || this.popupWindow.closed) { + // If the popup window will go off-screen, move it so it doesn't + if (this.x<0) { this.x=0; } + if (this.y<0) { this.y=0; } + if (screen && screen.availHeight) { + if ((this.y + this.height) > screen.availHeight) { + this.y = screen.availHeight - this.height; + } + } + if (screen && screen.availWidth) { + if ((this.x + this.width) > screen.availWidth) { + this.x = screen.availWidth - this.width; + } + } + var avoidAboutBlank = window.opera || ( document.layers && !navigator.mimeTypes['*'] ) || navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ); + this.popupWindow = window.open(avoidAboutBlank?"":"about:blank","window_"+anchorname,this.windowProperties+",width="+this.width+",height="+this.height+",screenX="+this.x+",left="+this.x+",screenY="+this.y+",top="+this.y+""); + } + this.refresh(); + } + } +// Hide the popup +function PopupWindow_hidePopup() { + if (this.divName != null) { + if (this.use_gebi) { + document.getElementById(this.divName).style.visibility = "hidden"; + } + else if (this.use_css) { + document.all[this.divName].style.visibility = "hidden"; + } + else if (this.use_layers) { + document.layers[this.divName].visibility = "hidden"; + } + } + else { + if (this.popupWindow && !this.popupWindow.closed) { + this.popupWindow.close(); + this.popupWindow = null; + } + } + } +// Pass an event and return whether or not it was the popup DIV that was clicked +function PopupWindow_isClicked(e) { + if (this.divName != null) { + if (this.use_layers) { + var clickX = e.pageX; + var clickY = e.pageY; + var t = document.layers[this.divName]; + if ((clickX > t.left) && (clickX < t.left+t.clip.width) && (clickY > t.top) && (clickY < t.top+t.clip.height)) { + return true; + } + else { return false; } + } + else if (document.all) { // Need to hard-code this to trap IE for error-handling + var t = window.event.srcElement; + while (t.parentElement != null) { + if (t.id==this.divName) { + return true; + } + t = t.parentElement; + } + return false; + } + else if (this.use_gebi && e) { + var t = e.originalTarget; + while (t.parentNode != null) { + if (t.id==this.divName) { + return true; + } + t = t.parentNode; + } + return false; + } + return false; + } + return false; + } + +// Check an onMouseDown event to see if we should hide +function PopupWindow_hideIfNotClicked(e) { + if (this.autoHideEnabled && !this.isClicked(e)) { + this.hidePopup(); + } + } +// Call this to make the DIV disable automatically when mouse is clicked outside it +function PopupWindow_autoHide() { + this.autoHideEnabled = true; + } +// This global function checks all PopupWindow objects onmouseup to see if they should be hidden +function PopupWindow_hidePopupWindows(e) { + for (var i=0; i0) { + this.type="DIV"; + this.divName = arguments[0]; + } + else { + this.type="WINDOW"; + } + this.use_gebi = false; + this.use_css = false; + this.use_layers = false; + if (document.getElementById) { this.use_gebi = true; } + else if (document.all) { this.use_css = true; } + else if (document.layers) { this.use_layers = true; } + else { this.type = "WINDOW"; } + this.offsetX = 0; + this.offsetY = 0; + // Method mappings + this.getXYPosition = PopupWindow_getXYPosition; + this.populate = PopupWindow_populate; + this.setUrl = PopupWindow_setUrl; + this.setWindowProperties = PopupWindow_setWindowProperties; + this.refresh = PopupWindow_refresh; + this.showPopup = PopupWindow_showPopup; + this.hidePopup = PopupWindow_hidePopup; + this.setSize = PopupWindow_setSize; + this.isClicked = PopupWindow_isClicked; + this.autoHide = PopupWindow_autoHide; + this.hideIfNotClicked = PopupWindow_hideIfNotClicked; + } + +/* SOURCE FILE: CalendarPopup.js */ + +// HISTORY +// ------------------------------------------------------------------ +// Feb 7, 2005: Fixed a CSS styles to use px unit +// March 29, 2004: Added check in select() method for the form field +// being disabled. If it is, just return and don't do anything. +// March 24, 2004: Fixed bug - when month name and abbreviations were +// changed, date format still used original values. +// January 26, 2004: Added support for drop-down month and year +// navigation (Thanks to Chris Reid for the idea) +// September 22, 2003: Fixed a minor problem in YEAR calendar with +// CSS prefix. +// August 19, 2003: Renamed the function to get styles, and made it +// work correctly without an object reference +// August 18, 2003: Changed showYearNavigation and +// showYearNavigationInput to optionally take an argument of +// true or false +// July 31, 2003: Added text input option for year navigation. +// Added a per-calendar CSS prefix option to optionally use +// different styles for different calendars. +// July 29, 2003: Fixed bug causing the Today link to be clickable +// even though today falls in a disabled date range. +// Changed formatting to use pure CSS, allowing greater control +// over look-and-feel options. +// June 11, 2003: Fixed bug causing the Today link to be unselectable +// under certain cases when some days of week are disabled +// March 14, 2003: Added ability to disable individual dates or date +// ranges, display as light gray and strike-through +// March 14, 2003: Removed dependency on graypixel.gif and instead +/// use table border coloring +// March 12, 2003: Modified showCalendar() function to allow optional +// start-date parameter +// March 11, 2003: Modified select() function to allow optional +// start-date parameter +/* +DESCRIPTION: This object implements a popup calendar to allow the user to +select a date, month, quarter, or year. + +COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. +The calendar can be modified to work for any location in the world by +changing which weekday is displayed as the first column, changing the month +names, and changing the column headers for each day. + +USAGE: +// Create a new CalendarPopup object of type WINDOW +var cal = new CalendarPopup(); + +// Create a new CalendarPopup object of type DIV using the DIV named 'mydiv' +var cal = new CalendarPopup('mydiv'); + +// Easy method to link the popup calendar with an input box. +cal.select(inputObject, anchorname, dateFormat); +// Same method, but passing a default date other than the field's current value +cal.select(inputObject, anchorname, dateFormat, '01/02/2000'); +// This is an example call to the popup calendar from a link to populate an +// input box. Note that to use this, date.js must also be included!! +Select + +// Set the type of date select to be used. By default it is 'date'. +cal.setDisplayType(type); + +// When a date, month, quarter, or year is clicked, a function is called and +// passed the details. You must write this function, and tell the calendar +// popup what the function name is. +// Function to be called for 'date' select receives y, m, d +cal.setReturnFunction(functionname); +// Function to be called for 'month' select receives y, m +cal.setReturnMonthFunction(functionname); +// Function to be called for 'quarter' select receives y, q +cal.setReturnQuarterFunction(functionname); +// Function to be called for 'year' select receives y +cal.setReturnYearFunction(functionname); + +// Show the calendar relative to a given anchor +cal.showCalendar(anchorname); + +// Hide the calendar. The calendar is set to autoHide automatically +cal.hideCalendar(); + +// Set the month names to be used. Default are English month names +cal.setMonthNames("January","February","March",...); + +// Set the month abbreviations to be used. Default are English month abbreviations +cal.setMonthAbbreviations("Jan","Feb","Mar",...); + +// Show navigation for changing by the year, not just one month at a time +cal.showYearNavigation(); + +// Show month and year dropdowns, for quicker selection of month of dates +cal.showNavigationDropdowns(); + +// Set the text to be used above each day column. The days start with +// sunday regardless of the value of WeekStartDay +cal.setDayHeaders("S","M","T",...); + +// Set the day for the first column in the calendar grid. By default this +// is Sunday (0) but it may be changed to fit the conventions of other +// countries. +cal.setWeekStartDay(1); // week is Monday - Sunday + +// Set the weekdays which should be disabled in the 'date' select popup. You can +// then allow someone to only select week end dates, or Tuedays, for example +cal.setDisabledWeekDays(0,1); // To disable selecting the 1st or 2nd days of the week + +// Selectively disable individual days or date ranges. Disabled days will not +// be clickable, and show as strike-through text on current browsers. +// Date format is any format recognized by parseDate() in date.js +// Pass a single date to disable: +cal.addDisabledDates("2003-01-01"); +// Pass null as the first parameter to mean "anything up to and including" the +// passed date: +cal.addDisabledDates(null, "01/02/03"); +// Pass null as the second parameter to mean "including the passed date and +// anything after it: +cal.addDisabledDates("Jan 01, 2003", null); +// Pass two dates to disable all dates inbetween and including the two +cal.addDisabledDates("January 01, 2003", "Dec 31, 2003"); + +// When the 'year' select is displayed, set the number of years back from the +// current year to start listing years. Default is 2. +// This is also used for year drop-down, to decide how many years +/- to display +cal.setYearSelectStartOffset(2); + +// Text for the word "Today" appearing on the calendar +cal.setTodayText("Today"); + +// The calendar uses CSS classes for formatting. If you want your calendar to +// have unique styles, you can set the prefix that will be added to all the +// classes in the output. +// For example, normal output may have this: +// Today +// But if you set the prefix like this: +cal.setCssPrefix("Test"); +// The output will then look like: +// Today +// And you can define that style somewhere in your page. + +// When using Year navigation, you can make the year be an input box, so +// the user can manually change it and jump to any year +cal.showYearNavigationInput(); + +// Set the calendar offset to be different than the default. By default it +// will appear just below and to the right of the anchorname. So if you have +// a text box where the date will go and and anchor immediately after the +// text box, the calendar will display immediately under the text box. +cal.offsetX = 20; +cal.offsetY = 20; + +NOTES: +1) Requires the functions in AnchorPosition.js and PopupWindow.js + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. + +4) When a CalendarPopup object is created, a handler for 'onmouseup' is + attached to any event handler you may have already defined. Do NOT define + an event handler for 'onmouseup' after you define a CalendarPopup object + or the autoHide() will not work correctly. + +5) The calendar popup display uses style sheets to make it look nice. + +*/ + +// CONSTRUCTOR for the CalendarPopup Object +function CalendarPopup() { + var c; + if (arguments.length>0) { + c = new PopupWindow(arguments[0]); + } + else { + c = new PopupWindow(); + c.setSize(150,175); + } + c.offsetX = -152; + c.offsetY = 25; + c.autoHide(); + // Calendar-specific properties + c.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December"); + c.monthAbbreviations = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"); + c.dayHeaders = new Array("S","M","T","W","T","F","S"); + c.returnFunction = "CP_tmpReturnFunction"; + c.returnMonthFunction = "CP_tmpReturnMonthFunction"; + c.returnQuarterFunction = "CP_tmpReturnQuarterFunction"; + c.returnYearFunction = "CP_tmpReturnYearFunction"; + c.weekStartDay = 0; + c.isShowYearNavigation = false; + c.displayType = "date"; + c.disabledWeekDays = new Object(); + c.disabledDatesExpression = ""; + c.yearSelectStartOffset = 2; + c.currentDate = null; + c.todayText="Today"; + c.cssPrefix=""; + c.isShowNavigationDropdowns=false; + c.isShowYearNavigationInput=false; + window.CP_calendarObject = null; + window.CP_targetInput = null; + window.CP_dateFormat = "MM/dd/yyyy"; + // Method mappings + c.copyMonthNamesToWindow = CP_copyMonthNamesToWindow; + c.setReturnFunction = CP_setReturnFunction; + c.setReturnMonthFunction = CP_setReturnMonthFunction; + c.setReturnQuarterFunction = CP_setReturnQuarterFunction; + c.setReturnYearFunction = CP_setReturnYearFunction; + c.setMonthNames = CP_setMonthNames; + c.setMonthAbbreviations = CP_setMonthAbbreviations; + c.setDayHeaders = CP_setDayHeaders; + c.setWeekStartDay = CP_setWeekStartDay; + c.setDisplayType = CP_setDisplayType; + c.setDisabledWeekDays = CP_setDisabledWeekDays; + c.addDisabledDates = CP_addDisabledDates; + c.setYearSelectStartOffset = CP_setYearSelectStartOffset; + c.setTodayText = CP_setTodayText; + c.showYearNavigation = CP_showYearNavigation; + c.showCalendar = CP_showCalendar; + c.hideCalendar = CP_hideCalendar; + c.getStyles = getCalendarStyles; + c.refreshCalendar = CP_refreshCalendar; + c.getCalendar = CP_getCalendar; + c.select = CP_select; + c.setCssPrefix = CP_setCssPrefix; + c.showNavigationDropdowns = CP_showNavigationDropdowns; + c.showYearNavigationInput = CP_showYearNavigationInput; + c.copyMonthNamesToWindow(); + // Return the object + return c; + } +function CP_copyMonthNamesToWindow() { + // Copy these values over to the date.js + if (typeof(window.MONTH_NAMES)!="undefined" && window.MONTH_NAMES!=null) { + window.MONTH_NAMES = new Array(); + for (var i=0; i\n"; + result += '
    \n'; + } + else { + result += '
    \n'; + result += '
    \n'; + result += '
    \n'; + } + // Code for DATE display (default) + // ------------------------------- + if (this.displayType=="date" || this.displayType=="week-end") { + if (this.currentDate==null) { this.currentDate = now; } + if (arguments.length > 0) { var month = arguments[0]; } + else { var month = this.currentDate.getMonth()+1; } + if (arguments.length > 1 && arguments[1]>0 && arguments[1]-0==arguments[1]) { var year = arguments[1]; } + else { var year = this.currentDate.getFullYear(); } + var daysinmonth= new Array(0,31,28,31,30,31,30,31,31,30,31,30,31); + if ( ( (year%4 == 0)&&(year%100 != 0) ) || (year%400 == 0) ) { + daysinmonth[2] = 29; + } + var current_month = new Date(year,month-1,1); + var display_year = year; + var display_month = month; + var display_date = 1; + var weekday= current_month.getDay(); + var offset = 0; + + offset = (weekday >= this.weekStartDay) ? weekday-this.weekStartDay : 7-this.weekStartDay+weekday ; + if (offset > 0) { + display_month--; + if (display_month < 1) { display_month = 12; display_year--; } + display_date = daysinmonth[display_month]-offset+1; + } + var next_month = month+1; + var next_month_year = year; + if (next_month > 12) { next_month=1; next_month_year++; } + var last_month = month-1; + var last_month_year = year; + if (last_month < 1) { last_month=12; last_month_year--; } + var date_class; + if (this.type!="WINDOW") { + result += ""; + } + result += '\n'; + var refresh = windowref+'CP_refreshCalendar'; + var refreshLink = 'javascript:' + refresh; + if (this.isShowNavigationDropdowns) { + result += ''; + result += ''; + + result += ''; + } + else { + if (this.isShowYearNavigation) { + result += ''; + result += ''; + result += ''; + result += ''; + + result += ''; + if (this.isShowYearNavigationInput) { + result += ''; + } + else { + result += ''; + } + result += ''; + } + else { + result += '\n'; + result += '\n'; + result += '\n'; + } + } + result += '
     <'+this.monthNames[month-1]+'> <'+year+'><<'+this.monthNames[month-1]+' '+year+'>>
    \n'; + result += '\n'; + result += '\n'; + for (var j=0; j<7; j++) { + + result += '\n'; + } + result += '\n'; + for (var row=1; row<=6; row++) { + result += '\n'; + for (var col=1; col<=7; col++) { + var disabled=false; + if (this.disabledDatesExpression!="") { + var ds=""+display_year+LZ(display_month)+LZ(display_date); + eval("disabled=("+this.disabledDatesExpression+")"); + } + var dateClass = ""; + if ((display_month == this.currentDate.getMonth()+1) && (display_date==this.currentDate.getDate()) && (display_year==this.currentDate.getFullYear())) { + dateClass = "cpCurrentDate"; + } + else if (display_month == month) { + dateClass = "cpCurrentMonthDate"; + } + else { + dateClass = "cpOtherMonthDate"; + } + if (disabled || this.disabledWeekDays[col-1]) { + result += ' \n'; + } + else { + var selected_date = display_date; + var selected_month = display_month; + var selected_year = display_year; + if (this.displayType=="week-end") { + var d = new Date(selected_year,selected_month-1,selected_date,0,0,0,0); + d.setDate(d.getDate() + (7-col)); + selected_year = d.getYear(); + if (selected_year < 1000) { selected_year += 1900; } + selected_month = d.getMonth()+1; + selected_date = d.getDate(); + } + result += ' \n'; + } + display_date++; + if (display_date > daysinmonth[display_month]) { + display_date=1; + display_month++; + } + if (display_month > 12) { + display_month=1; + display_year++; + } + } + result += ''; + } + var current_weekday = now.getDay() - this.weekStartDay; + if (current_weekday < 0) { + current_weekday += 7; + } + result += '\n'; + result += '
    '+this.dayHeaders[(this.weekStartDay+j)%7]+'
    '+display_date+''+display_date+'
    \n'; + if (this.disabledDatesExpression!="") { + var ds=""+now.getFullYear()+LZ(now.getMonth()+1)+LZ(now.getDate()); + eval("disabled=("+this.disabledDatesExpression+")"); + } + if (disabled || this.disabledWeekDays[current_weekday+1]) { + result += ' '+this.todayText+'\n'; + } + else { + result += ' '+this.todayText+'\n'; + } + result += '
    \n'; + result += '
    \n'; + } + + // Code common for MONTH, QUARTER, YEAR + // ------------------------------------ + if (this.displayType=="month" || this.displayType=="quarter" || this.displayType=="year") { + if (arguments.length > 0) { var year = arguments[0]; } + else { + if (this.displayType=="year") { var year = now.getFullYear()-this.yearSelectStartOffset; } + else { var year = now.getFullYear(); } + } + if (this.displayType!="year" && this.isShowYearNavigation) { + result += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += ' \n'; + result += '
    <<'+year+'>>
    \n'; + } + } + + // Code for MONTH display + // ---------------------- + if (this.displayType=="month") { + // If POPUP, write entire HTML document + result += '\n'; + for (var i=0; i<4; i++) { + result += ''; + for (var j=0; j<3; j++) { + var monthindex = ((i*3)+j); + result += ''; + } + result += ''; + } + result += '
    '+this.monthAbbreviations[monthindex]+'
    \n'; + } + + // Code for QUARTER display + // ------------------------ + if (this.displayType=="quarter") { + result += '
    \n'; + for (var i=0; i<2; i++) { + result += ''; + for (var j=0; j<2; j++) { + var quarter = ((i*2)+j+1); + result += ''; + } + result += ''; + } + result += '

    Q'+quarter+'

    \n'; + } + + // Code for YEAR display + // --------------------- + if (this.displayType=="year") { + var yearColumnSize = 4; + result += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += '
    <<>>
    \n'; + result += '\n'; + for (var i=0; i'+currentyear+''; + } + result += ''; + } + result += '
    \n'; + } + // Common + if (this.type == "WINDOW") { + result += "\n"; + } + return result; + } diff --git a/spyce-2.1/verchk.py b/spyce-2.1/verchk.py new file mode 100755 index 0000000000000000000000000000000000000000..d9ed675c78c4cd5c6de3cffc37b85d20b1e2a7df --- /dev/null +++ b/spyce-2.1/verchk.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +################################################## +# SPYCE - Python-based HTML Scripting +# Copyright (c) 2002 Rimon Barr. +# +# Refer to spyce.py +# CVS: $Id: verchk.py 941 2006-07-22 14:03:55Z ellisj $ +################################################## + +__doc__ = '''Version checking script.''' + +import sys, os + +REQUIRED = '2.3' + +def checkversion(required): + if int(sys.version[0]) +// WWW: http://www.mattkruse.com/ +// +// NOTICE: You may use this code for any purpose, commercial or +// private, without any further permission from the author. You may +// remove this notice from your final code if you wish, however it is +// appreciated by the author if at least my web site address is kept. +// +// You may *NOT* re-distribute this code in any way except through its +// use. That means, you can include it in your product, or your web +// site, or any other form where the code is actually being used. You +// may not put the plain javascript up on your site for download or +// include it in your javascript libraries for download. +// If you wish to share this code with others, please just point them +// to the URL instead. +// Please DO NOT link directly to my .js files from your site. Copy +// the files to your server and use them there. Thank you. +// +// (Redistribution in Spyce is with permission.) +// =================================================================== + + +/* SOURCE FILE: AnchorPosition.js */ + +/* +AnchorPosition.js +Author: Matt Kruse +Last modified: 5/24/04 + +DESCRIPTION: These functions find the position of an tag in a document, +so other elements can be positioned relative to it. + +COMPATABILITY: Netscape 4.x,6.x,Mozilla, IE 5.x,6.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. + +FUNCTIONS: +getAnchorPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor. Position is relative to the PAGE. + +getAnchorWindowPosition(anchorname) + Returns an Object() having .x and .y properties of the pixel coordinates + of the upper-left corner of the anchor, relative to the WHOLE SCREEN. + +NOTES: + +1) For popping up separate browser windows, use getAnchorWindowPosition. + Otherwise, use getAnchorPosition + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. +*/ + +// getAnchorPosition(anchorname) +// This function returns an object having .x and .y properties which are the coordinates +// of the named anchor, relative to the page. +function getAnchorPosition(anchorname) { + // This function will return an Object with x and y properties + var useWindow=false; + var coordinates=new Object(); + var x=0,y=0; + // Browser capability sniffing + var use_gebi=false, use_css=false, use_layers=false; + if (document.getElementById) { use_gebi=true; } + else if (document.all) { use_css=true; } + else if (document.layers) { use_layers=true; } + // Logic to find position + if (use_gebi && document.all) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_gebi) { + var o=document.getElementById(anchorname); + x=AnchorPosition_getPageOffsetLeft(o); + y=AnchorPosition_getPageOffsetTop(o); + } + else if (use_css) { + x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]); + y=AnchorPosition_getPageOffsetTop(document.all[anchorname]); + } + else if (use_layers) { + var found=0; + for (var i=0; i9?"":"0")+x} + +// ------------------------------------------------------------------ +// isDate ( date_string, format_string ) +// Returns true if date string matches format of format string and +// is a valid date. Else returns false. +// It is recommended that you trim whitespace around the value before +// passing it to this function, as whitespace is NOT ignored! +// ------------------------------------------------------------------ +function isDate(val,format) { + var date=getDateFromFormat(val,format); + if (date==0) { return false; } + return true; + } + +// ------------------------------------------------------------------- +// compareDates(date1,date1format,date2,date2format) +// Compare two date strings to see which is greater. +// Returns: +// 1 if date1 is greater than date2 +// 0 if date2 is greater than date1 of if they are the same +// -1 if either of the dates is in an invalid format +// ------------------------------------------------------------------- +function compareDates(date1,dateformat1,date2,dateformat2) { + var d1=getDateFromFormat(date1,dateformat1); + var d2=getDateFromFormat(date2,dateformat2); + if (d1==0 || d2==0) { + return -1; + } + else if (d1 > d2) { + return 1; + } + return 0; + } + +// ------------------------------------------------------------------ +// formatDate (date_object, format) +// Returns a date in the output format specified. +// The format string uses the same abbreviations as in getDateFromFormat() +// ------------------------------------------------------------------ +function formatDate(date,format) { + format=format+""; + var result=""; + var i_format=0; + var c=""; + var token=""; + var y=date.getYear()+""; + var M=date.getMonth()+1; + var d=date.getDate(); + var E=date.getDay(); + var H=date.getHours(); + var m=date.getMinutes(); + var s=date.getSeconds(); + var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k; + // Convert real date parts into formatted versions + var value=new Object(); + if (y.length < 4) {y=""+(y-0+1900);} + value["y"]=""+y; + value["yyyy"]=y; + value["yy"]=y.substring(2,4); + value["M"]=M; + value["MM"]=LZ(M); + value["MMM"]=MONTH_NAMES[M-1]; + value["NNN"]=MONTH_NAMES[M+11]; + value["d"]=d; + value["dd"]=LZ(d); + value["E"]=DAY_NAMES[E+7]; + value["EE"]=DAY_NAMES[E]; + value["H"]=H; + value["HH"]=LZ(H); + if (H==0){value["h"]=12;} + else if (H>12){value["h"]=H-12;} + else {value["h"]=H;} + value["hh"]=LZ(value["h"]); + if (H>11){value["K"]=H-12;} else {value["K"]=H;} + value["k"]=H+1; + value["KK"]=LZ(value["K"]); + value["kk"]=LZ(value["k"]); + if (H > 11) { value["a"]="PM"; } + else { value["a"]="AM"; } + value["m"]=m; + value["mm"]=LZ(m); + value["s"]=s; + value["ss"]=LZ(s); + while (i_format < format.length) { + c=format.charAt(i_format); + token=""; + while ((format.charAt(i_format)==c) && (i_format < format.length)) { + token += format.charAt(i_format++); + } + if (value[token] != null) { result=result + value[token]; } + else { result=result + token; } + } + return result; + } + +// ------------------------------------------------------------------ +// Utility functions for parsing in getDateFromFormat() +// ------------------------------------------------------------------ +function _isInteger(val) { + var digits="1234567890"; + for (var i=0; i < val.length; i++) { + if (digits.indexOf(val.charAt(i))==-1) { return false; } + } + return true; + } +function _getInt(str,i,minlength,maxlength) { + for (var x=maxlength; x>=minlength; x--) { + var token=str.substring(i,i+x); + if (token.length < minlength) { return null; } + if (_isInteger(token)) { return token; } + } + return null; + } + +// ------------------------------------------------------------------ +// getDateFromFormat( date_string , format_string ) +// +// This function takes a date string and a format string. It matches +// If the date string matches the format string, it returns the +// getTime() of the date. If it does not match, it returns 0. +// ------------------------------------------------------------------ +function getDateFromFormat(val,format) { + val=val+""; + format=format+""; + var i_val=0; + var i_format=0; + var c=""; + var token=""; + var token2=""; + var x,y; + var now=new Date(); + var year=now.getYear(); + var month=now.getMonth()+1; + var date=1; + var hh=now.getHours(); + var mm=now.getMinutes(); + var ss=now.getSeconds(); + var ampm=""; + + while (i_format < format.length) { + // Get next token from format string + c=format.charAt(i_format); + token=""; + while ((format.charAt(i_format)==c) && (i_format < format.length)) { + token += format.charAt(i_format++); + } + // Extract contents of value based on format token + if (token=="yyyy" || token=="yy" || token=="y") { + if (token=="yyyy") { x=4;y=4; } + if (token=="yy") { x=2;y=2; } + if (token=="y") { x=2;y=4; } + year=_getInt(val,i_val,x,y); + if (year==null) { return 0; } + i_val += year.length; + if (year.length==2) { + if (year > 70) { year=1900+(year-0); } + else { year=2000+(year-0); } + } + } + else if (token=="MMM"||token=="NNN"){ + month=0; + for (var i=0; i11)) { + month=i+1; + if (month>12) { month -= 12; } + i_val += month_name.length; + break; + } + } + } + if ((month < 1)||(month>12)){return 0;} + } + else if (token=="EE"||token=="E"){ + for (var i=0; i12)){return 0;} + i_val+=month.length;} + else if (token=="dd"||token=="d") { + date=_getInt(val,i_val,token.length,2); + if(date==null||(date<1)||(date>31)){return 0;} + i_val+=date.length;} + else if (token=="hh"||token=="h") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<1)||(hh>12)){return 0;} + i_val+=hh.length;} + else if (token=="HH"||token=="H") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<0)||(hh>23)){return 0;} + i_val+=hh.length;} + else if (token=="KK"||token=="K") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<0)||(hh>11)){return 0;} + i_val+=hh.length;} + else if (token=="kk"||token=="k") { + hh=_getInt(val,i_val,token.length,2); + if(hh==null||(hh<1)||(hh>24)){return 0;} + i_val+=hh.length;hh--;} + else if (token=="mm"||token=="m") { + mm=_getInt(val,i_val,token.length,2); + if(mm==null||(mm<0)||(mm>59)){return 0;} + i_val+=mm.length;} + else if (token=="ss"||token=="s") { + ss=_getInt(val,i_val,token.length,2); + if(ss==null||(ss<0)||(ss>59)){return 0;} + i_val+=ss.length;} + else if (token=="a") { + if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";} + else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";} + else {return 0;} + i_val+=2;} + else { + if (val.substring(i_val,i_val+token.length)!=token) {return 0;} + else {i_val+=token.length;} + } + } + // If there are any trailing characters left in the value, it doesn't match + if (i_val != val.length) { return 0; } + // Is date valid for month? + if (month==2) { + // Check for leap year + if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year + if (date > 29){ return 0; } + } + else { if (date > 28) { return 0; } } + } + if ((month==4)||(month==6)||(month==9)||(month==11)) { + if (date > 30) { return 0; } + } + // Correct hours value + if (hh<12 && ampm=="PM") { hh=hh-0+12; } + else if (hh>11 && ampm=="AM") { hh-=12; } + var newdate=new Date(year,month-1,date,hh,mm,ss); + return newdate.getTime(); + } + +// ------------------------------------------------------------------ +// parseDate( date_string [, prefer_euro_format] ) +// +// This function takes a date string and tries to match it to a +// number of possible date formats to get the value. It will try to +// match against the following international formats, in this order: +// y-M-d MMM d, y MMM d,y y-MMM-d d-MMM-y MMM d +// M/d/y M-d-y M.d.y MMM-d M/d M-d +// d/M/y d-M-y d.M.y d-MMM d/M d-M +// A second argument may be passed to instruct the method to search +// for formats like d/M/y (european format) before M/d/y (American). +// Returns a Date object or null if no patterns match. +// ------------------------------------------------------------------ +function parseDate(val) { + var preferEuro=(arguments.length==2)?arguments[1]:false; + generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d'); + monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d'); + dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M'); + var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst'); + var d=null; + for (var i=0; i tags may cause errors. + +USAGE: +// Create an object for a WINDOW popup +var win = new PopupWindow(); + +// Create an object for a DIV window using the DIV named 'mydiv' +var win = new PopupWindow('mydiv'); + +// Set the window to automatically hide itself when the user clicks +// anywhere else on the page except the popup +win.autoHide(); + +// Show the window relative to the anchor name passed in +win.showPopup(anchorname); + +// Hide the popup +win.hidePopup(); + +// Set the size of the popup window (only applies to WINDOW popups +win.setSize(width,height); + +// Populate the contents of the popup window that will be shown. If you +// change the contents while it is displayed, you will need to refresh() +win.populate(string); + +// set the URL of the window, rather than populating its contents +// manually +win.setUrl("http://www.site.com/"); + +// Refresh the contents of the popup +win.refresh(); + +// Specify how many pixels to the right of the anchor the popup will appear +win.offsetX = 50; + +// Specify how many pixels below the anchor the popup will appear +win.offsetY = 100; + +NOTES: +1) Requires the functions in AnchorPosition.js + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. + +4) When a PopupWindow object is created, a handler for 'onmouseup' is + attached to any event handler you may have already defined. Do NOT define + an event handler for 'onmouseup' after you define a PopupWindow object or + the autoHide() will not work correctly. +*/ + +// Set the position of the popup window based on the anchor +function PopupWindow_getXYPosition(anchorname) { + var coordinates; + if (this.type == "WINDOW") { + coordinates = getAnchorWindowPosition(anchorname); + } + else { + coordinates = getAnchorPosition(anchorname); + } + this.x = coordinates.x; + this.y = coordinates.y; + } +// Set width/height of DIV/popup window +function PopupWindow_setSize(width,height) { + this.width = width; + this.height = height; + } +// Fill the window with contents +function PopupWindow_populate(contents) { + this.contents = contents; + this.populated = false; + } +// Set the URL to go to +function PopupWindow_setUrl(url) { + this.url = url; + } +// Set the window popup properties +function PopupWindow_setWindowProperties(props) { + this.windowProperties = props; + } +// Refresh the displayed contents of the popup +function PopupWindow_refresh() { + if (this.divName != null) { + // refresh the DIV object + if (this.use_gebi) { + document.getElementById(this.divName).innerHTML = this.contents; + } + else if (this.use_css) { + document.all[this.divName].innerHTML = this.contents; + } + else if (this.use_layers) { + var d = document.layers[this.divName]; + d.document.open(); + d.document.writeln(this.contents); + d.document.close(); + } + } + else { + if (this.popupWindow != null && !this.popupWindow.closed) { + if (this.url!="") { + this.popupWindow.location.href=this.url; + } + else { + this.popupWindow.document.open(); + this.popupWindow.document.writeln(this.contents); + this.popupWindow.document.close(); + } + this.popupWindow.focus(); + } + } + } +// Position and show the popup, relative to an anchor object +function PopupWindow_showPopup(anchorname) { + this.getXYPosition(anchorname); + this.x += this.offsetX; + this.y += this.offsetY; + if (!this.populated && (this.contents != "")) { + this.populated = true; + this.refresh(); + } + if (this.divName != null) { + // Show the DIV object + if (this.use_gebi) { + document.getElementById(this.divName).style.left = this.x + "px"; + document.getElementById(this.divName).style.top = this.y + "px"; + document.getElementById(this.divName).style.visibility = "visible"; + } + else if (this.use_css) { + document.all[this.divName].style.left = this.x; + document.all[this.divName].style.top = this.y; + document.all[this.divName].style.visibility = "visible"; + } + else if (this.use_layers) { + document.layers[this.divName].left = this.x; + document.layers[this.divName].top = this.y; + document.layers[this.divName].visibility = "visible"; + } + } + else { + if (this.popupWindow == null || this.popupWindow.closed) { + // If the popup window will go off-screen, move it so it doesn't + if (this.x<0) { this.x=0; } + if (this.y<0) { this.y=0; } + if (screen && screen.availHeight) { + if ((this.y + this.height) > screen.availHeight) { + this.y = screen.availHeight - this.height; + } + } + if (screen && screen.availWidth) { + if ((this.x + this.width) > screen.availWidth) { + this.x = screen.availWidth - this.width; + } + } + var avoidAboutBlank = window.opera || ( document.layers && !navigator.mimeTypes['*'] ) || navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ); + this.popupWindow = window.open(avoidAboutBlank?"":"about:blank","window_"+anchorname,this.windowProperties+",width="+this.width+",height="+this.height+",screenX="+this.x+",left="+this.x+",screenY="+this.y+",top="+this.y+""); + } + this.refresh(); + } + } +// Hide the popup +function PopupWindow_hidePopup() { + if (this.divName != null) { + if (this.use_gebi) { + document.getElementById(this.divName).style.visibility = "hidden"; + } + else if (this.use_css) { + document.all[this.divName].style.visibility = "hidden"; + } + else if (this.use_layers) { + document.layers[this.divName].visibility = "hidden"; + } + } + else { + if (this.popupWindow && !this.popupWindow.closed) { + this.popupWindow.close(); + this.popupWindow = null; + } + } + } +// Pass an event and return whether or not it was the popup DIV that was clicked +function PopupWindow_isClicked(e) { + if (this.divName != null) { + if (this.use_layers) { + var clickX = e.pageX; + var clickY = e.pageY; + var t = document.layers[this.divName]; + if ((clickX > t.left) && (clickX < t.left+t.clip.width) && (clickY > t.top) && (clickY < t.top+t.clip.height)) { + return true; + } + else { return false; } + } + else if (document.all) { // Need to hard-code this to trap IE for error-handling + var t = window.event.srcElement; + while (t.parentElement != null) { + if (t.id==this.divName) { + return true; + } + t = t.parentElement; + } + return false; + } + else if (this.use_gebi && e) { + var t = e.originalTarget; + while (t.parentNode != null) { + if (t.id==this.divName) { + return true; + } + t = t.parentNode; + } + return false; + } + return false; + } + return false; + } + +// Check an onMouseDown event to see if we should hide +function PopupWindow_hideIfNotClicked(e) { + if (this.autoHideEnabled && !this.isClicked(e)) { + this.hidePopup(); + } + } +// Call this to make the DIV disable automatically when mouse is clicked outside it +function PopupWindow_autoHide() { + this.autoHideEnabled = true; + } +// This global function checks all PopupWindow objects onmouseup to see if they should be hidden +function PopupWindow_hidePopupWindows(e) { + for (var i=0; i0) { + this.type="DIV"; + this.divName = arguments[0]; + } + else { + this.type="WINDOW"; + } + this.use_gebi = false; + this.use_css = false; + this.use_layers = false; + if (document.getElementById) { this.use_gebi = true; } + else if (document.all) { this.use_css = true; } + else if (document.layers) { this.use_layers = true; } + else { this.type = "WINDOW"; } + this.offsetX = 0; + this.offsetY = 0; + // Method mappings + this.getXYPosition = PopupWindow_getXYPosition; + this.populate = PopupWindow_populate; + this.setUrl = PopupWindow_setUrl; + this.setWindowProperties = PopupWindow_setWindowProperties; + this.refresh = PopupWindow_refresh; + this.showPopup = PopupWindow_showPopup; + this.hidePopup = PopupWindow_hidePopup; + this.setSize = PopupWindow_setSize; + this.isClicked = PopupWindow_isClicked; + this.autoHide = PopupWindow_autoHide; + this.hideIfNotClicked = PopupWindow_hideIfNotClicked; + } + +/* SOURCE FILE: CalendarPopup.js */ + +// HISTORY +// ------------------------------------------------------------------ +// Feb 7, 2005: Fixed a CSS styles to use px unit +// March 29, 2004: Added check in select() method for the form field +// being disabled. If it is, just return and don't do anything. +// March 24, 2004: Fixed bug - when month name and abbreviations were +// changed, date format still used original values. +// January 26, 2004: Added support for drop-down month and year +// navigation (Thanks to Chris Reid for the idea) +// September 22, 2003: Fixed a minor problem in YEAR calendar with +// CSS prefix. +// August 19, 2003: Renamed the function to get styles, and made it +// work correctly without an object reference +// August 18, 2003: Changed showYearNavigation and +// showYearNavigationInput to optionally take an argument of +// true or false +// July 31, 2003: Added text input option for year navigation. +// Added a per-calendar CSS prefix option to optionally use +// different styles for different calendars. +// July 29, 2003: Fixed bug causing the Today link to be clickable +// even though today falls in a disabled date range. +// Changed formatting to use pure CSS, allowing greater control +// over look-and-feel options. +// June 11, 2003: Fixed bug causing the Today link to be unselectable +// under certain cases when some days of week are disabled +// March 14, 2003: Added ability to disable individual dates or date +// ranges, display as light gray and strike-through +// March 14, 2003: Removed dependency on graypixel.gif and instead +/// use table border coloring +// March 12, 2003: Modified showCalendar() function to allow optional +// start-date parameter +// March 11, 2003: Modified select() function to allow optional +// start-date parameter +/* +DESCRIPTION: This object implements a popup calendar to allow the user to +select a date, month, quarter, or year. + +COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small +positioning errors - usually with Window positioning - occur on the +Macintosh platform. +The calendar can be modified to work for any location in the world by +changing which weekday is displayed as the first column, changing the month +names, and changing the column headers for each day. + +USAGE: +// Create a new CalendarPopup object of type WINDOW +var cal = new CalendarPopup(); + +// Create a new CalendarPopup object of type DIV using the DIV named 'mydiv' +var cal = new CalendarPopup('mydiv'); + +// Easy method to link the popup calendar with an input box. +cal.select(inputObject, anchorname, dateFormat); +// Same method, but passing a default date other than the field's current value +cal.select(inputObject, anchorname, dateFormat, '01/02/2000'); +// This is an example call to the popup calendar from a link to populate an +// input box. Note that to use this, date.js must also be included!! +Select + +// Set the type of date select to be used. By default it is 'date'. +cal.setDisplayType(type); + +// When a date, month, quarter, or year is clicked, a function is called and +// passed the details. You must write this function, and tell the calendar +// popup what the function name is. +// Function to be called for 'date' select receives y, m, d +cal.setReturnFunction(functionname); +// Function to be called for 'month' select receives y, m +cal.setReturnMonthFunction(functionname); +// Function to be called for 'quarter' select receives y, q +cal.setReturnQuarterFunction(functionname); +// Function to be called for 'year' select receives y +cal.setReturnYearFunction(functionname); + +// Show the calendar relative to a given anchor +cal.showCalendar(anchorname); + +// Hide the calendar. The calendar is set to autoHide automatically +cal.hideCalendar(); + +// Set the month names to be used. Default are English month names +cal.setMonthNames("January","February","March",...); + +// Set the month abbreviations to be used. Default are English month abbreviations +cal.setMonthAbbreviations("Jan","Feb","Mar",...); + +// Show navigation for changing by the year, not just one month at a time +cal.showYearNavigation(); + +// Show month and year dropdowns, for quicker selection of month of dates +cal.showNavigationDropdowns(); + +// Set the text to be used above each day column. The days start with +// sunday regardless of the value of WeekStartDay +cal.setDayHeaders("S","M","T",...); + +// Set the day for the first column in the calendar grid. By default this +// is Sunday (0) but it may be changed to fit the conventions of other +// countries. +cal.setWeekStartDay(1); // week is Monday - Sunday + +// Set the weekdays which should be disabled in the 'date' select popup. You can +// then allow someone to only select week end dates, or Tuedays, for example +cal.setDisabledWeekDays(0,1); // To disable selecting the 1st or 2nd days of the week + +// Selectively disable individual days or date ranges. Disabled days will not +// be clickable, and show as strike-through text on current browsers. +// Date format is any format recognized by parseDate() in date.js +// Pass a single date to disable: +cal.addDisabledDates("2003-01-01"); +// Pass null as the first parameter to mean "anything up to and including" the +// passed date: +cal.addDisabledDates(null, "01/02/03"); +// Pass null as the second parameter to mean "including the passed date and +// anything after it: +cal.addDisabledDates("Jan 01, 2003", null); +// Pass two dates to disable all dates inbetween and including the two +cal.addDisabledDates("January 01, 2003", "Dec 31, 2003"); + +// When the 'year' select is displayed, set the number of years back from the +// current year to start listing years. Default is 2. +// This is also used for year drop-down, to decide how many years +/- to display +cal.setYearSelectStartOffset(2); + +// Text for the word "Today" appearing on the calendar +cal.setTodayText("Today"); + +// The calendar uses CSS classes for formatting. If you want your calendar to +// have unique styles, you can set the prefix that will be added to all the +// classes in the output. +// For example, normal output may have this: +// Today +// But if you set the prefix like this: +cal.setCssPrefix("Test"); +// The output will then look like: +// Today +// And you can define that style somewhere in your page. + +// When using Year navigation, you can make the year be an input box, so +// the user can manually change it and jump to any year +cal.showYearNavigationInput(); + +// Set the calendar offset to be different than the default. By default it +// will appear just below and to the right of the anchorname. So if you have +// a text box where the date will go and and anchor immediately after the +// text box, the calendar will display immediately under the text box. +cal.offsetX = 20; +cal.offsetY = 20; + +NOTES: +1) Requires the functions in AnchorPosition.js and PopupWindow.js + +2) Your anchor tag MUST contain both NAME and ID attributes which are the + same. For example: + + +3) There must be at least a space between for IE5.5 to see the + anchor tag correctly. Do not do with no space. + +4) When a CalendarPopup object is created, a handler for 'onmouseup' is + attached to any event handler you may have already defined. Do NOT define + an event handler for 'onmouseup' after you define a CalendarPopup object + or the autoHide() will not work correctly. + +5) The calendar popup display uses style sheets to make it look nice. + +*/ + +// CONSTRUCTOR for the CalendarPopup Object +function CalendarPopup() { + var c; + if (arguments.length>0) { + c = new PopupWindow(arguments[0]); + } + else { + c = new PopupWindow(); + c.setSize(150,175); + } + c.offsetX = -152; + c.offsetY = 25; + c.autoHide(); + // Calendar-specific properties + c.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December"); + c.monthAbbreviations = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"); + c.dayHeaders = new Array("S","M","T","W","T","F","S"); + c.returnFunction = "CP_tmpReturnFunction"; + c.returnMonthFunction = "CP_tmpReturnMonthFunction"; + c.returnQuarterFunction = "CP_tmpReturnQuarterFunction"; + c.returnYearFunction = "CP_tmpReturnYearFunction"; + c.weekStartDay = 0; + c.isShowYearNavigation = false; + c.displayType = "date"; + c.disabledWeekDays = new Object(); + c.disabledDatesExpression = ""; + c.yearSelectStartOffset = 2; + c.currentDate = null; + c.todayText="Today"; + c.cssPrefix=""; + c.isShowNavigationDropdowns=false; + c.isShowYearNavigationInput=false; + window.CP_calendarObject = null; + window.CP_targetInput = null; + window.CP_dateFormat = "MM/dd/yyyy"; + // Method mappings + c.copyMonthNamesToWindow = CP_copyMonthNamesToWindow; + c.setReturnFunction = CP_setReturnFunction; + c.setReturnMonthFunction = CP_setReturnMonthFunction; + c.setReturnQuarterFunction = CP_setReturnQuarterFunction; + c.setReturnYearFunction = CP_setReturnYearFunction; + c.setMonthNames = CP_setMonthNames; + c.setMonthAbbreviations = CP_setMonthAbbreviations; + c.setDayHeaders = CP_setDayHeaders; + c.setWeekStartDay = CP_setWeekStartDay; + c.setDisplayType = CP_setDisplayType; + c.setDisabledWeekDays = CP_setDisabledWeekDays; + c.addDisabledDates = CP_addDisabledDates; + c.setYearSelectStartOffset = CP_setYearSelectStartOffset; + c.setTodayText = CP_setTodayText; + c.showYearNavigation = CP_showYearNavigation; + c.showCalendar = CP_showCalendar; + c.hideCalendar = CP_hideCalendar; + c.getStyles = getCalendarStyles; + c.refreshCalendar = CP_refreshCalendar; + c.getCalendar = CP_getCalendar; + c.select = CP_select; + c.setCssPrefix = CP_setCssPrefix; + c.showNavigationDropdowns = CP_showNavigationDropdowns; + c.showYearNavigationInput = CP_showYearNavigationInput; + c.copyMonthNamesToWindow(); + // Return the object + return c; + } +function CP_copyMonthNamesToWindow() { + // Copy these values over to the date.js + if (typeof(window.MONTH_NAMES)!="undefined" && window.MONTH_NAMES!=null) { + window.MONTH_NAMES = new Array(); + for (var i=0; i\n"; + result += '
    \n'; + } + else { + result += '
    \n'; + result += '
    \n'; + result += '
    \n'; + } + // Code for DATE display (default) + // ------------------------------- + if (this.displayType=="date" || this.displayType=="week-end") { + if (this.currentDate==null) { this.currentDate = now; } + if (arguments.length > 0) { var month = arguments[0]; } + else { var month = this.currentDate.getMonth()+1; } + if (arguments.length > 1 && arguments[1]>0 && arguments[1]-0==arguments[1]) { var year = arguments[1]; } + else { var year = this.currentDate.getFullYear(); } + var daysinmonth= new Array(0,31,28,31,30,31,30,31,31,30,31,30,31); + if ( ( (year%4 == 0)&&(year%100 != 0) ) || (year%400 == 0) ) { + daysinmonth[2] = 29; + } + var current_month = new Date(year,month-1,1); + var display_year = year; + var display_month = month; + var display_date = 1; + var weekday= current_month.getDay(); + var offset = 0; + + offset = (weekday >= this.weekStartDay) ? weekday-this.weekStartDay : 7-this.weekStartDay+weekday ; + if (offset > 0) { + display_month--; + if (display_month < 1) { display_month = 12; display_year--; } + display_date = daysinmonth[display_month]-offset+1; + } + var next_month = month+1; + var next_month_year = year; + if (next_month > 12) { next_month=1; next_month_year++; } + var last_month = month-1; + var last_month_year = year; + if (last_month < 1) { last_month=12; last_month_year--; } + var date_class; + if (this.type!="WINDOW") { + result += ""; + } + result += '\n'; + var refresh = windowref+'CP_refreshCalendar'; + var refreshLink = 'javascript:' + refresh; + if (this.isShowNavigationDropdowns) { + result += ''; + result += ''; + + result += ''; + } + else { + if (this.isShowYearNavigation) { + result += ''; + result += ''; + result += ''; + result += ''; + + result += ''; + if (this.isShowYearNavigationInput) { + result += ''; + } + else { + result += ''; + } + result += ''; + } + else { + result += '\n'; + result += '\n'; + result += '\n'; + } + } + result += '
     <'+this.monthNames[month-1]+'> <'+year+'><<'+this.monthNames[month-1]+' '+year+'>>
    \n'; + result += '\n'; + result += '\n'; + for (var j=0; j<7; j++) { + + result += '\n'; + } + result += '\n'; + for (var row=1; row<=6; row++) { + result += '\n'; + for (var col=1; col<=7; col++) { + var disabled=false; + if (this.disabledDatesExpression!="") { + var ds=""+display_year+LZ(display_month)+LZ(display_date); + eval("disabled=("+this.disabledDatesExpression+")"); + } + var dateClass = ""; + if ((display_month == this.currentDate.getMonth()+1) && (display_date==this.currentDate.getDate()) && (display_year==this.currentDate.getFullYear())) { + dateClass = "cpCurrentDate"; + } + else if (display_month == month) { + dateClass = "cpCurrentMonthDate"; + } + else { + dateClass = "cpOtherMonthDate"; + } + if (disabled || this.disabledWeekDays[col-1]) { + result += ' \n'; + } + else { + var selected_date = display_date; + var selected_month = display_month; + var selected_year = display_year; + if (this.displayType=="week-end") { + var d = new Date(selected_year,selected_month-1,selected_date,0,0,0,0); + d.setDate(d.getDate() + (7-col)); + selected_year = d.getYear(); + if (selected_year < 1000) { selected_year += 1900; } + selected_month = d.getMonth()+1; + selected_date = d.getDate(); + } + result += ' \n'; + } + display_date++; + if (display_date > daysinmonth[display_month]) { + display_date=1; + display_month++; + } + if (display_month > 12) { + display_month=1; + display_year++; + } + } + result += ''; + } + var current_weekday = now.getDay() - this.weekStartDay; + if (current_weekday < 0) { + current_weekday += 7; + } + result += '\n'; + result += '
    '+this.dayHeaders[(this.weekStartDay+j)%7]+'
    '+display_date+''+display_date+'
    \n'; + if (this.disabledDatesExpression!="") { + var ds=""+now.getFullYear()+LZ(now.getMonth()+1)+LZ(now.getDate()); + eval("disabled=("+this.disabledDatesExpression+")"); + } + if (disabled || this.disabledWeekDays[current_weekday+1]) { + result += ' '+this.todayText+'\n'; + } + else { + result += ' '+this.todayText+'\n'; + } + result += '
    \n'; + result += '
    \n'; + } + + // Code common for MONTH, QUARTER, YEAR + // ------------------------------------ + if (this.displayType=="month" || this.displayType=="quarter" || this.displayType=="year") { + if (arguments.length > 0) { var year = arguments[0]; } + else { + if (this.displayType=="year") { var year = now.getFullYear()-this.yearSelectStartOffset; } + else { var year = now.getFullYear(); } + } + if (this.displayType!="year" && this.isShowYearNavigation) { + result += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += ' \n'; + result += '
    <<'+year+'>>
    \n'; + } + } + + // Code for MONTH display + // ---------------------- + if (this.displayType=="month") { + // If POPUP, write entire HTML document + result += '\n'; + for (var i=0; i<4; i++) { + result += ''; + for (var j=0; j<3; j++) { + var monthindex = ((i*3)+j); + result += ''; + } + result += ''; + } + result += '
    '+this.monthAbbreviations[monthindex]+'
    \n'; + } + + // Code for QUARTER display + // ------------------------ + if (this.displayType=="quarter") { + result += '
    \n'; + for (var i=0; i<2; i++) { + result += ''; + for (var j=0; j<2; j++) { + var quarter = ((i*2)+j+1); + result += ''; + } + result += ''; + } + result += '

    Q'+quarter+'

    \n'; + } + + // Code for YEAR display + // --------------------- + if (this.displayType=="year") { + var yearColumnSize = 4; + result += ""; + result += '\n'; + result += ' \n'; + result += ' \n'; + result += '
    <<>>
    \n'; + result += '\n'; + for (var i=0; i'+currentyear+''; + } + result += ''; + } + result += '
    \n'; + } + // Common + if (this.type == "WINDOW") { + result += "\n"; + } + return result; + } diff --git a/spyce-2.1/www/demo-site/demos/chat/chatbox.spi b/spyce-2.1/www/demo-site/demos/chat/chatbox.spi new file mode 100755 index 0000000000000000000000000000000000000000..765cfd5ad7714f45fe1a838e73b3db37757e03c9 --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/chat/chatbox.spi @@ -0,0 +1,38 @@ +[[.tagcollection ]] + +[[.begin name=boxlet singleton=True ]] +[[.attr name=width default=300 ]] +[[.attr name=lines default=5 ]] + +[[.import names="pool"]] [[-- loads Spyce pool module --]] + +[[-- + You can and often should put the handlers in a separate file, + with the class it associates with where applicable. Here + We just inline our handler; for an example of the other, + see the to-do demo. +--]] +[[! +def chatbox_addline(self, api, newline): + # (use setdefault() in case server restarted) + api.pool.setdefault('chatlines', []).append(newline) + api.pool['chatlines'] = api.pool['chatlines'][:100] # remembers max of 100 +]] + +
    + [[\ + i = -int(lines) + line = None # for first export + ]] + [[ for line in pool.setdefault('chatlines', [])[i:]:{ ]] +
    [[= line ]]
    + [[ } ]] +
    + + + +
    +
    + +[[.export var=line as=last ]] [[-- sends this variable to the calling scope --]] +[[.end]] diff --git a/spyce-2.1/www/demo-site/demos/chat/index.spy b/spyce-2.1/www/demo-site/demos/chat/index.spy new file mode 100755 index 0000000000000000000000000000000000000000..fdde76c140d261ea38699bf1c891f2f89cb0f1c7 --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/chat/index.spy @@ -0,0 +1,48 @@ +[[.taglib as='chat' from='chatbox.spi']] + +[[-- + You can and often should put the handlers in a separate file, + with the class it associates with where applicable. Here + We just inline our handler; for an example of the other, + see the to-do demo. +--]] +[[! + def __init__(self): + self.boxed = False + + def boxaction(self, api, one=True): + self.boxed = True +]] + +[[\ + import spyceUtil + # tell the parent template we want a link to the chatbox source + L = [('chatbox active tag', spyceUtil.url2file('chatbox.spi', request.filename()))] +]] + + + + + + + + + + + + +
    + First active tag instance: 3 lines max, declares handler outside of the tag. +
    Only this one will cause an extra handler to fire: + +

    + [[ if self.boxed:{ ]] + Extra handler fired + [[ } ]] +

    + 2nd boxlet instance. This one uses default settings (5 lines, no external handlers) + +
    + +The last line in chat is: [[=last ]] + diff --git a/spyce-2.1/www/demo-site/demos/to-do-durus/index.spy b/spyce-2.1/www/demo-site/demos/to-do-durus/index.spy new file mode 100755 index 0000000000000000000000000000000000000000..aec973d867c29c76b44df098896af782fc9a2d0e --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/to-do-durus/index.spy @@ -0,0 +1,37 @@ +[[ import urllib ]] +[[.import name=tododata as=data ]] + +[[-- Apply the parent template for title, header/footer. --]] + + +[[-- the spyce form library lets us specify python functions as handlers for submit tags --]] + + +

    To-do lists

    +[[\ +# turn the list of items into links to display in a UL +items = data.listroot().values() +def format(todo): + return '%s' % (todo.id(), todo.description) +items_for_list = [(format(todo), todo.id()) for todo in items] +]] +[[ if items:{ ]] + +[[ }else:{ ]] +(No lists created) +[[ } ]] + +

    Actions

    +
      + [[ if items:{ ]] +
    • + [[-- model module will be automatically imported by the form handler. + non-Spyce-specific parameters like onclick are output unmodified. --]] + + [[ } ]] +
    • + : + +
    + +
    diff --git a/spyce-2.1/www/demo-site/demos/to-do-durus/list-view.spy b/spyce-2.1/www/demo-site/demos/to-do-durus/list-view.spy new file mode 100755 index 0000000000000000000000000000000000000000..27539db7466467628ba82f8c3aaedbd3cabbee87 --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/to-do-durus/list-view.spy @@ -0,0 +1,48 @@ +[[.import name=tododata as=data ]] +[[\ + todo = data.get(request['todoid']) +]] + + + + + +

    Items in this list:

    +
      + [[ for item in todo.items:{ ]] +
    • [[ if item.done:{ ]][[ } ]] + [[= item.description ]] + [[ if item.done:{ ]] + [[ } else:{ ]] + + [[ } ]] + [[ } ]] + + [[ if 'item' not in locals():{ ]] +
    • (No items yet) + [[ } ]] +
    + +

    Item Actions

    +
      + [[ if [item for item in todo.items if not item.done]:{ ]] +
    • + [[ } ]] + +
    • : + +
    + +
    + +[[-- separate form for editing list so description textfields don't get confused --]] +

    List Actions

    + + + + + diff --git a/spyce-2.1/www/demo-site/demos/to-do-durus/model.py b/spyce-2.1/www/demo-site/demos/to-do-durus/model.py new file mode 100755 index 0000000000000000000000000000000000000000..a513924ca3b2ba34f4c077ae7c92ae1b3b492083 --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/to-do-durus/model.py @@ -0,0 +1,61 @@ +from durus.persistent_list import PersistentList +from spydurus import PersistentWithId +from spyceException import HandlerError + +# model +class TodoList(PersistentWithId): + def __init__(self, description): + self.items = PersistentList() + self.description = description + +class TodoItem(PersistentWithId): + def __init__(self, parent, description): + self.description = description + self.done = False + parent.items.append(self) + + +# handler actions for index +def list_new(api, description): + data = api.data + if description in data.listroot(): + raise HandlerError('New list', 'a list with that description already exists') + todo = TodoList(description) + data.listroot()[description] = todo + data.commit() + api.redirect.external('list-view.spy?todoid=%s' % todo.id()) + api.response.end() + +def list_delete(api, todoid): + data = api.data + for id in todoid: + todo = data.get(id) + del data.listroot()[todo.description] + data.commit() + + +# handler actions for list-view +def list_edit(api, todoid, description): + data = api.data + if description in data.listroot(): + raise HandlerError('Description', 'a list with that description already exists') + todo = data.get(todoid) + # we use the listroot() dict to prevent duplicate todo descriptions + # so we need to remove the old hash entry and add a new one + del data.listroot()[todo.description] + todo.description = description + data.listroot()[todo.description] = todo + data.commit() + +def item_new(api, todoid, description): + todo = api.data.get(todoid) + item = TodoItem(todo, description) + api.data.commit() + +def item_markdone(api, item_ids=None): + if not item_ids: + raise HandlerError('Items', 'None selected') + for id in item_ids: + item = api.data.get(id) + item.done = True + api.data.commit() diff --git a/spyce-2.1/www/demo-site/demos/to-do-durus/parent.spi b/spyce-2.1/www/demo-site/demos/to-do-durus/parent.spi new file mode 100755 index 0000000000000000000000000000000000000000..4063b97a5e18f4f589279537adfe2c07f3730712 --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/to-do-durus/parent.spi @@ -0,0 +1,14 @@ +[[-- This template is used just for the to-do demo. All it does + is hardcode some extra files into the 'extracode' list that + it then passes to the site template. --]] +[[\ + import spyceUtil + import os.path + L= [('model.py', spyceUtil.url2file('model.py', request.filename())), + ('tododata.py', spyceUtil.url2file('tododata.py', request.filename())), + ('to-do template', request.stack()[-1])] +]] + + + +[[= child._body ]] \ No newline at end of file diff --git a/spyce-2.1/www/demo-site/demos/to-do-durus/tododata.py b/spyce-2.1/www/demo-site/demos/to-do-durus/tododata.py new file mode 100755 index 0000000000000000000000000000000000000000..6ae197ce654fee2b198a9e78835768b447037e64 --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/to-do-durus/tododata.py @@ -0,0 +1,25 @@ +# this file extends contrib/spydurus.py, a connection-pooling Spyce module +# for Durus, with a convenience method specific to the to-do Durus database + +import os +import spydurus +from durus.persistent_dict import PersistentDict + +import spyce +durus_path = os.path.join(spyce.getServer().config.tmp, 'todo.durus') + +spydurus.maybeStartDurus(durus_path) +spydurus.initPool(db_path=durus_path) + +# initialize listroot if it's a brand-new database +conn = spydurus.q.get() +try: + conn.get_root()['lists'] +except KeyError: + conn.get_root()['lists'] = PersistentDict() + conn.commit() +spydurus.q.put(conn) + +class tododata(spydurus.spydurus): + def listroot(self): + return self.root()['lists'] diff --git a/spyce-2.1/www/demo-site/demos/to-do/actions.py b/spyce-2.1/www/demo-site/demos/to-do/actions.py new file mode 100755 index 0000000000000000000000000000000000000000..3359cd77d926c9183db178504f997bb35fc0a487 --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/to-do/actions.py @@ -0,0 +1,34 @@ +from spyceException import HandlerError + +# handler actions for index +def list_new(api, name): + if api.db.todo_lists.selectfirst_by(name=name): + raise HandlerError('New list', 'a list with that description already exists') + todo = api.db.todo_lists.insert(name=name) + api.db.flush() + api.redirect.external('list-view.spy?todoid=%s' % todo.list_id) + api.response.end() + +def list_delete(api, todoid): + lists = api.db.todo_lists + lists.delete(lists.c.id.in_(*todoid)) + + +# handler actions for list-view +def list_edit(api, todoid, name): + todo = api.db.todo_lists.selectfirst_by(name=name) + if todo and todo.list_id != todoid: + raise HandlerError('New list', 'a list with that description already exists') + todo = api.db.todo_lists.selectone_by(list_id=todoid) + todo.name = name + api.db.flush() + +def item_new(api, todoid, description): + todo = api.db.todo_items.insert(list_id=todoid, description=description) + api.db.flush() + +def item_markdone(api, item_ids=None): + if not item_ids: + raise HandlerError('Items', 'None selected') + items = api.db.todo_items + items.update(items.c.item_id.in_(*item_ids), done=1) diff --git a/spyce-2.1/www/demo-site/demos/to-do/index.spy b/spyce-2.1/www/demo-site/demos/to-do/index.spy new file mode 100755 index 0000000000000000000000000000000000000000..77a9dd72e4fe77b46d0e237ddd012b9f538566f4 --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/to-do/index.spy @@ -0,0 +1,34 @@ +[[-- Apply the parent template for title, header/footer. --]] + + +[[-- the Spyce form library lets us specify python functions as handlers for submit tags --]] + + +

    To-do lists

    +[[\ +# turn the list of items into links for display +lists = db.todo_lists.select(order_by=db.todo_lists.c.name) +def format(todo): + return '%s' % (todo.list_id, todo.name) +formatted_lists = [(format(todo), todo.list_id) for todo in lists] +]] +[[ if lists:{ ]] + +[[ }else:{ ]] +(No lists created) +[[ } ]] + +

    Actions

    +
      + [[ if lists:{ ]] +
    • + [[-- actions module will be automatically imported by the form handler. + non-Spyce-specific parameters like onclick are output unmodified. --]] + + [[ } ]] +
    • + : + +
    + +
    diff --git a/spyce-2.1/www/demo-site/demos/to-do/list-view.spy b/spyce-2.1/www/demo-site/demos/to-do/list-view.spy new file mode 100755 index 0000000000000000000000000000000000000000..fe32870bd7c8c2e1e64abba074bcd8ee2cbb5e87 --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/to-do/list-view.spy @@ -0,0 +1,47 @@ +[[\ +todo = db.todo_lists.get(request['todoid']) +items = db.todo_items.select(db.todo_items.c.list_id==todo.list_id, + order_by=[db.todo_items.c.done]) +]] + + + + + +

    Items in this list:

    +[[ if items:{ ]] + [[ for item in items:{ ]] +

    + [[ if item.done:{ ]] + [[= item.description ]] + [[ }else:{ ]] + + [[ } ]] +

    + [[ } ]] +[[ }else:{ ]] +(No items yet) +[[ } ]] + +

    Item Actions

    +
      + [[ if [item for item in items if not item.done]:{ ]] +
    • + [[ } ]] + +
    • : + +
    + +
    + +

    List Actions

    + + + + + diff --git a/spyce-2.1/www/demo-site/demos/to-do/parent.spi b/spyce-2.1/www/demo-site/demos/to-do/parent.spi new file mode 100755 index 0000000000000000000000000000000000000000..604e76bc30f5f244211dcd09546ac989af8fb7df --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/to-do/parent.spi @@ -0,0 +1,14 @@ +[[-- This template is used just for the to-do demo. All it does + is hardcode some extra files into the 'extracode' list that + it then passes to the site template. --]] +[[\ + import spyceUtil + import os.path + L= [('actions.py', spyceUtil.url2file('actions.py', request.filename())), + ('todo.sql', spyceUtil.url2file('todo.sql', request.filename())), + ('to-do template', request.stack()[-1])] +]] + + + +[[= child._body ]] \ No newline at end of file diff --git a/spyce-2.1/www/demo-site/demos/to-do/todo.db b/spyce-2.1/www/demo-site/demos/to-do/todo.db new file mode 100755 index 0000000000000000000000000000000000000000..8ae616465495d998c4e723be259107e90fc0e63d Binary files /dev/null and b/spyce-2.1/www/demo-site/demos/to-do/todo.db differ diff --git a/spyce-2.1/www/demo-site/demos/to-do/todo.sql b/spyce-2.1/www/demo-site/demos/to-do/todo.sql new file mode 100755 index 0000000000000000000000000000000000000000..079ff4e12474740a9ae4cfc8b3a0ea8daa3187dc --- /dev/null +++ b/spyce-2.1/www/demo-site/demos/to-do/todo.sql @@ -0,0 +1,11 @@ +CREATE TABLE todo_lists ( + list_id integer PRIMARY KEY, -- int PK automatically autonumbered by sqlite + name varchar(256) NOT NULL +); + +CREATE TABLE todo_items ( + item_id integer PRIMARY KEY, -- int PK automatically autonumbered by sqlite + list_id integer NOT NULL REFERENCES todo_lists(list_id), + description varchar(256) NOT NULL, + done integer NOT NULL DEFAULT 0 +); diff --git a/spyce-2.1/www/demo-site/docs/compress.src.html b/spyce-2.1/www/demo-site/docs/compress.src.html new file mode 100755 index 0000000000000000000000000000000000000000..76dbaf4fe0379662652bb8e6470695f77f59a387 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/compress.src.html @@ -0,0 +1,210 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Examples
    +
    + + + +
    +examples/compress.spy +
    + +
    [[.import name=compress args="gzip=1, spaces=1"]]
    +[[\
    +  response.write('<html><body>')
    +  response.write('  Space compression will remove these     spaces.<br>')
    +  response.write('  gzip compression will highly compress this:<br>')
    +  for i in range(1000):
    +    response.write('  hello')
    +  response.write('</body></html>')
    +]]
    +
    +
    +
    + + Run this code + +
    +

    Back to List of Examples + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/config.src.html b/spyce-2.1/www/demo-site/docs/config.src.html new file mode 100755 index 0000000000000000000000000000000000000000..f0870c53a381e78fe761d27c105e80bdb60ca855 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/config.src.html @@ -0,0 +1,207 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Examples
    +
    + + + +
    +examples/config.spy +
    + +
    [[\
    +  import spyceConfig
    +  home = spyceConfig.SPYCE_HOME
    +]]
    +
    +[[= home ]]
    +
    +
    +
    + + Run this code + +
    +

    Back to List of Examples + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/cookie.src.html b/spyce-2.1/www/demo-site/docs/cookie.src.html new file mode 100755 index 0000000000000000000000000000000000000000..4fdbd38538898b794222402b063879b1c1f13758 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/cookie.src.html @@ -0,0 +1,258 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Examples
    +
    + + + +
    +examples/cookie.spy +
    + +
    [[.import name=cookie]]
    +<html><body>
    +  Managing cookies is simple. Use the following forms 
    +  to create and destroy cookies. Remember to refresh 
    +  once, because the cookie will only be transmitted on 
    +  the <i>following</i> request.<br>
    +  [[-- input forms --]]
    +  <hr>
    +  <form action="[[=request.uri('path')]]" method=post>
    +    <table><tr>
    +      <td align=right>Cookie name:</td>
    +      <td><input type=text name=name></td>
    +      <td>(required)</td>
    +    </tr><tr>
    +      <td align=right>value:</td>
    +      <td><input type=text name=value></td>
    +      <td>(required for set)</td>
    +    </tr><tr>
    +      <td align=right>expiration:</td>
    +      <td><input type=text name=exp> seconds.</td>
    +      <td>(optional)</td>
    +    </tr><tr>
    +      <td colspan=3>
    +        <input type=submit name=operation value=set>
    +        <input type=submit name=operation value=delete>
    +        <input type=submit name=operation value=refresh>
    +      </td>
    +    </tr></table>
    +  </form>
    +  <hr>
    +  [[-- show cookies --]]
    +  Cookies: [[=len(cookie.get().keys())]]<br>
    +  <table>
    +    <tr>
    +      <td><b>name</b></td>
    +      <td><b>value</b></td>
    +    </tr>
    +    [[for c in cookie.get().keys(): {]]
    +      <tr>
    +        <td>[[=c]]</td>
    +        <td>[[=cookie.get(c)]]</td>
    +      </tr>
    +    [[ } ]]
    +  </table>
    +  [[-- set cookies --]]
    +  [[\
    +    operation = request.post('operation')
    +    if operation:
    +      operation = operation[0]
    +      name = request.post('name')[0]
    +      value = request.post('value')[0]
    +      if operation == 'set' and name and value:
    +        cookie.set(name, value)
    +      if operation == 'delete' and name:
    +        cookie.delete(name)
    +  ]]
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    Back to List of Examples + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/db.src.html b/spyce-2.1/www/demo-site/docs/db.src.html new file mode 100755 index 0000000000000000000000000000000000000000..6092ca342e94e412c44faa092bcd8d181462df7c --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/db.src.html @@ -0,0 +1,224 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Examples
    +
    + + + +
    +examples/db.spy +
    + +
    <spy:parent title="To-do demo" />
    +
    +[[!
    +def list_new(self, api, name):
    +    if api.db.todo_lists.selectfirst_by(name=name):
    +        raise HandlerError('New list', 'a list with that description already exists')
    +    api.db.todo_lists.insert(name=name)
    +    api.db.flush()
    +]]
    +
    +(This is an self-contained example using the same database as the
    +<a href=/demos/to-do/index.spy>to-do demo</a>.)
    +
    +<h2>To-do lists</h2>
    +
    +[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]]
    +<spy:ul data="[L.name for L in lists]" />
    +
    +<h2>New list</h2>
    +<f:form>
    +<f:submit value="New list" handler=self.list_new />:
    +<f:text name=name value="" />
    +</f:form>
    +
    +
    +
    + + Run this code + +
    +

    Back to List of Examples + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-add.html b/spyce-2.1/www/demo-site/docs/doc-add.html new file mode 100755 index 0000000000000000000000000000000000000000..7f9bfb0b4197923bacf9f79b92e91a8239714e49 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-add.html @@ -0,0 +1,213 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation
    +
    + + + + + + + +
    Prev: 3.11.1 - ExampleUp: Table of ContentsNext: 4.1 - Performance
    +
    +

    4. ADDENDA

    + + +List of appendices: +

      +
    • Performance - Some throughput +micro-benchmarks
    • +

    • History - The brief history +of Spyce
    • +

    + + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-add_history.html b/spyce-2.1/www/demo-site/docs/doc-add_history.html new file mode 100755 index 0000000000000000000000000000000000000000..d317a216ebf914216eee2c882b0c1659c63263fb --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-add_history.html @@ -0,0 +1,246 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - History
    +
    + + + + + + + +
    Prev: 4.1 - PerformanceUp: 4 - AddendaNext: None
    +
    +4.2. History

    + + +The initial idea for a Python-based HTML scripting language came in May 1999, +a few months after I (Rimon) had first learned of Python, while working with JSP on +some website. The idea was pretty basic and I felt that someone was bound to +implement it sooner or later, so I waited. But, nobody stepped up to the task, +and the idea remained little more than a design in my head for two and a half +years. In early 2002, after having successfully used Python extensively for +various other tasks and gaining experience with the language, I began to +revisit my thoughts on a Python-based HTML scripting language, and by late +May 2002 the beta of version 1.0 was released.

    +Version 1.0 had support for standard features like get and post, +cookies, session management, etc. Development was still on-going, but Spyce +was mature and being used on live systems. Support of various features was +enhanced for about a week or two, and then a new design idea popped into my +head. Version 1.1 was the first modular release of Spyce. Lots of +prior functionality was shipped out of the core engine and into standard +modules. Many, many new modules and features were added. Spyce popularity rose +to the top percentile of SourceForge projects and the user base grew. +Version 1.2 represented a greatly matured release of Spyce. Spyce +got a totally revamped website and documentation, and development continued. +Version 1.3 introduced active tags. More performance work, more +modules, etc. +

    +In February 2005 Jonathan Ellis was hired by SilentWhistle to work on their +web application, written in Spyce. He began by overhauling the Active Tag code +and was soon deep in the guts of Spyce. Jonathan added Active Handlers, +parent templates, and the tag compiler on the way to Version 2.0. +

    +In July 2006 Version 2.1 introduced the Spyce login tags and +database integration via SQLAlchemy, as well as improvments to parameter +marshalling for Active Handlers. +

    +For more detail, please refer to the +change log. +As always, user feedback is welcome and appreciated.

    +


    + + + + + +
    Prev: 4.1 - PerformanceUp: 4 - AddendaNext: None
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-add_perf.html b/spyce-2.1/www/demo-site/docs/doc-add_perf.html new file mode 100755 index 0000000000000000000000000000000000000000..27ca86984a111d0439d28448aa0ea222b14b7be2 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-add_perf.html @@ -0,0 +1,311 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Performance
    +
    + + + + + + + +
    Prev: 4 - AddendaUp: 4 - AddendaNext: 4.2 - History
    +
    +4.1. Performance

    + + +Although flexibility usually outweighs raw performance in the choice of +technology, it is nice to know that the technology that you have chosen is not +a resource hog, and can scale to large production sites. The current Spyce +implementation is comparable to its cousin technologies: JSP, PHP and ASP. We +ran a micro-benchmark using hello.spy and equivalents. All benchmark +files are available in the misc/benchmark +directory.

    + +
    + examples/hello.spy +
    + +
    <spy:parent title="Hello" />
    +[[ import spyce ]]
    +
    +Hello from Spyce version 
    +[[= spyce.__version__ ]]!
    +
    +
    +
    + + Run this code + +
    +

    +Spyce was measured under CGI, FCGI, mod_python and proxy configurations. For +calibration the static HTML, CGI-C, CGI-Python and FCGI-Python tests were +performed. In the case of CGI-C, the request is handled by a compiled C +program with the appropriate printf statements. In the case of CGI-Python, we +have an executable Python script with the appropriate print statements. +FCGI-Python is a similar script that is FCGI enabled. ASP was measured on a +different machine, only to satisfy curiosity; those results are omitted.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ConfigurationHello world!
    Spyce-modpython250
    modpython publisher300
    Spyce-proxy200
    JSP100
    PHP450
    Spyce-FCGI100
    Python-FCGI140
    Spyce-CGI8
    Python-CGI25
    C-CGI180
    Static HTML1500
    +

    +The throughput results (shown above in requests per second) were measured on a +Intel PIII 700MHz, with 128 MB of RAM and a 512 KB cache +running RedHat Linux 7.2 (2.4.7-10 kernel), Apache 1.3.22 and +Python 2.2 using loopback (http://localhost/...) requests. Since each of +the script languages requires an initial compilation phase (of which JSP seems +the longest), the server was warmed up with 100 requests before executing +1000 measured requests with a concurrency level of 3, using the +ab (Apache benchmark) tool. Figures are rounded to the nearest +25 requests/second.

    +Conclusion:All spyce configuration options except CGI +can handle large +websites, as the Spyce engine and cache persist between requests. The CGI +version takes a hit in recompiling Spyce files on every request. (This may be +alleviated using a disk-based Spyce cache (as opposed to the current +memory-based implementation).) +

    +


    + + + + + +
    Prev: 4 - AddendaUp: 4 - AddendaNext: 4.2 - History
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-conf.html b/spyce-2.1/www/demo-site/docs/doc-conf.html new file mode 100755 index 0000000000000000000000000000000000000000..90386e0b50b6932f4110dc0ec54368c64acb0de3 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-conf.html @@ -0,0 +1,231 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Install
    +
    + + + + + + + +
    Prev: 3.9.5 - Writing Tag Libraries the hard wayUp: 3 - RuntimeNext: 3.10.1 - Overview
    +
    +3.10. Installation

    + + +Spyce can be installed and used in many configurations. Hopefully, one of the +ones below will suit your needs. If not, feel free to email the lists asking +for assistance in using a different setup. If you have successfully set up +Spyce by some other method, please also email us the details. +Finally, if you had troubles following these instructions, +please send suggestions on how to improve them.

    + +


    + + + + + +
    Prev: 3.9.5 - Writing Tag Libraries the hard wayUp: 3 - RuntimeNext: 3.10.1 - Overview
    +

    +Sub-sections: +

    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-conf_apache.html b/spyce-2.1/www/demo-site/docs/doc-conf_apache.html new file mode 100755 index 0000000000000000000000000000000000000000..7efae4d5517918f7845099392fa8c2e1db331ab8 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-conf_apache.html @@ -0,0 +1,223 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Install
    +
    + + + + + + + +
    Prev: 3.10.6 - Notes on RPM installationUp: 3.10 - InstallationNext: 3.10.8 - Starting your first project
    +
    +3.10.7. Notes on Apache integration

    + + +

      +
    • If you installed via RPM, Apache is configured to proxy .spy requests to the Spyce webserver. If you are running without the spyce standalone server, comment out those lines in httpd.conf (starting with "Spyce via proxy"). +
    • +
    • To avoid confusion, you may wish to change the "root" option in your +spyceconf module +to be the same as Apache's DocumentRoot. +
    • You may wish to copy the "Documentation alias" section from spyceApache.conf. +This will allow you to access the spyce documentation and examples +from http://localhost/spyce/, so you can test your install by browsing to +http://localhost/spyce/examples/hello.spy. +
    +
    + + + + + +
    Prev: 3.10.6 - Notes on RPM installationUp: 3.10 - InstallationNext: 3.10.8 - Starting your first project
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-conf_modpython.html b/spyce-2.1/www/demo-site/docs/doc-conf_modpython.html new file mode 100755 index 0000000000000000000000000000000000000000..29b61ee45c7b7b5f247447aea3bd0196e2882bfe --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-conf_modpython.html @@ -0,0 +1,227 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Install
    +
    + + + + + + + +
    Prev: 3.10.3 - CGI/FastCGI installationUp: 3.10 - InstallationNext: 3.10.5 - Notes for Windows
    +
    +3.10.4. Mod_Python

    + + +mod_python is primarily recommended if you cannot get +FastCGI to work. (Some users have reported problems with FastCGI on Windows.) +

    +Before you try to install Spyce, first get mod_python to work! +You may have to compile from sources, and possibly do the same for Python. (See +the documentation at: mod_python.) +

    +To use Spyce via mod_python: +

      +
    • Copy the "Spyce via mod_python" lines from spyceApache.conf to your +/etc/httpd/conf/httpd.conf +file, and replace the XXX with the appropriate +path to your spyce installation. +
    • Restart Apache +
    +
    + + + + + +
    Prev: 3.10.3 - CGI/FastCGI installationUp: 3.10 - InstallationNext: 3.10.5 - Notes for Windows
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-conf_next.html b/spyce-2.1/www/demo-site/docs/doc-conf_next.html new file mode 100755 index 0000000000000000000000000000000000000000..a455d999e769804b2626ff2247038aeea30faedd --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-conf_next.html @@ -0,0 +1,247 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Install
    +
    + + + + + + + +
    Prev: 3.10.7 - Notes on Apache integrationUp: 3.10 - InstallationNext: 3.11 - Programmatic Interface
    +
    +3.10.8. Starting your first project

    + + +Spyce provides a simple method to create a new project. +This creates directories for your Spyce project, creates an initial +Spyce config file, +and sets up other resources. +

    +For example, running +

    python spyceProject.py /var/firstproject
    +results in the following directory structure + +
    +$ tree /var/firstproject/
    +    /var/firstproject/
    +    |-- config.py
    +    |-- lib
    +    |-- login_tokens
    +    `-- www
    +        |-- _util
    +        |   |-- form_calendar.gif
    +        |   `-- form_calendar.js
    +        |-- index.spy
    +        |-- parent.spi
    +        `-- style.css
    +
    + +Now you can simply run spyceCmd.py -l --conf /var/firstproject/config.py +and browse to http://localhost:8000/index.spy to verify that it worked. +

    +Notes: +

      +
    • The login_tokens directory is used by the spy:login tags; do not remove it. +
    • The lib/ directory (also initially empty) +is placed on the Python sys.path, meaning you can import any Python module in this +directory from any .spy file. It is good practice to put re-usable code +into .py files in this directory. +
    +
    + + + + + +
    Prev: 3.10.7 - Notes on Apache integrationUp: 3.10 - InstallationNext: 3.11 - Programmatic Interface
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-conf_overview.html b/spyce-2.1/www/demo-site/docs/doc-conf_overview.html new file mode 100755 index 0000000000000000000000000000000000000000..ec704ce8340c8686a9fe63d22f39dbd5a1c41ad6 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-conf_overview.html @@ -0,0 +1,257 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Install
    +
    + + + + + + + +
    Prev: 3.10 - InstallationUp: 3.10 - InstallationNext: 3.10.2 - Web Server
    +
    +3.10.1. Overview

    + + +Spyce (the core engine and all the standard modules) currently requires +Python version 2.3 or greater. Spyce uses no version-specific Apache features.

    +Spyce supports operation through +its own webserver, FastCGI, mod_python, Apache proxy, CGI, and the command-line. +Some of these require configuration-specific tweaks. These are kept to an +absolute minimum, however; where possible, the configuration of the +Spyce engine is performed through the +Spyce configuration module. +The supported adapters are: +

      +
    • Web server: The preferred alternative is to serve Spyce files via +its built-in webserver. For production use, it is recommended to do this +as a proxy behind another server such as Apache that handles static content, +url rewriting, ssl, etc. +This is the best option if it is available to you, since you only +have a single process (with concurrency provided by multithreading) which +makes resource pooling (data, database handles, etc.) an easy way to help +your site scale. It also avoids the waste of loading your code into +multiple Python interpreters. +
    • +

    • FastCGI: +This is a CGI-like interface that +is relatively fast, because it does not incur the large process startup +overhead on each request.
    • +

    • mod_python: This is another relatively performant +way to serve up Spyce. +If this is that is your chosen Apache integration route, +make sure you can first get mod_python running on your system, before +adding Spyce to the mix. +
    • +

    • CGI: Failing these alternatives, you can always process requests +via regular CGI, but this alternative is the slowest option and is intended +primarily for those who do not have much control over their web environments. +
    • +

    • Command line: Spyce is useful as a command-line tool +for pre-processing Spyce pages and creating static HTML files. +This documentation, for instance, is produced this way. +
    • +

    • Others: Spyce abstracts its operating environment using a thin +abstraction layer. Spyce users have written small Spyce adapters for the +Xitami webserver and also to integrate with the Coil framework. Writing your +own adapter, should the need arise, is therefore a realistic possibility. +
    • +

    +The following sections assume that you have already downloaded and uncompressed Spyce. +
    + + + + + +
    Prev: 3.10 - InstallationUp: 3.10 - InstallationNext: 3.10.2 - Web Server
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-conf_proxy.html b/spyce-2.1/www/demo-site/docs/doc-conf_proxy.html new file mode 100755 index 0000000000000000000000000000000000000000..03e2a5f47660a2568af83f69300e799489fcca17 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-conf_proxy.html @@ -0,0 +1,232 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Install
    +
    + + + + + + + +
    Prev: 3.10.1 - OverviewUp: 3.10 - InstallationNext: 3.10.3 - CGI/FastCGI installation
    +
    +3.10.2. Web Server

    + + +The preferred method of running Spyce it to run it as a web-server. +

    +The Spyce web server configuration is defined in the "webserver options" section +of the Spyce configuration module. +

      +
    • Start the Spyce web server. The command-line syntax for starting the +server is:
      spyceCmd.py -l +
      +Spyce will now be running on port 8000. You can change the port in your +spyceconf module. +
    • +
    • If you are going to proxy Spyce behind Apache (this is done automatically if you installed via RPM): +
        +
      1. Copy the proxy section ("Spyce via proxy") from spyceApache.conf into httpd.conf. +
      2. Make sure mod_proxy is installed and enabled. Check httpd.conf for a mod_proxy +section; look for instructions like "Uncomment the following lines to enable the proxy server." +
      3. Restart Apache
      4. +
      +
    • +
    +
    + + + + + +
    Prev: 3.10.1 - OverviewUp: 3.10 - InstallationNext: 3.10.3 - CGI/FastCGI installation
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-conf_rpm.html b/spyce-2.1/www/demo-site/docs/doc-conf_rpm.html new file mode 100755 index 0000000000000000000000000000000000000000..1883ea116f8c538784fb9fa64aba9972eb0a48d3 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-conf_rpm.html @@ -0,0 +1,229 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Install
    +
    + + + + + + + +
    Prev: 3.10.5 - Notes for WindowsUp: 3.10 - InstallationNext: 3.10.7 - Notes on Apache integration
    +
    +3.10.6. Notes on RPM installation

    + + +

      +
    • The RPM installation installs Spyce to /usr/share/spyce and +creates a /usr/bin/spyce as a symlink to spyceCmd.py. It also +configures Apache to forward .spy requests to the Spyce server on port 8000. +You're still responsible for starting the spyce server as described in the +Web Server configuration section. +
    • If you upgrading Spyce, it is recommended to uninstall the previous +version using rpm -e spyce, and then install a fresh copy, as opposed +to using the rpm -U option. +
    • Historical note to Redhat users: RH releases prior to 8.0 still used Python version +1.5, as many standard scripts depended on it. If you want to run Spyce with +some of the newer Python2 rpms, you will need to change top line of the run_spyceCmd.py, run_spyceCGI.py, run_spyceModpy.py and verchk.py +scripts, or reconfigure your path so that the default python is the version +that you want.

      +


      + + + + + +
      Prev: 3.10.5 - Notes for WindowsUp: 3.10 - InstallationNext: 3.10.7 - Notes on Apache integration
      + +

      +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-conf_source.html b/spyce-2.1/www/demo-site/docs/doc-conf_source.html new file mode 100755 index 0000000000000000000000000000000000000000..e019c738dd667236e8f2e975e99b2e6ad6f552d9 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-conf_source.html @@ -0,0 +1,257 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Install
    +
    + + + + + + + +
    Prev: 3.10.2 - Web ServerUp: 3.10 - InstallationNext: 3.10.4 - Mod_Python
    +
    +3.10.3. CGI/FastCGI installation

    + + +

      +
    • Ensure that you have Apache and FastCGI installed and functioning.
    • +
    • Create a symlink to the command-line executable:
      ln -sf /usr/share/spyce/run_spyceCmd.py /usr/bin/spyce +
      or wherever you have chosen to install it.
    • +
    • Copy the "Spyce via cgi or fcgi" lines from spyceApache.conf to your +/etc/httpd/conf/httpd.conf +file, and replace the XXX with the appropriate +path to your spyce installation. +
    • Restart Apache +
    +Alternative CGI configuration: +The alternative CGI configuration directs the webserver to execute the .spy +file itself, not the Spyce engine. This is appropriate if you do not control +Apache on the server you are installing to, e.g., low-end hosting +plans. Each .spy file should have execute +permissions for the web server, and the first line should be: +

    + + +

    +  #!/usr/bin/python /home/username/spyce/run_spyceCGI.py
    +
    + + +(Adjust to the correct Spyce installation path as necessary.) +Then add the following line to the httpd.conf, or to +the .htaccess file in the same directory. + + +
    +  AddHandler cgi-script spy
    +
    +
    + +Finally, ensure that the directory itself has the ExecCGI option enabled, +and set cgi_allow_only_redirect to +False in your Spyce configuration module. +For more details, please refer to the Apache documentation, specifically +ExecCGI option, +Directory, +Location, +AllowOverride and +Apache CGI documentation, +for more information on how to get a standard CGI setup working.

    +


    + + + + + +
    Prev: 3.10.2 - Web ServerUp: 3.10 - InstallationNext: 3.10.4 - Mod_Python
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-conf_windows.html b/spyce-2.1/www/demo-site/docs/doc-conf_windows.html new file mode 100755 index 0000000000000000000000000000000000000000..b42236cf18c9d2094226100cfd2a6f6bba3243ec --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-conf_windows.html @@ -0,0 +1,260 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Install
    +
    + + + + + + + +
    Prev: 3.10.4 - Mod_PythonUp: 3.10 - InstallationNext: 3.10.6 - Notes on RPM installation
    +
    +3.10.5. Notes for Windows

    + + +Besides your preferred installation option above, remember to +add the following line to Apache's httpd.conf:

    + + +

    +  ScriptInterpreterSource registry
    +
    + +

    +This assumes that Python has registered itself with the Windows registry to +run .py files. Otherwise, you can also omit this line, but make sure that the +first line of the run_spyceCGI.py file points to a +valid Python executable, as in: + + +

    +  #! c:/python24/python.exe
    +
    + +

    +If you are running using IIS on Windows, you can take a look at +how to +configure IIS or PWS for Python/CGI scripts.

    +The basics for getting IIS to work with Spyce are: +

      +
    • Start the IIS administration console. You get to it from the Control +Panel. Click on Administrative Tools, and then Internet +Services Manager.
    • +
    • Drill down to your Default Web Site. Right click and select +Properties.
    • +
    • Select the Home Directory tab, and click on the +Configuration... button near the bottom right.
    • +
    • Add an application mapping. On the executable line you should +type the equivalent of:
      "c:\program files\python22\python.exe" "c:\program files\spyce\spyceCGI.py". +
      Set the extension to .spy, or +whatever you like.
      Limit the Verbs to GET,POST.
      Select the Script engine and +Check that file exists check-boxes.
    • +
    • Click OK twice. Make sure to propagate these properties to all +sub-nodes. That is, click Select All and then OK once more. +
    • +
    • You should now be able to browse .spy files within your website. Note, +that this is a very slow mechanism, since it utilizes CGI and restarts the +Spyce engine on each request.
    • +
    • Using the Spyce proxy web server or installing FastCGI are much +advised for the vast majority of environments.
    • +
    +
    + + + + + +
    Prev: 3.10.4 - Mod_PythonUp: 3.10 - InstallationNext: 3.10.6 - Notes on RPM installation
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-intro.html b/spyce-2.1/www/demo-site/docs/doc-intro.html new file mode 100755 index 0000000000000000000000000000000000000000..6de7da5618003b57a689b2b941e864c5a372dafc --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-intro.html @@ -0,0 +1,245 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Introduction
    +
    + + + + + + + +
    Prev: Table of ContentsUp: Table of ContentsNext: 1.1 - Rationale / competitive analysis
    +
    +

    1. INTRODUCTION

    + + +This document aims to be the authoritative source of +information about Spyce, usable as a comprehensive refence, a user guide and a +tutorial. It should be at least skimmed from beginning to end, +so you have at least an idea of the functionality available and can refer +back for more details as needed.

    + +Spyce is a server-side language that supports elegant and +efficient Python-based dynamic HTML generation. +Spyce allows embedding Python in pages similar to how JSP embeds Java, +but Spyce is far more than a JSP clone. Out of the box, Spyce provides +development as rapid as other modern frameworks like Rails, but with an +cohesive design rather than a morass of special cases. + +

    +Spyce's modular design makes it very flexible +and extensible. It can also be used as a command-line utility for static text +pre-processing or as a web-server proxy. +

    +Spyce's performance is comparable to the +other solutions in its class.

    +Note: This manual assumes a knowledge of Python and focusses +exclusively on Spyce. If you do not already know Python, it is easy to +learn via this short tutorial, and has +extensive documentation.

    + +


    + + + + + +
    Prev: Table of ContentsUp: Table of ContentsNext: 1.1 - Rationale / competitive analysis
    +

    +Sub-sections: +

    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-intro_design.html b/spyce-2.1/www/demo-site/docs/doc-intro_design.html new file mode 100755 index 0000000000000000000000000000000000000000..2e9853a7ad7e3ae2e2ba67359858a0c77a70a5a4 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-intro_design.html @@ -0,0 +1,254 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Introduction
    +
    + + + + + + + +
    Prev: 1.1 - Rationale / competitive analysisUp: 1 - IntroductionNext: 2 - Language
    +
    +1.2. Design Goals

    + + +As a Spyce user, it helps to understand the broad design goals of this tool. +Spyce is designed to be: +

      +
    • Minimalist: The philosophy behind the design of Spyce is only +to include features that particularly enhance its functionality over the +wealth that is already available from within Python. One can readily import +and use Python modules for many functions, and there is no need to recode +large bodies of functionality.
    • +

    • Powerful: Spyce aims to free the programmer from as much +"plumbing"-style drudgery as possible through features such as +Active Handlers +and reusable Active Tags.
    • +

    • Modular: Spyce is built to be extended with +Spyce modules and +Active Tags +that provide additional functionality over the core engine +capabilities and standard Python modules. New features in the core engine +and language are rationalised against the option of creating a new module or +a new tag library. Standard Spyce modules and tag libraries are those that +are considered useful in a general setting and are included in the default +Spyce distribution. Users and third-parties are encouraged to develop their +own Spyce modules.
    • +

    • Intuitive: Obey user expectations. Part of this is avoiding +special cases.
    • +

    • Convenient: Using Spyce should be made as efficient as possible. +This, for example, is the reason behind the choice of [[ as delimeters over alternatives such as <? (php) and <% (jsp). +(However, ASP/JSP-style delimeters are also supported, so if you're +used to that style and like it, feel free to continue using it with Spyce.) +Functions and +modules are also designed with as many defaults as possible. +There are no XML configuration files in Spyce. +
    • +

    • Single-purpose: To be the best, most versatile, wildly-popular +Python-based dynamic HTML engine. Nothing more; nothing less.
    • +

    • Fast: Performance is +important. It is expected that Spyce will perform comparably with any other +dynamic, scripting solutions available. +
    • +

    +Now, let's start using Spyce...

    +


    + + + + + +
    Prev: 1.1 - Rationale / competitive analysisUp: 1 - IntroductionNext: 2 - Language
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-intro_rationale.html b/spyce-2.1/www/demo-site/docs/doc-intro_rationale.html new file mode 100755 index 0000000000000000000000000000000000000000..d738c93ae1d317d4443646ee336dbece87fad3f6 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-intro_rationale.html @@ -0,0 +1,322 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Introduction
    +
    + + + + + + + +
    Prev: 1 - IntroductionUp: 1 - IntroductionNext: 1.2 - Design Goals
    +
    +1.1. Rationale / competitive analysis

    + + +This section is somewhat dated. We plan to update it soon. +

    +A natural question to ask is why one would choose Spyce over JSP, ASP, PHP, +or any of the other HTML scripting languages that +perform a similar function. We compare Spyce with an array of exising tools: +

      +
    • Java Server Pages, JSP, is a widely popular, effective and +well-supported solution based on Java Servlet technology. Spyce differs from +JSP in that it embeds Python code among the HTML, thus providing a number of +advantages over Java. +
        +
      • Python is a high-level scripting language, +where rapid prototyping is syntactically easier to perform. +
      • There is no need for a separate "expression langauge" in Spyce; +Python is well-suited for both larger modules and active tag scripting. +
      • Python +is interpreted and latently typed, which can be advantageous for +prototyping, especially in avoiding unnecessary binary incompatibility of +classes for minor changes. +
      • Spyce code is of first-order in the Spyce +language, unlike JSP, which allows you to create useful Spyce lambda +functions. +
      • Creating new active tags and modules is simpler in +Spyce than in JSP. +
      • Spyce is better-integrated than JSP; to get similar functionality +in JSP, you have to add JSF (Java Server Faces) and Tiles, or +equivalents. +
      +

      +

    • +
    • PHP is another popular webserver module for dynamic +content generation. The PHP interpreter engine and the language itself were +explicitly designed for the task of dynamic HTML generation, while Python is +a general-purpose scripting language. +
        +
      • Spyce leverages from the extensive +development effort in Python: since any Python library can be imported and +reused, Spyce does not need to rebuild many of the core function libraries +that have been implemented by the PHP project. +
      • Use of Python +often simplifies integration of Spyce with existing system environments. +
      • Spyce code is also first-order in the Spyce language and Spyce supports +active tags. +
      • Spyce is modular in its design, allowing +users to easily extend its base functinality with add-on modules. +
      • The Spyce engine can be run from the command-line, which allows +Spyce to be used as an HTML preprocessor. +
      +Spyce, +like PHP, can run entirely within the process space of a webserver or via +CGI (as well as other web server adapters), and has been benchmarked to be +competitive in performance.
    • +

    • ASP.NET is a Microsoft technology +popular with Microsoft Internet Information Server (IIS) users. Visual +Basic .NET and C# are both popular implementation languages. +
        +
      • Spyce provides the power of the ASP.NET "component" development style +without trying to pretend that web applications live in a stateful, +event-driven environment. This is a leaky abstraction that causes +ASP.NET to have a steep learning curve while the user learns +where the rough edges are. +
      • ASP.NET is not well-supported outside the IIS environment. Spyce can +currently run as a standalone or proxy server, under mod_python (Apache), +or under CGI and FastCGI, which are +supported in the majority of web server environments. Adapters have also +been written for Xitami, Coil, Cheetah -- other web servers and frameworks. +
      • Spyce is open-source, and free. +
      +
    • +

      +

    • WebWare with Python Server Pages, PSP, is another +Python-based open-source development. PSP is similar in design to the Spyce +language, and shares many of the same benefits. Some important differences +include +
        +
      • Spyce supports both +Python chunks (indented Python) as well as PSP-style statements (braced +Python). +
      • Spyce supports active tags and component-based development +
      • Spyce code is first-order in the Spyce language +
      +PSP is also an integral part of +WebWare, an application-server framework similar to Tomcat Java-based +application server of the Apache Jakarta project. Spyce is to WebWare as JSP +is to Tomcat. Spyce is far simpler to install and run than WebWare (in the +author's humble opinion), and does not involve notions such as application +contexts. It aims to do only one thing well: provide a preprocessor and +runtime engine for the dynamic generation of HTML using embedded Python. +
    • +

    • Zope is an object-oriented open-source application server, +specializing in "content management, portals, and custom applications." Zope +is the most mature Python web application development environment, but +to a large degree suffers from +second-system syndrome. +In the author's opinion, Zope is to a large degree responsible for the +large number of python +web environments: a few years ago, it was de rigeur for talented programmers +to try Zope, realize it was a mess, and go off to write their own framework. +

      +Zope provides a scripting language called DHTML and can call +extensions written in Perl or Python. Spyce embeds Python directly in the +HTML, and only Python. It is an HTML-embedded language, not an application +server.

    • +

    +Spyce strikes a unique balance between power and simplicity. +Many users have said that this +is "exactly what they have been waiting for". Hopefully, this is the correct +point in the design space for your project as well. +

    +


    + + + + + +
    Prev: 1 - IntroductionUp: 1 - IntroductionNext: 1.2 - Design Goals
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-lang.html b/spyce-2.1/www/demo-site/docs/doc-lang.html new file mode 100755 index 0000000000000000000000000000000000000000..a91abda1aae3a2404cd555cc12f59d63b122e4e7 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-lang.html @@ -0,0 +1,265 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Language
    +
    + + + + + + + +
    Prev: 1.2 - Design GoalsUp: Table of ContentsNext: 2.1 - Plain HTML and Active Tags
    +
    +

    2. LANGUAGE

    + + +The basic structure of a Spyce script is an HTML file with embeddings. There +are six types of possible embeddings among the plain HTML text: +

      +
    • <taglib:name attr1=val1 ...> +
      Active tags may include presentation and action code. +
    • [[-- Spyce comment      --]] +
      Enclosed code is elided from the compiled Spyce class. +
    • [[\  Python chunk         ]] +
      Embed python in your code. +
    • [[!  Python class chunk  ]] +
      Like chunks, but at the class level rather than the spyceProcess method. +
    • [[   Python statement(s)  ]] +
      Like chunks, but may include braces to indicate that the block should continue after the ]]. +
    • [[=  Python expression    ]] +
      Output the result of evaluating the given expression. +
    • [[.  Spyce directive      ]] +
      Pass options to the Spyce compiler. +
    • [[spy  lambda             ]] +
      Allows dynamic compilation of Spyce code to a python function. +
    +Each Spyce tag type has a +unique beginning delimeter, namely [[, [[\, [[=, [[. or [[--. All +tags end with ]], except comment tags, which +end with --]]. +

    +Since +[[ and +]] +are special Spyce delimeters, one would escape them as +\[[ and +\]] +for use in HTML text. They can not be escaped within Python code, but the +string expressions +("["*2) and +("]"*2), or equivalent expressions, +can be used instead, or the brackets can be conveniently separated with a +space in the case of list or slicing expressions.

    + +


    + + + + + +
    Prev: 1.2 - Design GoalsUp: Table of ContentsNext: 2.1 - Plain HTML and Active Tags
    +

    +Sub-sections: +

    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-lang_asp.html b/spyce-2.1/www/demo-site/docs/doc-lang_asp.html new file mode 100755 index 0000000000000000000000000000000000000000..9cd9a0bb49dccf695c7a0299d5fb2b3ec887c706 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-lang_asp.html @@ -0,0 +1,227 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Language
    +
    + + + + + + + +
    Prev: 2.8 - Spyce LambdasUp: 2 - LanguageNext: 3 - Runtime
    +
    +2.9. ASP/JSP syntax

    + + +Finally, due to popular demand, because of current editor support and people +who actually enjoy pains in their wrists, the Spyce engine will respect +ASP/JSP-like delimeters. In other words, it will also recognize the following +syntax: +

    +The two sets of delimeters may be used interchangeably within the same file, +though for the sake of consistency this is not recommended. +
    + + + + + +
    Prev: 2.8 - Spyce LambdasUp: 2 - LanguageNext: 3 - Runtime
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-lang_chunk.html b/spyce-2.1/www/demo-site/docs/doc-lang_chunk.html new file mode 100755 index 0000000000000000000000000000000000000000..1558538faaca42a95735f7f578ddccd632d3a273 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-lang_chunk.html @@ -0,0 +1,248 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Language
    +
    + + + + + + + +
    Prev: 2.4 - Python StatementsUp: 2 - LanguageNext: 2.6 - Python Class Chunks
    +
    +2.5. Python Chunks

    + + +Syntax: [[\ Python chunk ]] +

    +There are many Python users that experience anguish, disgust or dismay upon +reading the previous section: "Braces!? Give me real, indented Python!". These +intendation zealots will be more comfortable using Python chunks, which is why +Spyce supports them. Feel free to use Spyce statements or chunks +inter-changeably, as the need arises.

    +A Python chunk is straight Python code, and the internal indentation is +preserved. The entire block is merely outdented (or indented) as a whole, such +that the first non-empty line of the block matches the indentation level of +the context into which the chunk was placed. Thus, a Python chunk can not +affect the indentation level outside its scope, but internal indentation is +fully respected, relative to the first line of code, and braces ({, }) are not required, nor +expected for anything but Python dictionaries. Since the first line of code is +used as an indentation reference, it is recommended that the start delimeter +of the tag (i.e. the [[\) be placed on its own +line, above the code chunk, as shown in the following example:

    + +
    + +
    [[\
    +    def printHello(num):
    +      for i in range(num):
    +        response.write('hello<br>')
    +
    +    printHello(5)
    +]]
    +
    +
    +

    +Naturally, one should not use braces here for purposes of indentation, +only for Python dictionaries. Additional braces will merely generate Python +syntax errors in the context of chunks. To recap: a Python statement tag +should contain braced Python; A Python chunk tag should contain regular +indented Python.

    +


    + + + + + +
    Prev: 2.4 - Python StatementsUp: 2 - LanguageNext: 2.6 - Python Class Chunks
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-lang_chunkc.html b/spyce-2.1/www/demo-site/docs/doc-lang_chunkc.html new file mode 100755 index 0000000000000000000000000000000000000000..88d919aba59d1b9805e572fe974a95867bd8be03 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-lang_chunkc.html @@ -0,0 +1,257 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Language
    +
    + + + + + + + +
    Prev: 2.5 - Python ChunksUp: 2 - LanguageNext: 2.7 - Python Expressions
    +
    +2.6. Python Class Chunks

    + + +Syntax: [[! Python class chunk ]] +

    +Behind the scenes, your Spyce files are compiled into a class called spyceImpl. Your Spyce script runs in a method of this class +called spyceProcess. Class chunks allow you to +specify code to be placed inside the class, but outside the main method, +analogously to the "<%!" token in JSP code. (If you would like to +see your Spyce file in compiled Python form, use the following command-line: +spyce.py -c myfile.spy.)

    +

    +This is primarily useful when defining active +handlers without using a separate .py file: active handlers are the first +thing that the spyceProcess calls, even before any "python chunks." +For a handler callback to be visible at this stage, it needs to be defined +at the class level. Class chunks to the rescue: +

    + +
    + examples/handlerintro.spy +
    + +
    [[!
    +def calculate(self, api, x, y):
    +    self.result = x * y
    +]]
    +
    +<spy:parent title="Active Handler example" />
    +<f:form>
    +    <f:text name="x:float" default="2" label="first value" />
    +    <f:text name="y:float" default="3" label="second value" />
    +
    +    <f:submit handler="self.calculate" value="Multiply" />
    +</f:form>
    +
    +<p>
    +Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]]
    +</p>
    +
    +
    +
    + + Run this code + +
    +

    +


    + + + + + +
    Prev: 2.5 - Python ChunksUp: 2 - LanguageNext: 2.7 - Python Expressions
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-lang_comment.html b/spyce-2.1/www/demo-site/docs/doc-lang_comment.html new file mode 100755 index 0000000000000000000000000000000000000000..4c2e36af2635668acd5f0eeb91099c3672db3c47 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-lang_comment.html @@ -0,0 +1,217 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Language
    +
    + + + + + + + +
    Prev: 2.1 - Plain HTML and Active TagsUp: 2 - LanguageNext: 2.3 - Spyce Directives
    +
    +2.2. Spyce Comments

    + + +Syntax: [[-- comment --]]

    +Spyce comments are ignored, and do not produce any output, meaning that they +will not appear at the browser even in the HTML source. The first line of a +Spyce file, if it begins with the characters #!, is also considered a +comment, by Unix scripting convention. Spyce comments do not nest.

    +


    + + + + + +
    Prev: 2.1 - Plain HTML and Active TagsUp: 2 - LanguageNext: 2.3 - Spyce Directives
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-lang_directive.html b/spyce-2.1/www/demo-site/docs/doc-lang_directive.html new file mode 100755 index 0000000000000000000000000000000000000000..592ea154cf490d85afbb27a7e67bf704f7a5f921 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-lang_directive.html @@ -0,0 +1,311 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Language
    +
    + + + + + + + +
    Prev: 2.2 - Spyce CommentsUp: 2 - LanguageNext: 2.4 - Python Statements
    +
    +2.3. Spyce Directives

    + + +Syntax: [[. directive ]] +

    +Spyce directives directly affect the operation of the Spyce compiler. There +is a limited set of directives, listed and explained below: +

      +
    • +[[.compact mode=mode]] :
      +Spyce can output the static HTML strings in various modes of compaction, +which can both save bandwidth and improve download times without visibly +affecting the output. Compaction of static HTML strings is performed once +when the input Spyce file is compiled, and there is no additional run-time +overhead beyond that. Dynamically generated content from Python code tags +and expressions is not compacted nor altered in any way. Spyce can operate +in one of the compaction modes listed below. One can use the compact +tag to change the compaction mode from that point in the file forwards.

      +

        +
      • off: No compaction is performed. Every space and newline in the +static HTML strings is preserved.
      • +

      • space: Space compaction involves reducing any consecutive runs +of spaces or tabs down to a single space. Any spaces or tabs at the +beginning of a line are eliminated. These transformations will not affect +HTML output, barring the <pre> tag, but can considerably reduce the +size of the emitted text.
      • +

      • line: Line compaction eliminates any (invisible) trailing +whitespace at the end of lines. More significantly it improves the indented +presentation of HTML, by ignoring any lines that do not contain any static +text or expression tags. Namely, it removes all the whitespace, including +the line break, surrounding the code or directives on that line. This +compaction method usually "does the right thing", and produces nice HTML +without requiring tricky indentation tricks by the developer. It is, +therefore, the initial compaction mode.
      • +

      • full: Full compaction applies both space and line compaction. If +the optional mode attribute is omitted, full compaction mode is the +default value assumed.
      • +

      +
    • +

    • +[[.import name=name from=file as=name args=arguments]] :
      +The import directive loads and defines a Spyce module into the global +context. (The [[.module ... ]]directive +is synonymous.) A Spyce module is a +Python file, written specifically to interact with Spyce. The name +parameter is required, specifying the name of the Python class to load. The +file parameter is optional, specifying the file where the named class +is to be found. If omitted, file will equal name.py. The file path can be absolute or +relative. Relative paths are scanned in the Spyce home, user-configurable server path directories and current +script directory, in that order. Users are encouraged to name or prefix their +modules uniquely so as not to be masked by system modules or tag libraries. +The as parameter is optional, and specifies the name under which the +module will be installed in the global context. If omitted, this parameter +defaults to the name parameter. Lastly, the optional args parameter +provides arguments to be passed to the module initialization function. All +Spyce modules are start()ed before Spyce processing begins, +init()ed at the point where the directive is placed in the code, and +finish()ed after Spyce processing terminates. It is convention to +place modules at, or near, the very top of the file unless the location of +initialization is relevant for the functioning of the specific module.

      + +[[.import names="name1,name2,..."]] :
      +An alternative syntax allows convenient loading of multiple Spyce modules. +One can not specify non-standard module file locations, nor rename the +modules using this syntax.

    • +

    • +[[.taglib name=name from=file as=name]] :
      +The taglib directive loads a Spyce tag library. A Spyce tag library is a Python file, written +specifically to interact with Spyce. The name parameter specifies +the name of the Python class to load if using a 1.x-style taglib; +otherwise it is ignored. The file parameter is +optional, specifying the file where the named class is to be found. If +omitted, file will equal name.py. The file +path can be absolute or relative. Relative paths are scanned in the Spyce +home, user-configurable server +path directories and current script directory, in that order. Users are +encouraged to name or prefix their tag libraries uniquely so as not to be +masked by system tag libraries and modules. The as parameter is +optional, and specifies the unique tag prefix that will be used to identify +the tags from this library. If omitted, this parameter defaults to the name +parameter. It is convention to place tag library directives at, or near, the +very top of the file. The tags only become active after the point of the tag +library directive.

      +Also note that the configuration parameter globaltags allows you +to set up tag libraries globally, freeing you from having to specify the +taglib directive on each page that uses a tag. By default, globaltags +installs core under the spy: prefix, and form under the f: prefix. +(Tag libraries specified in globaltags are only loaded if the Spyce compiler +determines they are actually used on the page, so there is no performance +difference between globaltags and manually setting up taglib for each page.) +

      There are some additional directives that are only legal when +defining an active tag library. +

    +It is important to note that Spyce directives are processed at compile +time, not during the execution of the script, much like directives in C, and +other languages. In other words, they are processed as the Python code for the +Spyce script is being produced, not as it is being executed. Consequently, it +is not possible to include runtime values as parameters to the various +directives.

    +


    + + + + + +
    Prev: 2.2 - Spyce CommentsUp: 2 - LanguageNext: 2.4 - Python Statements
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-lang_expr.html b/spyce-2.1/www/demo-site/docs/doc-lang_expr.html new file mode 100755 index 0000000000000000000000000000000000000000..a752aaec5486377fddf0ba6cc70f8f44e47ab823 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-lang_expr.html @@ -0,0 +1,224 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Language
    +
    + + + + + + + +
    Prev: 2.6 - Python Class ChunksUp: 2 - LanguageNext: 2.8 - Spyce Lambdas
    +
    +2.7. Python Expressions

    + + +Syntax: [[= expression ]] +

    +The contents of an expression tag is a Python expression. The result of that +expression evaluation is printed using the its string representation. +The Python object None is special cased to output as the empty string +just as it is in the Python interactive shell. This is almost always +more convenient when working with HTML. (If you really want a literal +string 'None' emitted instead, use response.write in a statement or chunk.) +

    +The Spyce transform module, can +pre-processes this result, to assist with mundane tasks such as ensuring that +the string is properly HTML-encoded, or formatted.

    +


    + + + + + +
    Prev: 2.6 - Python Class ChunksUp: 2 - LanguageNext: 2.8 - Spyce Lambdas
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-lang_lambda.html b/spyce-2.1/www/demo-site/docs/doc-lang_lambda.html new file mode 100755 index 0000000000000000000000000000000000000000..9c3512485ffd8b653c7f40f90054bbe7172db477 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-lang_lambda.html @@ -0,0 +1,286 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Language
    +
    + + + + + + + +
    Prev: 2.7 - Python ExpressionsUp: 2 - LanguageNext: 2.9 - ASP/JSP syntax
    +
    +2.8. Spyce Lambdas

    + + +Syntax: [[spy [params] : spyce lambda code ]] +
    +or: [[spy! [params] : spyce lambda code ]] +

    +A nice feature of Spyce is that Spyce scripts are first-class members of the +language. In other words, you can create a Spyce lambda (or function) in any +of the Spyce Python elements (statements, chunks and expressions). These can +then be invoked like regular Python functions, stored in variables for later +use, or be passed around as paramaters. This feature is often very useful for +templating (example shown below), and can also be used to implement more +esoteric processing functionality, such as internationalization, multi-modal +component frameworks and other kinds of polymorphic renderers.

    +It is instructive to understand how these functions are generated. The [[spy ... : ... ]] syntax is first +translated during compilation into a call to the define() function of the spylambda module. At runtime, this +call compiles the Spyce code at the point of its definition, and returns a +function. While the invocation of a Spyce lambda is reasonably efficient, it +is certainly not as fast as a regular Python function invocation. The +spycelambda can be memoized (explained in the spylambda module section) by using +the [[spy! ... : ... ]] +syntax. However, even with this optimization one should take care to use +Python lambdas and functions when the overhead of Spyce parsing and invocation +is not needed.

    +Note that Spyce lambdas do not currently support nested variable scoping, nor +default parameters. The global execution context (specifically, Spyce modules) +of the Spyce lambda is defined at the point of its execution.

    + +
    + examples/spylambda.spy +
    + +
    [[\
    +  # table template
    +  table = [[spy! title, data: 
    +    <table>
    +      <tr>
    +        [[for cell in title: {]]
    +          <td><b>[[=cell]]</b></td>
    +        [[}]]
    +      </tr>
    +      [[for row in data: {]]
    +        <tr>
    +          [[for cell in row: {]]
    +            <td>[[=cell]]</td>
    +          [[}]]
    +        </tr>
    +      [[}]]
    +    </table> 
    +  ]]
    +
    +  # table information
    +  title = ['Country', 'Size', 'Population', 'GDP per capita']
    +  data = [
    +    [ 'USA', '9,158,960', '280,562,489', '$36,300' ],
    +    [ 'Canada', '9,220,970', '31,902,268', '$27,700' ],
    +    [ 'Mexico', '1,923,040', '103,400,165', '$9,000' ],
    +  ]
    +]]
    +
    +[[-- emit web page --]]
    +<html><body>
    +  [[ table(title, data) ]]
    +</body></html>
    +
    +
    +
    +
    + + Run this code + +
    +

    +


    + + + + + +
    Prev: 2.7 - Python ExpressionsUp: 2 - LanguageNext: 2.9 - ASP/JSP syntax
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-lang_stmt.html b/spyce-2.1/www/demo-site/docs/doc-lang_stmt.html new file mode 100755 index 0000000000000000000000000000000000000000..69a642658e86fb1e48cd1e8cd34cd9013a220b61 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-lang_stmt.html @@ -0,0 +1,276 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Language
    +
    + + + + + + + +
    Prev: 2.3 - Spyce DirectivesUp: 2 - LanguageNext: 2.5 - Python Chunks
    +
    +2.4. Python Statements

    + + +Syntax: [[ statement(s) ]]

    +The contents of a code tag is one or more Python statements. The statements +are executed when the page is emitted. There will be no output unless the +statements themselves generate output.

    +The statements are separated with semi-colons or new lines, as in regular +Python scripts. However, unlike regular Python code, Python statements do +not nest based on their level of indentation. This is because +indenting code properly in the middle of HTML is difficult on the developer. +To alleviate this problem, Spyce supports a slightly modifed Python syntax: +proper nesting of Spyce statements is achieved using begin- and end-braces: +{ and }, +respectively. These MUST be used, because the compiler regenerates the +correct indentation based on these markers alone. Even single-statement blocks +of code must be wrapped with begin and end braces. (If you prefer to use +Python-like indentation, read about chunks).

    +The following Spyce code, from the Hello World! +example above:

    + +
    + +
    +  [[ for i in range(10): { ]]
    +    [[=i]]
    +  [[ } ]]
    +
    +
    +
    +

    +produces the following indented Python code:

    + +
    + +
    +  for i in range(10):
    +    response.writeStatic('  ')
    +    response.writeExpr(i)
    +    response.writeStatic('\n')
    +
    +
    +
    +

    +Without the braces, the code produced would be unindented and, in this case, +also invalid:

    + +
    + +
    +  for i in range(10):
    +  response.writeStatic('  ')
    +  response.writeExpr(i)
    +  response.writeStatic('\n')
    +
    +
    +
    +

    +Note how the indentation of the expression does not affect the indentation of +the Python code that is produced; it merely changes the number of spaces in +the writeStatic string. Also note that unbalanced +open and close braces within a single tag are allowed, as in the example +above, and they modify the indentation level outside the code tag. However, +the braces must be balanced across an entire file. Remember: inside the [[ ... ]] delimiters, braces are always +required to change the indentation level.

    +


    + + + + + +
    Prev: 2.3 - Spyce DirectivesUp: 2 - LanguageNext: 2.5 - Python Chunks
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-lang_string.html b/spyce-2.1/www/demo-site/docs/doc-lang_string.html new file mode 100755 index 0000000000000000000000000000000000000000..2ab4188f8361cbb1bdf3aa66205e355bc8cc0b9b --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-lang_string.html @@ -0,0 +1,230 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Language
    +
    + + + + + + + +
    Prev: 2 - LanguageUp: 2 - LanguageNext: 2.2 - Spyce Comments
    +
    +2.1. Plain HTML and Active Tags

    + + +Static plain HTML strings are printed as they are encountered. Depending on +the compacting mode of the +Spyce compiler, some whitespace may be eliminated. The Spyce transform module, for example, may +further pre-processes this string, by inserting transformations into the +output pipe. This is useful, for example, for dynamic compression of the +script result.

    +The Spyce language supports tag libraries. +Once a tag library is imported under some name, mytags, then all static +HTML tags of the form <mytags:foo ... > become "active". +That is, code from the tag library is executed at that point in the document. +Tags can control their output, conditionally skip or loop the execution of +their bodies, and can interact with other active tags in the document. They +are similar, in spirit and functionality, to JSP tags. Tag libraries and modules (discussed later) can both +considerably reduce the amount of code on a Spyce page, and increase code +reuse and modularity.

    +


    + + + + + +
    Prev: 2 - LanguageUp: 2 - LanguageNext: 2.2 - Spyce Comments
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod.html b/spyce-2.1/www/demo-site/docs/doc-mod.html new file mode 100755 index 0000000000000000000000000000000000000000..eb041e92bc05fe25a9e33db6e734caba9335e81e --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod.html @@ -0,0 +1,248 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.7.2 - spyceUtilUp: 3 - RuntimeNext: 3.8.1 - DB (implicit)
    +
    +3.8. Modules

    + + +The Spyce language, as described above, is simple and small. Most +functionality is provided at runtime through Spyce modules and Python modules. +

    +The standard Spyce modules are documented here; some other modules are +also distributed in the contrib/ directory. +

    +Non-implicit modules are +imported using the Spyce [[.import]] directive. Python modules are +imported using the Python import keyword. Remember that +modules need to have the same read permissions as regular files that you +expect the web server to read. +

    +Modules may be imported with a non-default name using the as attribute +to the .import directive. This is discouraged for standard Spyce modules; the +session module, for example, expects to find or +otherwise load a module named cookie in the Spyce environment. +

    +Once included, a Spyce module may be accessed anywhere in the Spyce code. + +


    + + + + + +
    Prev: 3.7.2 - spyceUtilUp: 3 - RuntimeNext: 3.8.1 - DB (implicit)
    +

    +Sub-sections: +

    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_compress.html b/spyce-2.1/www/demo-site/docs/doc-mod_compress.html new file mode 100755 index 0000000000000000000000000000000000000000..66a729ac3f9fe4a73ddc9ff53efcc0b9c0a2d506 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_compress.html @@ -0,0 +1,266 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.8 - TransformUp: 3.8 - ModulesNext: 3.8.10 - Include
    +
    +3.8.9. Compress

    + + +The compress module supports dynamic compression of Spyce output, and can save +bandwidth in addition to static compaction. The different forms +of compression supported are described below. +

      +
    • spaces( [ boolean ] ):
      Controls dynamic space compression. +Dynamic space compression will eliminate consecutive whitespaces (spaces, +newlines and tabs) in the output stream, each time it is flushed. The optional +boolean parameter defaults to true.

      +

    • gzip( [ level ] ):
      Applies gzip compression to the Spyce +output stream, but only if the browser can support gzip content encoding. Note +that this function will fail if the output stream has already been flushed, +and should generally only be used with buffered output streams. The optional +level parameter specifies the compression level, between 1 and 9 +inclusive. A value of zero disables compression. If level is omitted, the +default gzip compression level is used. This function will automatically check +the request's Accept-Encoding header, and set the response's +Content-Encoding header.

      +

    +The example below shows the compression module in use.

    + +
    + examples/compress.spy +
    + +
    [[.import name=compress args="gzip=1, spaces=1"]]
    +[[\
    +  response.write('<html><body>')
    +  response.write('  Space compression will remove these     spaces.<br>')
    +  response.write('  gzip compression will highly compress this:<br>')
    +  for i in range(1000):
    +    response.write('  hello')
    +  response.write('</body></html>')
    +]]
    +
    +
    +
    + + Run this code + +
    +

    +Note that the compression functions need not be called at the beginning of the +input, but before the output stream is flushed. Also, to really see what is +going on, you should telnet to your web server, and provide something like the +following request. + +
    + +
    GET /spyce/examples/compress.spy HTTP/1.1
    +Accept-Encoding: gzip
    +
    +
    +

    +


    + + + + + +
    Prev: 3.8.8 - TransformUp: 3.8 - ModulesNext: 3.8.10 - Include
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_cookie.html b/spyce-2.1/www/demo-site/docs/doc-mod_cookie.html new file mode 100755 index 0000000000000000000000000000000000000000..3f4800067daad1c422625461029919b8a7ae01bf --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_cookie.html @@ -0,0 +1,309 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.4 - RedirectUp: 3.8 - ModulesNext: 3.8.6 - Session
    +
    +3.8.5. Cookie

    + + +This module provides cookie functionality. Its methods are: +

      +
    • get( [key] ):
      Return a specific cookie string sent by the +browser. If the optional cookie key is omitted, a dictionary of all +cookies is returned. The cookie module may also be accessed as an +associative array to achieve the same result as calling: namely, cookie['foo'] and cookie.get('foo') are equivalent.
    • +

    • set( key, value, [expire], [domain], [path], [secure] ):
      +Sends a cookie to the browser. The cookie will be sent back on +subsequent requests and can be retreived using the get function. The +key and value parameters are required; the rest are optional. +The expire parameter determines how long this cookie information will +remain valid. It is specified in seconds from the current time. If expire is +omitted, no expiration value will be provided along with the cookie header, +meaning that the cookie will expire when the browser is closed. The +domain and path parameters specify when the cookie will get +sent; it will be restricted to certain document paths at certain domains, +based on the cookie standard. If these are omitted, then path and/or domain +information will not be sent in the cookie header. Lastly, the secure +parameter, which defaults to false if omitted, determines whether the cookie +information can be sent over an HTTP connection, or only via HTTPS.
    • +

    • delete( key ):
      Send a cookie delete header to the browser to +delete the key cookie. The same may be achieved by: del cookie[key].
    • +

    +The example below shows to manage browser cookies.

    + +
    + examples/cookie.spy +
    + +
    [[.import name=cookie]]
    +<html><body>
    +  Managing cookies is simple. Use the following forms 
    +  to create and destroy cookies. Remember to refresh 
    +  once, because the cookie will only be transmitted on 
    +  the <i>following</i> request.<br>
    +  [[-- input forms --]]
    +  <hr>
    +  <form action="[[=request.uri('path')]]" method=post>
    +    <table><tr>
    +      <td align=right>Cookie name:</td>
    +      <td><input type=text name=name></td>
    +      <td>(required)</td>
    +    </tr><tr>
    +      <td align=right>value:</td>
    +      <td><input type=text name=value></td>
    +      <td>(required for set)</td>
    +    </tr><tr>
    +      <td align=right>expiration:</td>
    +      <td><input type=text name=exp> seconds.</td>
    +      <td>(optional)</td>
    +    </tr><tr>
    +      <td colspan=3>
    +        <input type=submit name=operation value=set>
    +        <input type=submit name=operation value=delete>
    +        <input type=submit name=operation value=refresh>
    +      </td>
    +    </tr></table>
    +  </form>
    +  <hr>
    +  [[-- show cookies --]]
    +  Cookies: [[=len(cookie.get().keys())]]<br>
    +  <table>
    +    <tr>
    +      <td><b>name</b></td>
    +      <td><b>value</b></td>
    +    </tr>
    +    [[for c in cookie.get().keys(): {]]
    +      <tr>
    +        <td>[[=c]]</td>
    +        <td>[[=cookie.get(c)]]</td>
    +      </tr>
    +    [[ } ]]
    +  </table>
    +  [[-- set cookies --]]
    +  [[\
    +    operation = request.post('operation')
    +    if operation:
    +      operation = operation[0]
    +      name = request.post('name')[0]
    +      value = request.post('value')[0]
    +      if operation == 'set' and name and value:
    +        cookie.set(name, value)
    +      if operation == 'delete' and name:
    +        cookie.delete(name)
    +  ]]
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    +


    + + + + + +
    Prev: 3.8.4 - RedirectUp: 3.8 - ModulesNext: 3.8.6 - Session
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_db.html b/spyce-2.1/www/demo-site/docs/doc-mod_db.html new file mode 100755 index 0000000000000000000000000000000000000000..ccc2b0f422d73f74cfbdcd7b7b470e9f38ab38a8 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_db.html @@ -0,0 +1,428 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8 - ModulesUp: 3.8 - ModulesNext: 3.8.2 - Request (implicit)
    +
    +3.8.1. DB (implicit)

    + + + + +Spyce integrates an advanced database module to make reading and modifying +your data faster and less repetitive. +

    +Just initialize the db reference in your Spyce config file following +the examples given there, and you're all set. + +

    The general idea is, db.tablename represents the tablename table +in your database, and provides hooks for reading and modifying data in that +table in pure Python, no SQL required. We find this gives 90% of the benefits +of an object-relational mapping layer, without making developers learn another +complex tool. And since the Spyce db module is part of +SQLAlchemy, probably the most advanced +database toolkit in the world, the full ORM approach is available to those +who want it. + +

    +Here's a quick example of reading +and inserting data from a table called todo_lists: + + +
    + examples/db.spy +
    + +
    <spy:parent title="To-do demo" />
    +
    +[[!
    +def list_new(self, api, name):
    +    if api.db.todo_lists.selectfirst_by(name=name):
    +        raise HandlerError('New list', 'a list with that description already exists')
    +    api.db.todo_lists.insert(name=name)
    +    api.db.flush()
    +]]
    +
    +(This is an self-contained example using the same database as the
    +<a href=/demos/to-do/index.spy>to-do demo</a>.)
    +
    +<h2>To-do lists</h2>
    +
    +[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]]
    +<spy:ul data="[L.name for L in lists]" />
    +
    +<h2>New list</h2>
    +<f:form>
    +<f:submit value="New list" handler=self.list_new />:
    +<f:text name=name value="" />
    +</f:form>
    +
    +
    +
    + + Run this code + +
    + + +

    Full SqlSoup documentation follows. + +

    Loading objects

    +

    +Loading objects is as easy as this: +

    +    >>> users = db.users.select()
    +    >>> users.sort()
    +    >>> users
    +    [MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0), 
    +     MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)]
    +
    + +

    +Of course, letting the database do the sort is better (".c" is short for ".columns"): +

    +    >>> db.users.select(order_by=[db.users.c.name])
    +    [MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1),
    +     MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0)]
    +
    + +

    +Field access is intuitive: +

    +    >>> users[0].email
    +    u'student@example.edu'
    +
    + +

    +Of course, you don't want to load all users very often. The common case is to +select by a key or other field: +

    +    >>> db.users.selectone_by(name='Bhargan Basepair')
    +    MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)
    +
    + +

    Select variants

    +All the SqlAlchemy Query select variants are available. +Here's a quick summary of these methods: + +- get(PK): load a single object identified by its primary key (either a scalar, or a tuple) +- select(Clause, **kwargs): perform a select restricted by the Clause argument; returns a list of objects. The most common clause argument takes the form "db.tablename.c.columname == value." The most common optional argument is order_by. +- select_by(**params): the *_by selects allow using bare column names. (columname=value)This feels more natural to most Python programmers; the downside is you can't specify order_by or other select options. +- selectfirst, selectfirst_by: returns only the first object found; equivalent to select(...)[0] or select_by(...)[0], except None is returned if no rows are selected. +- selectone, selectone_by: like selectfirst or selectfirst_by, but raises if less or more than one object is selected. +- count, count_by: returns an integer count of the rows selected. + +See the SqlAlchemy documentation for details: +- general info and examples +- details on constructing WHERE clauses + +

    Modifying objects

    + +

    +Modifying objects is intuitive: +

    +    >>> user = _
    +    >>> user.email = 'basepair+nospam@example.edu'
    +    >>> db.flush()
    +
    + +

    (SqlSoup leverages the sophisticated SqlAlchemy unit-of-work code, so +multiple updates to a single object will be turned into a single UPDATE +statement when you flush.) + +

    +To finish covering the basics, let's insert a new loan, then delete it: +

    +    >>> db.loans.insert(book_id=db.books.selectfirst(db.books.c.title=='Regional Variation in Moss').id, user_name=user.name)
    +    MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
    +    >>> db.flush()
    +
    +    >>> loan = db.loans.selectone_by(book_id=2, user_name='Bhargan Basepair')
    +    >>> db.delete(loan)
    +    >>> db.flush()
    +
    + +

    +You can also delete rows that have not been loaded as objects. Let's do our insert/delete cycle once more, +this time using the loans table's delete method. (For SQLAlchemy experts: +note that no flush() call is required since this +delete acts at the SQL level, not at the Mapper level.) The same where-clause construction rules +apply here as to the select methods: +

    +    >>> db.loans.insert(book_id=book_id, user_name=user.name)
    +    MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
    +    >>> db.flush()
    +    >>> db.loans.delete(db.loans.c.book_id==2)
    +
    + +

    +You can similarly update multiple rows at once. This will change the book_id to 1 in all loans whose book_id is 2: +

    +    >>> db.loans.update(db.loans.c.book_id==2, book_id=1)
    +    >>> db.loans.select_by(db.loans.c.book_id==1)
    +    [MappedLoans(book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
    +
    + + +

    Joins

    + +

    Occasionally, you will want to pull out a lot of data from related tables all at +once. In this situation, it is far +more efficient to have the database perform the necessary join. (Here +we do not have "a lot of data," but hopefully the concept is still clear.) +SQLAlchemy is smart enough to recognize that loans has a foreign key +to users, and uses that as the join condition automatically. +

    +    >>> join1 = db.join(db.users, db.loans, isouter=True)
    +    >>> join1.select_by(name='Joe Student')
    +    [MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
    +     book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
    +
    + +

    +You can compose arbitrarily complex joins by combining Join objects with +tables or other joins. +

    +    >>> join2 = db.join(join1, db.books)
    +    >>> join2.select()
    +    [MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
    +     book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0),
    +     id=1,title='Mustards I Have Known',published_year='1989',authors='Jones')]
    +
    + +

    +If you join tables that have an identical column name, wrap your join with "with_labels", +and all the columns will be prefixed with their table name: +

    +    >>> db.with_labels(join1).select()
    +    [MappedUsersLoansJoin(users_name='Joe Student',users_email='student@example.edu',
    +                          users_password='student',users_classname=None,users_admin=0,
    +                          loans_book_id=1,loans_user_name='Joe Student',
    +                          loans_loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
    +
    + +

    Advanced usage

    + +

    +You can access the SqlSoup's engine attribute to compose SQL directly. +The engine's execute method corresponds +to the one of a DBAPI cursor, and returns a ResultProxy that has fetch methods +you would also see on a cursor. + +

    +    >>> rp = db.engine.execute('select name, email from users order by name')
    +    >>> for name, email in rp.fetchall(): print name, email
    +    Bhargan Basepair basepair+nospam@example.edu
    +    Joe Student student@example.edu
    +
    + +

    +You can also pass this engine object to other SQLAlchemy constructs; see the SQLAlchemy documentation for details. + +

    +You can access SQLAlchemy Table and Mapper objects as db.tablename._table and db.tablename._mapper, respectively. + + +


    + + + + + +
    Prev: 3.8 - ModulesUp: 3.8 - ModulesNext: 3.8.2 - Request (implicit)
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_error.html b/spyce-2.1/www/demo-site/docs/doc-mod_error.html new file mode 100755 index 0000000000000000000000000000000000000000..0d4008e1f707b78f0aded54c7834519ed7dbee93 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_error.html @@ -0,0 +1,337 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.11 - Internal modulesUp: 3.8.11 - Internal modulesNext: 3.8.11.2 - Stdout
    +
    + +The error module is implicitly loaded and provides error-handling +functionality. An error is any unhandled runtime exception that +occurs during Spyce processing. This mechanism does not include +exceptions that are not related to Spyce processing (i.e. server-related +exceptions), that can be caused before or after Spyce processing by invalid +syntax, missing files and file access restrictions. To install a server-level +error handler use a configuration +file. The default page-level error handler can also be modified in the configuration file. This module +allows the user to install page-level error handling code, overriding the +default page-level handler, by using one of the following functions:

    +

      +
    • setStringHandler( string ):
      Installs a function that will +processes the given string, as Spyce code, for error handling. +
    • +

    • setFileHandler( uri ):
      Installs a function that will +processes the given uri for error handling.
    • +

    • setHandler( fn ):
      Installs the fn function for error +handling. The function is passed one parameter, a reference to the error +module. From this, all the error information as well as references to other +modules and Spyce objects can be accessed.
    • +

    +The error module provides the following information about an error:

    +

      +
    • isError():
      Returns whether an error is being handled. +
    • +

    • getMessage():
      Return the error message; the string of the +object that was raised, or None if there is no current error.
    • +

    • getType():
      Return the error type; the type of the object +that was raised, or None if there is no current error.
    • +

    • getFile():
      Return the file where the error was raised, or +None if there is no current error.
    • +

    • getTraceback():
      Return the stack trace as an array of +tuples, or None if there is no current error. Each tuple entry is of the +form: (file, line numbers, function name, code context).
    • +

    • getString():
      Return the string of the entire error (the +string representation of the message, type, location and stack trace), or +None if there is no current error.
    • +

    +The default error handling function uses the following string handler: +
    +
    
    +[[.module name=transform]]
    +[[transform.expr('html_encode')]]
    +<html>
    +<title>Spyce exception: [[=error.getMessage()]]</title>
    +<body>
    +<table cellspacing=10 border=0>
    +  <tr><td colspan=2><h1>Spyce exception</h1></td></tr>
    +  <tr><td valign=top align=right><b>File:</b></td><td>[[=error.getFile()]]</tr>
    +  <tr><td valign=top align=right><b>Message:</b></td>
    +    <td><pre>[[=error.getMessage()]]</pre></tr>
    +  <tr><td valign=top align=right><b>Stack:</b></td><td>
    +    [[\
    +      L = list(error.getTraceback())
    +      L.reverse()
    +    ]]
    +    [[ for frame in L: { ]]
    +      [[=frame[0] ]]:[[=frame[1] ]], in [[=frame[2] ]]:<br>
    +      <table border=0><tr><td width=10></td><td>
    +        <pre>[[=frame[3] ]]</pre>
    +      </td></tr></table>
    +    [[ } ]]
    +    </td></tr>
    +</table>
    +</body></html>
    +
    +
    +

    +The example below shows the error module in use. Error handling can often be +used to send emails notifying webmasters of problems, as this example shows. +

    + +
    + examples/error.spy +
    + +
    [[error.setFileHandler('error.spi') ]]
    +This is a page with an error...
    +[[ raise 'an error' ]]
    +
    +
    +
    + + Run this code + +
    +

    + +
    + examples/error.spi +
    + +
    <h1>Oops</h1>
    +An error occurred while processing your request. 
    +We have logged this for our webmasters, and they 
    +will fix it shortly. We apologize for the inconvenience.
    +In the meantime, please use the parts of our site that 
    +actually do work... <a href="somewhere">somewhere</a>.
    +[[\
    +  # could redirect the user immediately
    +  #response.getModule('redirect').external('somewhere.spy')
    +
    +  # could send an email
    +  import time
    +  msg = '''
    +time: %s
    +error: %s
    +env: %s
    +other info...
    +''' % (
    +    time.asctime(time.localtime(time.time())), 
    +    error.getString(),
    +    request.env()
    +  )
    +  #function_to_send_email('webmaster@foo.com', msg)
    +
    +  #or perform other generic error handling...
    +]]
    +
    +
    +
    +

    +This mechanism is not a subsititute for proper exception handling within the +code itself, and should not be abused. It does, however, serve as a useful +catch-all for bugs that slip through the cracks.

    +


    + + + + + +
    Prev: 3.8.11 - Internal modulesUp: 3.8.11 - Internal modulesNext: 3.8.11.2 - Stdout
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_include.html b/spyce-2.1/www/demo-site/docs/doc-mod_include.html new file mode 100755 index 0000000000000000000000000000000000000000..104b2b05c20229ca70e3026dddfdb5e81c228a36 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_include.html @@ -0,0 +1,381 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.9 - CompressUp: 3.8 - ModulesNext: 3.8.11 - Internal modules
    +
    +3.8.10. Include

    + + +Many websites carry a theme across their various pages, which is often +achieved by including a common header or footer. This is best done with a +parent template from the spy:parent tag, +but you can also do this with the include module for backwards compatibility +with Spyce 1.x. +

    +Another option to consider for repeating a common task is +a custom +active tag. +

    +The include module can also pretty print Spyce code or include the contents of +anything in your filesystem. +

      +
    • spyce( file, [context] ):
      Dynamically includes the specified +file (corresponding to the Spyce document root, not filesystem), +and processes it as Spyce code. The return value is that of the +included Spyce file. One can optionally provide a context value to +the included file. If omitted, the value defaults to None. All currently +imported modules are passed along into the included file without +re-initialization. However, for each explicit [[.import ]] tag in the included file, a new +module is initialized and also finalized up at the end of processing. The +include module provides three fields for use inside included files:

      +

        +
      • include.context: This field stores the value passed in at the +point of inclusion. Note that if the value is one that is passed by +reference (as is the case with object, list, and dictionary types), then +the context may be used to pass information back to the including file, in +addition to the return value.
      • +

      • include.vars: If the include context is of type dictionary, +then the vars field is initialized, otherwise it is None. The vars field +provides attribute-based access to the context dictionary, merely for +convenience. In other words, include.vars.x is +equivalent to include.context['x'].
      • +

      +Note that either the locals() or globals() dictionaries may be passed in as +include contexts. However, be advised that due to Python optimizations of +local variable access, any updates to the locals() dictionary may not be +reflected in the local namespace under all circumstances and all versions of +Python. In fact, this is the reason why the context has been made explicit, +and does not simply grab the locals() dictionary. It may, however, safely be +used for read access. With respect to the globals() dictionary, it is not +advised to pollute this +namespace.
    • +

    • spyceStr( file, [context] ):
      Same as spyce(), but +performs no output and instead returns the processed included Spyce file as +a string.
    • +

    • dump( file, [binary] ):
      Contents of the file +(from the filesystem -- use spyceUtil.url2file(url, request.filename) +if you need to turn a url into a filesystem path) +are returned. If the binary parameter is true, the file is opened in +binary mode. By default, text mode is used. +

      Be careful not to blindly trust the user to specify which file +to dump, since anything your Spyce process has access to in the filesystem +is fair game. +

    • +

    • spycecode( file ):
      Contents of the file +(relative to the Spyce document root) +are returned +as HTML formatted Spyce code.
    • +

    +The example below (taken from this documentation file), uses a common header +template only requiring two context variables to change the title and the +highlighted link:
    + +
    + +
      [[.import name=include]]
    +  [[include.spyce('inc/head.spi', 
    +      {'pagename': 'Documentation', 
    +       'page': 'manual.html'}) ]]
    +
    +

    + +In head.spi, we use this information to set the title:

    + +
    + +
    +  [[.import name=include]]
    +  <title>[[=include.context['pagename'] ]]</title>
    +
    +
    +

    + +By convention, included files are given the extension .spi.

    +Below we contrast the difference between static and dynamic includes. A +dynamic include is included on each request; a static include is inserted at +compile time. A static include runs in the same context, while a dynamic +include has a separate context.

    + +
    + examples/include.spy +
    + +
    [[.import name=include]]
    +<html><body>
    +  main file<br>
    +  <hr>
    +  [[
    +    context = {'foo': 'old value'}
    +    result=include.spyce('include.spi', context)
    +  ]]
    +  <hr>
    +  main file again<br>
    +  context: [[=context]]<br>
    +  return value: [[=result]]<br>
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    + +
    + examples/include.spi +
    + +
    begin include<br>
    +context: [[=include.context ]]<br>
    +from: [[=request.stack()[-2] ]]<br>
    +foo was [[=include.vars.foo]]<br>
    +setting foo to 'new value' [[include.vars.foo = 'new value']]<br>
    +returing 'retval'<br>
    +end include<br>
    +[[ return 'retval' ]]
    +
    +
    +
    +

    + +
    + examples/includestatic.spy +
    + +
    <html><body>
    +  [[x=1]]
    +  main file<br>
    +  x=[[=x]]<br>
    +  <hr>
    +  [[.include file="includestatic.spi"]]
    +  <hr>
    +  main file again<br>
    +  x=[[=x]]
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    + +
    + examples/includestatic.spi +
    + +
    begin included file<br>
    +changing value of x<br>
    +[[x=2]]
    +end included file<br>
    +
    +
    +
    +

    +


    + + + + + +
    Prev: 3.8.9 - CompressUp: 3.8 - ModulesNext: 3.8.11 - Internal modules
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_internal.html b/spyce-2.1/www/demo-site/docs/doc-mod_internal.html new file mode 100755 index 0000000000000000000000000000000000000000..62890eb090c1d8c06c749ddc395eb02f09ecc541 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_internal.html @@ -0,0 +1,224 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.10 - IncludeUp: 3.8 - ModulesNext: 3.8.11.1 - Error
    +
    +3.8.11. Internal modules

    + + +These modules are used internally by Spce. Documentation is included for +those curious about Spyce internals; ordinarily you will never use these +modules directly. + +


    + + + + + +
    Prev: 3.8.10 - IncludeUp: 3.8 - ModulesNext: 3.8.11.1 - Error
    +

    +Sub-sections: +

    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_lambda.html b/spyce-2.1/www/demo-site/docs/doc-mod_lambda.html new file mode 100755 index 0000000000000000000000000000000000000000..f3cfbbe73f66e2c441668f9d32c046d0ad7c9583 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_lambda.html @@ -0,0 +1,238 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.11.2 - StdoutUp: 3.8.11 - Internal modulesNext: 3.8.11.4 - Taglib
    +
    + +This module is used internally by Spyce, not usually by users. Documentation is +included for those curious about Spyce internals. The spylambda module is +loaded implicitly and allows the definition of functions based on Spyce +scripts; see Spyce Lambdas. The +spylambda module provides the following methods: +
      +
    • define( args, code, [memoize] ):
      Returns a function that +accepts the given args and executes the Spyce script defined by the +code parameter. Note that the code is compiled immediately and that +spyce.spyceSyntaxError or spyce.spycePythonError exceptions can be thrown for +invalid code arguments. The optional memoize parameter sets whether +the spyce can or can not be memoized, with the default being false. +Memoizing a function means capturing the result and output and caching them, +keyed on the function parameters. Later, if a function is called again with +the same parameters, the cached information is returned, if it exists, and +the function may not actually be called. Thus, you should only memoize +functions that are truly functional, i.e. they do not have side-effects: +they only return a value and output data to the response object, and their +behaviour depends exclusively on their parameters. If you memoize code that +does have side-effects, those side-effects may not occur on every +invocation.
    • +

    • __call__( args, code, _spyceCache ):
      This is an alias to the +define function. Because of the special method name, the spylambda module +object can be called as +if it were a function.
    • +

    +
    + + + + + +
    Prev: 3.8.11.2 - StdoutUp: 3.8.11 - Internal modulesNext: 3.8.11.4 - Taglib
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_new.html b/spyce-2.1/www/demo-site/docs/doc-mod_new.html new file mode 100755 index 0000000000000000000000000000000000000000..e863c0b52bb02d812ded3e244ab4748923f68880 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_new.html @@ -0,0 +1,373 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.11.4 - TaglibUp: 3.8 - ModulesNext: 3.9 - Tags
    +
    +3.8.12. Writing Modules

    + + +Writing your own Spyce modules is simple. +

    +A Spyce modules is simply a Python class that exposes specific methods +to the Spyce server. The most important are start, finish, +and init. With these, +a Spyce module may access the +internal request and response structures or alter the behaviour of the +runtime engine in some way. +

    Let us begin with a basic example +called myModule. It is a module that implements one function named foo(). + +
    + examples/myModule.py +
    + + + +
    +from spyceModule import spyceModule
    +
    +class myModule(spyceModule):
    +  def foo(self):
    +    print 'foo called'
    +
    +
    +
    + +
    +
    +

    +Saving this code in myModule.py in the same +directory as the Spyce script, or somewhere on the module path, we could use +it as expected:

    +
    + +
    [[.import name=myModule]]
    +[[ myModule.foo() ]]
    +
    +
    +

    +A Spyce module can be any Python class that derives from +spyceModule.spyceModule. When it is loaded, Spyce assigns it a +__file__ attribute indicating its source location. +Do not override the __init__(...) +method because it is inherited from spyceModule and has an fixed signature +that is expected by the Spyce engine's module loader. The inherited method +accepts a Spyce API object, a Bastion +of spyce.spyceWrapper, an internal engine object, and stores it in +self._api. This is the building block for all the functionality that +any module provides. The available API methods of the wrapper are (listed in +spyceModule.spyceModuleAPI): +

      + +
    • getFilename: Return filename of current Spyce
    • + +
    • getCode: Return processed Spyce (i.e. Python) code
    • + +
    • getCodeRefs: Return python-to-Spyce code line references
    • + +
    • getModRefs: Return list of import references in Spyce code
    • + +
    • getServerObject: Return unique (per engine instance) server object
    • + +
    • getServerGlobals: Return server configuration globals
    • + +
    • getServerID: Return unique server identifier
    • + +
    • getModules: Return references to currently loaded modules
    • + +
    • getModule: Get module reference. The module is dynamically loaded and initialised + if it does not exist (ie. if it was not explicitly imported, but requested + by another module during processing)
    • + +
    • setModule: Add existing module (by reference) to Spyce namespace (used for includes)
    • + +
    • getGlobals: Return the Spyce global namespace dictionary
    • + +
    • registerModuleCallback: Register a callback for modules change
    • + +
    • unregisterModuleCallback: Unregister a callback for modules change
    • + +
    • getRequest: Return internal request object
    • + +
    • getResponse: Return internal response object
    • + +
    • setResponse: Set internal response object
    • + +
    • registerResponseCallback: Register a callback for when internal response changes
    • + +
    • unregisterResponseCallback: Unregister a callback for when internal response changes
    • + +
    • spyceString: Return a spyceCode object of a string
    • + +
    • spyceFile: Return a spyceCode object of a file
    • + +
    • spyceModule: Return Spyce module class
    • + +
    • spyceTaglib: Return Spyce taglib class
    • + +
    • setStdout: Set the stdout stream (thread-safe)
    • + +
    • getStdout: Get the stdout stream (thread-safe)
    • + +

    +For convenience, one can sub-class the spyceModulePlus class instead of +the regular spyceModule. The spyceModulePlus defines a +self.modules field, which can be used to acquire references to other +modules loaded into the Spyce environment. The response module, for +instance, would be referenced as self.modules.response. Modules are +loaded on demand, if necessary. The spyceModulePlus also contains a +self.globals field, which is a reference to the Spyce global namespace +dictionary, though this should rarely be needed.

    +Note: It is not expected that many module writers will need the entire +API functionality. In fact, the vast majority of modules will use a small +portion of the API, if at all. Many of these functions are included for just +one of the standard Spyce modules that needs to perform some esoteric +function.

    +Three Spyce module methods, start(), init([args]) and +finish(error) are special in that they are automatically called by the +runtime during Spyce request startup, processing and cleanup, respectively. +The modules are started in the order in which module directives appear in the +file, before processing begins. The implicitly loaded modules are always +loaded first. The init method is called during Spyce processing at the +location of the module directive in the file, with the optional args attribute +is passed as the arguments of this call. Finally, after Spyce processing is +complete, the modules are finalized in reverse order. If there is an unhandled +exception, it will be wrapped in a spyce.spyceException object and passed as +the first parameter to finish(). During successful completion of Spyce +processing (i.e. without exception), the error parameter is None. The default +inherited start, init and finish methods from spyceModule are noops.

    +Note 2: When writing a Spyce module, consider carefully why you are +selecting a Spyce module over a regular Python module. If it is just code, +that does not interact with the Spyce engine, then a regular Python import instead of an Spyce [[.import]] can just as easily bring in the necessary +code, and is preferred. In other words, choose a Spyce module only when there +is a need for per-request initialization or for one of the engine APIs.

    +Module writers are encouraged to look at the existing standard modules as +examples and the definitions of the core Spyce objects in spyce.py as well. If you write or use a novel Spyce +module that you think is of general use, please email your +contribution, +or a link to it. Also, please keep in mind that the standard modules are designed +with the goal of being minimalist. Much functionality is readily available +using the Python language libraries. If you think that they should be +expanded, also please send a note.

    +


    + + + + + +
    Prev: 3.8.11.4 - TaglibUp: 3.8 - ModulesNext: 3.9 - Tags
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_pool.html b/spyce-2.1/www/demo-site/docs/doc-mod_pool.html new file mode 100755 index 0000000000000000000000000000000000000000..b0f3bf71beef51d5703b6ac3163723a91a4ac4c3 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_pool.html @@ -0,0 +1,259 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.6 - SessionUp: 3.8 - ModulesNext: 3.8.8 - Transform
    +
    +3.8.7. Pool

    + + +The pool module provides support for server-pooled variables. That is support +for variables whose lifetime begins when declared, and ends when explicitly +deleted or when the server dies. These variables are often useful for caching +information that would be expensive to +compute from scratch for each request. Another common use of pool variables is to store +file- or memory-based lock objects for concurrency control. A pooled variable +can hold any Python value.

    +The pool module may be accessed as a regular dictionary, supporting the usual +get, set, delete, has_key, keys, values and clear operations. +

    +The example below shows how the module is used:

    + +
    + examples/pool.spy +
    + +
    [[.import names="pool"]]
    +<html><body>
    +  The pool module supports long-lived server-pooled objects,<br>
    +  useful for database connections, and other variables<br>
    +  that are expensive to compute.<br>
    +  [[\
    +    if 'foo' in pool:
    +      print 'Pooled object foo EXISTS.'
    +    else:
    +      pool['foo'] = 1
    +      print 'Pooled object foo CREATED.'
    +  ]]
    +  <br>
    +  Value: [[=pool['foo'] ]] <p>
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    +

    +Pool performance suffers when not used with the Spyce webserver in +threaded concurrency mode; Spyce has to un/pickle the shared pool with +each request since there is no single long-lived process that can keep +the data in-memory. +


    + + + + + +
    Prev: 3.8.6 - SessionUp: 3.8 - ModulesNext: 3.8.8 - Transform
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_redirect.html b/spyce-2.1/www/demo-site/docs/doc-mod_redirect.html new file mode 100755 index 0000000000000000000000000000000000000000..e6ea78b1c8fca2adfa278518d2d3d919991df41a --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_redirect.html @@ -0,0 +1,294 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.3 - Response (implicit)Up: 3.8 - ModulesNext: 3.8.5 - Cookie
    +
    +3.8.4. Redirect

    + + +The redirect module allows requests to be redirected to different pages, by +providing the following methods: +

      +
    • internal( uri ):
      Performs an internal redirect. All +processing on the current page ends, the output buffer is cleared and +processing continues at the named uri. +The browser URI remains +unchanged, and does not realise that a redirect has even occurred during +processing.
    • +

    • external( uri, [permanent] ):
      Performs an external redirect +using the HTTP Location header to a new uri. Processing of the +current file continues unless you raise spyceDone, +but the content is ignored (ie. the buffer is +cleared at the end). The status of the document is set to 301 MOVED +PERMANENTLY or 302 MOVED TEMPORARILY, depending on the permanent +boolean parameter, which defaults to false or temporary. The redirect +document is sent to the browser, which requests the new relative uri. +
    • +

    • externalRefresh( uri, [seconds] ):
      Performs an external +redirect using the HTTP Refresh header a new uri. Processing of the +current file continues, and will be displayed on the browser as a regular +document. Unless interrupted by the user, the browser will request the new +URL after the specified number of seconds, which defaults to zero if +omitted. Many websites use this functionality to show some page, while a +file is being downloaded. To do this, one would show the page using Spyce, +and redirect with an externalRefresh to the download URI. Remember to set +the Content-Type on the target download file page +to be something that the browser can not display, only download.
    • +

    +The example below, shows the possible redirects in use:

    + +
    + examples/redirect.spy +
    + +
    [[.import name=redirect]]
    +<html><body>
    +  [[ type = request['type']
    +     url = request['url']
    +     if url and not type: {
    +       ]] 
    +       <font color=red><b>
    +         please select a redirect type
    +       </b></font><br> 
    +       [[
    +     }
    +     if type and url: {
    +       if type=='internal': redirect.internal(url)
    +       if type=='external': redirect.external(url)
    +       if type=='externalRefresh': redirect.externalRefresh(url, 3)
    +       ]] Received POST info: [[=request.post1()]] [[
    +     }
    +  ]]
    +  <form action="[[=request.uri('path')]]" method=post>
    +    Redirection url:
    +    <input type=text name=url value=hello.spy><br>
    +    Redirection type:
    +    <table border=0>
    +      <tr><td>
    +        <input type=radio name=type value=internal>
    +        internal
    +      </td></tr>
    +      <tr><td>
    +        <input type=radio name=type value=external>
    +        external
    +      </td></tr>
    +      <tr><td>
    +        <input type=radio name=type value=externalRefresh>
    +        externalRefresh (3 seconds)
    +      </td></tr>
    +    </table>
    +    <input type=submit value=redirect>
    +  </form>
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    +


    + + + + + +
    Prev: 3.8.3 - Response (implicit)Up: 3.8 - ModulesNext: 3.8.5 - Cookie
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_request.html b/spyce-2.1/www/demo-site/docs/doc-mod_request.html new file mode 100755 index 0000000000000000000000000000000000000000..a9af5b6b07fc723b4c727055231e02e73e72abe6 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_request.html @@ -0,0 +1,494 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.1 - DB (implicit)Up: 3.8 - ModulesNext: 3.8.3 - Response (implicit)
    +
    +3.8.2. Request (implicit)

    + + +The request module is loaded implicitly into every Spyce environment. +

    +The spyce configuration file gives two lists that affect the request module: +param_filters and file_filters. param_filters is a list of functions to run +against the GET and POST variables in the request; file filters is the same, +only for files uploaded. Each function will be passed the request module +and a dictionary when it is called; each will be called once for GET and once for POST +with each new request. +

    +These hooks exist because the request dictionaries should not be modified +in an ad-hoc manner; these allow you to set an application-wide policy +in a well-defined manner. You might, for instance, disallow all file uploads +over 1 MB. +

    +Here's an example that calls either Html.clean +or Html.escape (not shown) to ensure that no potentially harmful html can +be injected in user-editable areas of a site: + +
    + examples/filter.py +
    + + + +
    +def htmlFilter(request, d):
    +  # note that spoofing __htmlfields doesn't help attacker get unsafe html in;
    +  # we always call either clean() or escape().
    +  try:
    +    # don't use request['__htmlfields'], or you will recurse infinitely
    +    toClean = request._post['__htmlfields'][0].split(',')
    +  except KeyError:
    +    toClean = []
    +  for key in d:
    +    if key in toClean:
    +      d[key] = [Html.clean(s) for s in d[key]]
    +    else:
    +      d[key] = [Html.escape(s) for s in d[key]]
    +
    +
    + +
    +
    + +

    +The request module provides the following methods: +

      +
    • login_id:
      +Returns the id generated by your validator function if the user has logged in +via spy:login or spy:login_required, or None if the user is unvalidated. (See the +core tag library for details on +the login tags.) +
    • uri( [component] ):
      Returns the request URI, or some +component thereof. If the optional component parameter is specified, +it should be one of the following strings: +'scheme', +'location', +'path', +'parameters', +'query' or +'fragment'. +
    • +

    • method():
      Returns request method type (GET, POST, +...)
    • +

    • query():
      Returns the request query string
    • +

    • get( [name], [default], [ignoreCase] ):
      Returns request GET +information. If name is specified then a single list of values is +returned if the parameter exists, or default, which defaults to an +empty list, +if the parameter does not exist. Parameters without values are skipped, +though empty string values are allowed. If name is omitted, then a +dictionary of lists is returned. If ignoreCase is true, then the +above behaviour is performed in a case insensitive manner (all parameters +are treated as lowercase).
    • +

    • get1( [name], [default], [ignoreCase] ):
      Returns request GET +information, similarly to (though slightly differently from) the function +above. If name is specified then a single string is returned if the +parameter exists, or default, which default to None, if the parameter +does not exist. If there is more than one value for a parameter, then only +one is returned. Parameters without values are skipped, though empty string +values are allowed. If name is omitted, then a dictionary of strings is +returned. If the optional ignoreCase flag is true, then the above +behaviour is performed in a case insensitive manner (all parameters are +treated as lowercase).
    • +

    • post( [name], [default], [ignoreCase] ):
      Returns request +POST information. If name is specified then a single list of values +is returned if the parameter exists, or default, which defaults to +an empty list, if the parameter does not exist. Parameters without values are +skipped, though empty string values are allowed. If name is omitted, then a +dictionary of lists is returned. If ignoreCase is true, then the +above behaviour is performed in a case insensitive manner (all parameters +are treated as lowercase). This function understands form information +encoded either as 'application/x-www-form-urlencoded' or +'multipart/form-data'. Uploaded file parameters are not included in this +dictionary; they can be accessed via the file method.
    • +

    • post1( [name], [default], [ignoreCase] ):
      Returns request +POST information, similarly to (though slightly differently from) the +function above. If name is specified then a single string is returned +if the parameter exists, or default, which defaults to None, if the +parameter does not exist. If there is more than one value for a parameter, +then only one is returned. Parameters without values are skipped, though +empty string values are allowed. If name is omitted, then a dictionary of +strings is returned. If the optional ignoreCase flag is true, then +the above behaviour is performed in a case insensitive manner (all +parameters are treated as lowercase). This function understands form +information encoded either as 'application/x-www-form-urlencoded' or +'multipart/form-data'. Uploaded file parameters are not included in this +dictionary; they can be accessed via the file method.
    • +

    • file( [name], [ignoreCase] ):
      Returns files POSTed in the +request. If name is specified then a single cgi.FieldStorage class is +returned if such a file parameter exists, otherwise None. If name is +omitted, then a dictionary of file entries is returned. If the optional +ignoreCase flag is true, then the above behaviour is performed in a +case insensitive manner (all parameters are treated as lowercase). The +interesting fields of the FieldStorage class are:

      +

        +
      • name: the field name, if specified; otherwise None
      • +
      • filename: the filename, if specified; otherwise None; this is +the client-side filename, not the filename in which the content is stored +- a temporary file you don't deal with +
      • value: the value as a string; for file uploads, this +transparently reads the file every time you request the value +
      • file: the file(-like) object from which you can read the data; +None if the data is stored a simple string +
      • type: the content-type, or None if not specified +
      • type_options: dictionary of options specified on the +content-type line +
      • disposition: content-disposition, or None if not specified +
      • disposition_options: dictionary of corresponding options +
      • headers: a dictionary(-like) object (sometimes rfc822.Message +or a subclass thereof) containing *all* headers +

      +

    • __getitem__( key ):
      The request module can be used as a +dictionary: i.e. request['foo']. This method first calls the get1() method, +then the post1() method and lastly the file() method trying to find the +first non-None value to return. If no value is found, then this method +returns None. Note: Throwing an exception seemed too strong a semantics, and +so this is a break from Python. One can also iterate over the request +object, as if over a dictionary of field names in the get1 and post1 +dictionaries. In the case of overlap, the get1() dictionary takes +precedence.
    • +

    • getpost( [name], [default], [ignoreCase] ):
      Using given +parameters, return get() result if not None, otherwise return post() result +if not None, otherwise default.
    • +

    • getpost1( [name], [default], [ignoreCase] ):
      Using given +parameters, return get1() result if not None, otherwise return post1() +result if not None, otherwise default.
    • +

    • postget( [name], [default], [ignoreCase] ):
      Using given +parameters, return post() result if not None, otherwise return get() result +if not None, otherwise default.
    • +

    • postget1( [name], [default], [ignoreCase] ):
      Using given +parameters, return post1() result if not None, otherwise return get1() +result if not None, otherwise default.
    • +

    • env( [name], [default] ):
      Returns a dictionary with CGI-like +environment information of this request. If name is specified then a +single entry is returned if the parameter exists, otherwise default, +which defaults to None, if omitted.
    • +

    • getHeader( [type] ):
      Return a specific header sent by the +browser. If optional type is omitted, a dictionary of all headers is +returned.
    • +

    • filename( [path] ):
      Return the Spyce filename of the request +currently being processed. If an optional path parameter is provided, +then that path is made relative to the Spyce filename of the request +currently being processed.
    • +

    • stack( [i] ):
      Returns a stack of files processed by the +Spyce runtime. If i is provided, then a given frame is returned, +with negative numbers wrapping from the back as per Python convention. +The first (index zero) item on the stack is the filename +corresponding to the URL originally requested. The last (index -1) item +on the stack is the current filename being processed. Items are added +to the stack by includes, +Spyce lambdas, and +internal redirects.

      +

    • default( value, value2 ):
      (convenience method) Return +value if it is not None, otherwise return value2.
    • +

    +The example below presents the results of all the method calls list above. Run +it to understand the information available.

    + +
    + examples/request.spy +
    + +
    <html><body>
    +  Using the Spyce request object, we can obtain 
    +  information sent along with the request. The 
    +  table below shows some request methods and their 
    +  return values. Use the form below to post form 
    +  data via GET or POST. <br>
    +  <hr>
    +  [[-- input forms --]]
    +  <form action="[[=request.uri('path')]]" method=get>
    +    get: <input type=text name=name>
    +    <input type=submit value=ok>
    +  </form>
    +  <form action="[[=request.uri('path')]]" method=post>
    +    post: <input type=text name=name>
    +    <input type=submit value=ok>
    +  </form>
    +  <hr>
    +  [[-- tabulate response information --]]
    +  <table border=1>
    +    <tr>
    +      <td><b>Method</b></td>
    +      <td><b>Return value</b></td>
    +    </tr>
    +    [[ for method in ['uri()', 'uri("path")',
    +      'uri("query")', 'method()','query()',
    +      'get()','get1()', 'post()','post1()',
    +      'getHeader()','env()', 'filename()']: { 
    +    ]]
    +      <tr>
    +        <td valign=top>request.[[=method]]</td>
    +        <td>[[=eval('request.%s' % method)]]</td>
    +      </tr>
    +    [[ } ]]
    +  </table>
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    +Lastly, the following example shows how to deal with uploaded files.

    + +
    + examples/fileupload.spy +
    + +
    [[\ 
    +if request.post('ct'):
    +  response.setContentType(request.post1('ct'))
    +  if request.file('upfile')!=None:
    +    response.write(request.file('upfile').value)
    +  else:
    +    print 'file not properly uploaded'
    +  raise spyceDone
    +]]
    +<html><body>
    +  Upload a file and it will be sent back to you.<br>
    +  [[-- input forms --]]
    +  <hr>
    +  <table>
    +    <form action="[[=request.uri('path')]]" method=post 
    +        enctype="multipart/form-data">
    +      <tr>
    +        <td>file:</td>
    +        <td><input type=file name=upfile></td>
    +      </tr><tr>
    +        <td>content-type:</td>
    +        <td><input type=text name=ct value="text/html"></td>
    +      </tr><tr>
    +        <td><input type=submit value=ok></td>
    +      </tr>
    +    </form>
    +  </table>
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    +


    + + + + + +
    Prev: 3.8.1 - DB (implicit)Up: 3.8 - ModulesNext: 3.8.3 - Response (implicit)
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_response.html b/spyce-2.1/www/demo-site/docs/doc-mod_response.html new file mode 100755 index 0000000000000000000000000000000000000000..7799a5fcfa76c425187a63cd9be56e89b585b6c2 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_response.html @@ -0,0 +1,315 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.2 - Request (implicit)Up: 3.8 - ModulesNext: 3.8.4 - Redirect
    +
    +3.8.3. Response (implicit)

    + + +Like the request module, the response module is also loaded implicitly into every +Spyce environment. It provides the following methods: +

      +
    • write( string ):
      Sends a string to the client. All +writes are buffered by default and sent at the end of Spyce processing to +allow appending headers, setting cookies and exception handling. Note that +using the print statement is often easier, and +stdout is implicitly redirected +to the browser.
    • +

    • writeln( string ):
      Sends a string to the client, and +appends a newline.
    • +

    • writeStatic( string ):
      All static HTML strings are +emitted to the client via this method, which (by default) simply calls +write(). This method is not commonly invoked by the user.
    • +

    • writeExpr( object ):
      All expression results are emitted to +the client via this method, which (by default) calls write() with the str() +of the result object. This method is not commonly invoked by +the user.
    • +

    • clear( ): Clears the output buffer.
    • +

    • flush( ): Sends buffered output to the client immediately. This +is a blocking call, and can incur a performance hit.
    • +

    • setContentType( contentType ):
      Sets the MIME content +type of the response.
    • +

    • setReturnCode( code ):
      Set the HTTP return code for this +response. This return code may be overriden if an error occurs or by +functions in other modules (such as redirects).
    • +

    • addHeader( type, data, [replace] ):
      Adds the header line +"type: data" to the outgoing response. The +optional replace flag determines whether any previous headers of the +same type are first removed.
    • +

    • unbuffer():
      Turns off buffering on the output stream. In +other words, each write is followed by a flush(). An unbuffered output +stream should be used only when sending large amounts of data (ie. file +transfers) that would take up server memory unnecessarily, and involve +consistently large writes. Note that using an unbuffered response stream +will not allow the output to be cleared if an exception occurs. It will also +immediately send any headers.
    • +

    • isCancelled():
      Returns true if it has been detected that the +client is no longer connected. This flag will turn on, and remain on, after +the first client output failure. However, the detection is best-effort, and +may never turn on in certain configurations (such as CGI) due to buffering. +
    • +

    • timestamp( [t] ):
      Timestamps the response with an HTTP +Date: header, using the optional t +parameter, which may be either be the number of seconds since the epoch +(see Python time +module), or a properly formatted HTTP date string. If t is omitted, +the current time is used.
    • +

    • expires( [t] ):
      Sets the expiration time of the +response with an HTTP Expires: header, using the +optional t parameter, which may be either the number of seconds +since the epoch (see Python time +module), or a properly formatted HTTP date string. If t is omitted, +the current time is used.
    • +

    • expiresRel( [secs] ):
      Sets the expiration time of the +response relative to the current time with an HTTP Expires: header. The optional secs (which may +also be negative) indicates the number of seconds to add to the current time +to compute the expiration time. If secs is omitted, it defaults to zero. +
    • +

    • lastModified( [t] ):
      Sets the last modification time of +the response with an HTTP Last-Modified: header, +using the optional t parameter, which can be either the number +of seconds since the epoch (see Python time +module), or a properly formatted HTTP date string, or None indicating the +current time. If t is omitted, this function will default to the last +modification time of the Spyce file for this request, and raise an exception +if this time can not be determined. Note that, as per the HTTP +specification, you should not set a last modification time that is beyond +the response timestamp.
    • +

    • uncacheable():
      Sets the HTTP/1.1 Cache-Control: and HTTP/1.0 Pragma: headers to inform clients and proxies that this +content should not be cached.
    • +

    +The methods are self-explanatory. One of the more interesting things that one could do is +to emit non-HTML content types. The example below emits the Spyce logo as a GIF.

    + +
    + examples/gif.spy +
    + +
    [[.import name=include ]]
    +[[\
    +  # Spyce can also generate other content types
    +  # The following code displays the Spyce logo
    +  response.setContentType('image/gif')
    +  import os.path, spyce
    +  path = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'spyce.gif')
    +  response.write(include.dump(path, 1))
    +  raise spyceDone
    +]]
    +
    +
    +
    + + Run this code + +
    +

    +


    + + + + + +
    Prev: 3.8.2 - Request (implicit)Up: 3.8 - ModulesNext: 3.8.4 - Redirect
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_session.html b/spyce-2.1/www/demo-site/docs/doc-mod_session.html new file mode 100755 index 0000000000000000000000000000000000000000..0137a96453ae13b6e9c031033a1a6d4a2ccbaa0d --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_session.html @@ -0,0 +1,287 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.5 - CookieUp: 3.8 - ModulesNext: 3.8.7 - Pool
    +
    +3.8.6. Session

    + + +Sessions allow information to be efficiently passed from one user request to +the next via some browser mechanism: get, post or cookie. Potentially large or +sensitive information is stored at the server, and only a short identifier is +sent to the client to be returned on callback. Sessions are often used to +create sequences of stateful pages that represent an application or work-flow. +

    +This module automates sessioning for a Spyce web site. It emulates +a dictionary specific to each user (really each browser) accessing +your web site. You simply use session as if it were a dictionary +variable, and its contents automatically change depending upon the +user calling the page. For example: + +
    + examples/session2.spy +
    + +
    [[.import name=session ]]
    +[[\
    +  session['visited'] = session.get('visited', 0) + 1
    +]]
    +
    +<spy:parent title="Session example" />
    +
    +You visited us [[= session['visited'] ]] times.
    +
    +
    +
    + + Run this code + +
    + +

    +In the example above, the 'visited' key would now be valid for +all pages on your site, until the session expires. +

    +Global session options +

    +These options are configured only in the Spyce config file: +

      +
    • session_store: declares the backing storage for session +information. It should be either session.DbmStore(path) or session.MemoryStore(). In-memory sessions storage is +faster, but volatile, and does not work in multi-process server +configurations. Advanced users are welcome to create their own storage +manager, by subclassing session.SessionStore.
    • +
    +Advanced users can create their own storage manager by subclassing +session.SessionStore. +

    +Per-session options +

    +These options are set in the Spyce config file, but may be overridden +at module-import time on a per-page basis: +

      +
    • session_path: the default path to attach the session to. +This refers to +cookie semantics. For example, if the path is /myapp, the session +will only be valid under /myapp pages (and below). The default is +'/', which means the session is valid site-wide. +
    • session_expire: the number of seconds the session is good for. +The default is one day. +

      +You should clean up expired session state periodically. The easiest way +is to schedule session.clean_store every day or so in your config file: + +

      +import session, scheduler
      +scheduler.schedule_daily(0, 0, session.clean_store)
      +
      + +

      +(Note: for backwards compatibility, there is also a module called "session1." +New code should simply use the module described here.) +


      + + + + + +
      Prev: 3.8.5 - CookieUp: 3.8 - ModulesNext: 3.8.7 - Pool
      + +

      +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_stdout.html b/spyce-2.1/www/demo-site/docs/doc-mod_stdout.html new file mode 100755 index 0000000000000000000000000000000000000000..1285cc6cca1b1b239a70283416276f5bd6bd9739 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_stdout.html @@ -0,0 +1,260 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.11.1 - ErrorUp: 3.8.11 - Internal modulesNext: 3.8.11.3 - Spylambda
    +
    + +The stdout module is loaded implicitly and redirects Python's sys.stdout (in a thread-safe manner) to the appropriate +response object for the duration of Spyce processing. This allows one to use +print, without having to write print >> response, .... The stdout +module provides a variable stdout.stdout, which +refers to the original stream, but is unlikely to be needed. It may also be +useful to know that sys.stderr is, under many +configurations, connected to the webserver error log.

    +In addition, the stdout module provides the following functions for capturing +or redirecting output: +

      +
    • push( [filename] ):
      Begin capturing output. Namely, the current +output stream is pushed onto the stack and replaced with a memory buffer. An +optional filename may be associated with this operation (see pop() +method below).
    • +

    • pop():
      Close current output buffer, and return the captured +output as a string. If a filename was associated with the push(), then the +string will also be written to that file.
    • +

    • capture(f, [*args], [**kwargs] ):
      Push the current stream, +call the given function f with any supplied arguments *args +and keyword arguments **kwargs, and then pop it back. Capture returns +a tuple (r,s), where r is the result returned by f and s is a string of its +output.
    • +

    +The example below show how the module is used: + +
    + examples/stdout.spy +
    + +
    <html><body>
    +  [[ print '''Using the stdout module redirects 
    +    stdout to the response object, so you can use
    +    <b>print</b>!''']]<br>
    +  redirecting stdout can be used to...
    +  [[stdout.push()]]
    +  [[print 'capture']] out[[='put']]
    +  [[cached = stdout.pop()]]
    +  ... for later: <br>
    +  [[=cached]]
    +</body></html>
    +
    +
    +
    + + Run this code + +
    + +


    + + + + + +
    Prev: 3.8.11.1 - ErrorUp: 3.8.11 - Internal modulesNext: 3.8.11.3 - Spylambda
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_taglib.html b/spyce-2.1/www/demo-site/docs/doc-mod_taglib.html new file mode 100755 index 0000000000000000000000000000000000000000..cc41b1c5eec4d2b7956d94654587182e874856a1 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_taglib.html @@ -0,0 +1,253 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.11.3 - SpylambdaUp: 3.8.11 - Internal modulesNext: 3.8.12 - Writing Modules
    +
    + +The taglib module is loaded implicitly and supports +Active Tags +functionality. The taglib module provides the following +methods: +
      +
    • load( libname, [libfrom], [libas] ):
      Loads a tag library +class named libname from a file called libfrom in the search +path, and installed it under the tag prefix libas. The default for +libfrom is libname.py. The default for +libas is libname. Once installed, a library +name is its unique tag prefix.
    • +

    • unload( libname ):
      Unload a tag library that is installed +under the libname prefix. This is usually performed only at the end +of a request.
    • +

    • tagPush( libname, tagname, pair ):
      Push a new tag object for +a libname:tagname tag onto the tag stack. The pair +parameter is a flag indicating whether this is a singleton or a paired tag. +
    • +

    • tagPop():
      Pop the current tag from the tag stack.
    • +

    • getTag():
      Return the current tag object.
    • +

    • outPush():
      Begin capturing the current output stream. This +is usually called by the tagBegin method.
    • +

    • outPopCond():
      End capturing the current output stream, and +return the captured contents. It will only "pop" once, even if called +multiple times for the same tag. This method is usually called by either the +tagEnd(), tagCatch, or tagPop() methods.
    • +

    • tagBegin( attrs ):
      This method sets the tag output and +variable environment, and then calls the tag's begin() method with +the given attrs tag attribute dictionary. This method returns a flag, +and the tag body must be processed if and only if this flag is true.
    • +

      +

    • tagBody():
      This method sets the tag output and variable +environment, and then calls the tag's body() method with the captured +output of the body processing. If this method returns true, then the +processing of the body must be repeated.
    • +

    • tagEnd():
      This method sets the tag output and variable +environment, and then calls the tag's end() method. This method must +be called if the tagBegin() method completes successfully in order to +preserve tag semantics.
    • +

    • tagCatch():
      This method should be called if any of the +tagBegin, tagBody or tagEnd methods raise an exception. It calls the tag's +catch() method with the current exception.
    • +

    +
    + + + + + +
    Prev: 3.8.11.3 - SpylambdaUp: 3.8.11 - Internal modulesNext: 3.8.12 - Writing Modules
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-mod_transform.html b/spyce-2.1/www/demo-site/docs/doc-mod_transform.html new file mode 100755 index 0000000000000000000000000000000000000000..50f8ec3dd32ae01b89248356518bbedca57211eb --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-mod_transform.html @@ -0,0 +1,410 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Modules
    +
    + + + + + + + +
    Prev: 3.8.7 - PoolUp: 3.8 - ModulesNext: 3.8.9 - Compress
    +
    +3.8.8. Transform

    + + +The transform module contains useful text transformation functions, commonly +used during web-page generation.

    +

      +
    • html_encode( string, [also] ):
      Returns a HTML-encoded +string, with special characters replaced by entity references as +defined in the HTML 3.2 and 4 specifications. The optional also +parameter can be used to encode additional characters.
    • +

    • url_encode( string, ):
      Returns an URL-encoded string, +with special characters replaced with %XX equivalents as defined by the URI +RFC document.
    • +

    +The transform module also be used to intercept and insert intermediate +processing steps when response.writeStatic(), +response.writeExpr() and response.write() are called to emit +static html, expressions and dynamic content, respectively. It can be useful, +for example, to automatically ensure that expressions never produce output +that is HTML-unsafe, in other words strings that contain characters such as +&, < and >. Many interesting processing +functions can be defined. By default, the transform module leaves all output +untouched. These processing functions, called filters, can be inserted via the +following module functions:

    +

      +
    • static( [ fn ] ):
      Defines the processing performed on all +static HTML strings from this point forwards. The fn parameter is +explained below.
    • +

    • expr( [ fn ] ):
      Defines the processing performed on all the +results of all expression tags from this point forwards. The fn +parameter is explained below.
    • +

    • dynamic( [ fn ] ):
      Defines the processing performed on all +dynamic content generated, i.e. content generated using response.write in the +code tags. The fn parameter is explained below.
    • +

    +

    +Each of the functions above take a single, optional parameter, which specifies +the processing to be performed. The parameter can be one of the following +types: +

      +
    • None:
      If the paramter is None, or omitted, then no processing +is performed other converting the output to a string.
    • +

    • Function:
      If a parameter of function type is specified, then +that function is called to process the output. The function input can be any +Python type, and the function output may be any Python type. The result is +then converted into a string and emitted. The first parameter to a filter +will always be the object to be processed for output. However, the function +should be properly defined so as to possibly accept other parameters. The +details of how to define filters are explained below.
    • +

    • String:
      If a paramter of string type is specified, then the +string should be of the following format: "file:name", where file is the location where +the function is defined and name is the name of the filter. The file +component is optional, and is searched for using the standard module-finding +rules. If only the function name is specified, then the default location +(inside the transform module itself) is used, where the standard Spyce +filters reside. The standard Spyce filters are described below.
    • +

    • List / Tuple:
      If a parameter of list or tuple type is +specified, its elements should be functions, strings, lists or +tuples. The compound filter is recursively defined as +f=fn(...f2(f1())...), for the parameter +(f1,f2,...,fn). +
    • +

    +

    +Having explained how to install filters, we now list the standard Spyce +filters and show how they are used: +

      +
    • ignore_none( o ):
      Emits any input o except for None, +which is converted into an empty string.
    • +

    • truncate( o, [maxlen] ):
      If maxlen is specified, +then only the first maxlen characters of input o are returned, +otherwise the entire original.
    • +

    • html_encode( o, [also] ):
      Converts any '&', '<' and +'>' characters of input o into HTML entities for safe inclusion in +among HTML. The optional also parameter can specify, additional +characters that should be entity encoded.
    • +

    • url_encode( o ):
      Converts input o into a URL-encoded +string.
    • +

    • nb_space( o ):
      Replaces all spaces in input o with +"&nbsp;".
    • +

    • silence( o ):
      Outputs nothing.
    • +

    +

    +The optional parameters to some of these filters can be passed to the various +write functions as named parameters. They can also be specified in an +expression tag, as in the following example. (One should simply imagine that +the entire expression tag is replaced with a call to response.writeExpr). +
    + +
    [[.import name=transform]]
    +[[ transform.expr(("truncate", "html_encode")) ]]
    +[[='This is an unsafe (< > &) string... '*100, maxlen=500]] 
    +
    +

    +In the example above, the unsafe string is repeated 100 times. It is then +passed through a truncate filter that will accept +only the first 500 characters. It is then passed through the html_encode filter that will convert the unsafe +characters into their safe, equivalent HTML entities. The resulting string is +emitted.

    +The parameters (specified by their names) are simply accepted by the +appropriate write method (writeExpr() in the case above) and passed along to +the installed filter. Note that in the case of compound filters, the +parameters are passed to ALL the functions. The html_encode filter is +written to ignore the maxlen parameter, and does not fail.

    +For those who would like to write their own filters, looking at the definition +of the truncate filter will help. The other standard filters are in modules/transform.py. +
    + +
    def truncate(o, maxlen=None, **kwargs):
    +
    +

    +When writing a filter, any function will do, but it is strongly advised to +follow the model above. The important points are: +

      +
    • The input o can be of any type, not only a string.
    • +
    • The function result does not have to be string either. It is +automatically stringified at the end.
    • +
    • The function can accept parameters that modify its behaviour, such +as maxlen, above.
    • +
    • It is recommended to provide convenient user defaults for all +parameters.
    • +
    • The last parameter should be **kwargs so that unneeded parameters +are quietly passed along.
    • +
    +

    +Lastly, one can retrieve filters. This can be useful when creating new +functions that depend on existing filters, but can not be compounded using the +tuple syntax above. For example, one might use one filter or another +conditionally. For whatever purpose, the following module function is provided +to retreive standard Spyce filters, if needed:

    +

      +
    • create( [ fn ] ):
      Returns a filter. The fn parameter +can be of type None, function, string, list or tuple and is handled as in +the installation functions discussed above.
    • +

    +The transform module is flexible, but not complicated to use. The example +below is not examplary of typical use. Rather it highlights some of the +flexibility, so that users can think about creative uses.

    + +
    + examples/transform.spy +
    + +
    [[.import name=transform]]
    +[[\
    +def tag(o, tags=[], **kwargs):
    +  import string
    +  pre = string.join(map(lambda x: '<'+x+'>',tags))
    +  tags.reverse()
    +  post = string.join(map(lambda x: '</'+x+'>',tags))
    +  return pre+str(o)+post
    +def bold(o, _tag=tag, **kwargs):
    +  kwargs['tags'] = ['b']
    +  return apply(_tag, (o,), kwargs)
    +def bolditalic(o, _tag=tag, **kwargs):
    +  kwargs['tags'] = ['b','i']
    +  return apply(_tag, (o,), kwargs)
    +myfilter = transform.create(['html_encode', bolditalic])
    +mystring = 'bold and italic unsafe string: < > &'
    +def simpletable(o, **kwargs):
    +  s = '<table border=1>'
    +  for row in o:
    +    s=s+'<tr>'
    +    for cell in row:
    +      s=s+'<td>'+str(cell)+'</td>'
    +    s=s+'</tr>'
    +  s = s+'</table>'
    +  return s
    +]]
    +<html><body>
    +  install an expression filter:<br>
    +  [[transform.expr(['html_encode', tag])]]
    +  1.[[=mystring, tags=['b','i'] ]]
    +  <br>
    +  [[transform.expr(myfilter)]]
    +  2.[[=mystring]]
    +  [[transform.expr()]]
    +  <p>
    +  or use a filter directly:<br>
    +  1.[[=transform.create(['html_encode',tag])(mystring,tags=['b','i'])]]
    +  <br>
    +  2.[[=myfilter(mystring)]]
    +  <p>
    +  Formatting data in a table...<br>
    +  [[=simpletable([ [1,2,3], [4,5,6] ])]]
    +  <p>
    +  Though the transform module is flexible, <br>
    +  most users will probably only install the <br>
    +  <b>html_encode</b> filter.
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    +


    + + + + + +
    Prev: 3.8.7 - PoolUp: 3.8 - ModulesNext: 3.8.9 - Compress
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime.html b/spyce-2.1/www/demo-site/docs/doc-runtime.html new file mode 100755 index 0000000000000000000000000000000000000000..c55e396245d3bd392d50a98113761843cc46be06 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime.html @@ -0,0 +1,239 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 2.9 - ASP/JSP syntaxUp: Table of ContentsNext: 3.1 - Exceptions
    +
    +

    3. RUNTIME

    + + +Having covered the Spyce language syntax, we now move to describing the +runtime processing. Each time a request comes in, the cache of compiled Spyce +files is checked for the compiled version of the requisite Spyce file. If one +is not found, the Spyce file is quickly read, transformed, compiled and cached +for future use.

    +The compiled Spyce is initialized, then processed, then finalized. The +initialization consists of initializing all the Spyce modules. The Spyce file +is executed top-down, until the end is reached or an exception is thrown, +whichever comes first. The finalization step then finalizes each module in +reverse order of initialization, and any buffered output is automatically +flushed.

    + +


    + + + + + +
    Prev: 2.9 - ASP/JSP syntaxUp: Table of ContentsNext: 3.1 - Exceptions
    +

    +Sub-sections: +

    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_cmdline.html b/spyce-2.1/www/demo-site/docs/doc-runtime_cmdline.html new file mode 100755 index 0000000000000000000000000000000000000000..e7753d0b0307b1b614b7cc4776151fef2213c22d --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_cmdline.html @@ -0,0 +1,241 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.4 - Static ContentUp: 3 - RuntimeNext: 3.6 - Configuration
    +
    +3.5. Command line

    + + +The full command-line syntax is:

    + +
    + +
    Spyce 2.1
    +Command-line usage:
    +  spyce -c [-o filename.html] <filename.spy>
    +  spyce -w <filename.spy>                <-- CGI
    +  spyce -O filename(s).spy               <-- batch process
    +  spyce -l [-d file ]                    <-- proxy server
    +  spyce -h | -v
    +    -h, -?, --help       display this help information
    +    -v, --version        display version
    +    -o, --output         send output to given file
    +    -O                   send outputs of multiple files to *.html
    +    -c, --compile        compile only; do not execute
    +    -w, --web            cgi mode: emit headers (or use run_spyceCGI.py)
    +    -q, --query          set QUERY_STRING environment variable
    +    -l, --listen         run in HTTP server mode
    +    -d, --daemon         run as a daemon process with given pidfile
    +    --conf [file]        Spyce configuration file
    +To configure Apache, please refer to: spyceApache.conf
    +For more details, refer to the documentation.
    +  http://spyce.sourceforge.net
    +Send comments, suggestions and bug reports to <rimon-AT-acm.org>.
    +
    +
    +
    +

    +


    + + + + + +
    Prev: 3.4 - Static ContentUp: 3 - RuntimeNext: 3.6 - Configuration
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_common.html b/spyce-2.1/www/demo-site/docs/doc-runtime_common.html new file mode 100755 index 0000000000000000000000000000000000000000..2614cbb7d0ee803b795e536154c640bf7330773b --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_common.html @@ -0,0 +1,609 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.5 - Command lineUp: 3 - RuntimeNext: 3.7 - Server utilities
    +
    +3.6. Configuration

    + + +Since there are a variety of very different +installation +alternatives for the Spyce +engine, effort has been invested in consolidating all the various runtime +configuration options. By default, the Spyce engine will search for a file +called spyceconf.py in its installation directory. An alternative file +location may be specified via the --conf command-line option. +

    +The spyce configuration file is a valid python module; any python code +may be used. To avoid duplication, we recommend starting with +"from spyceconf import *" and +override only select settings. One thing you cannot do from the config module +is access the Spyce server object spyce.getServer(), +since it has not been initialized yet.

    +You may access the loaded configuration module from Spyce scripts and from +Python modules using the config attribute of the +server object. Or, simply as the spyceConfig module, +regardless of it actual file name. For example:

    + +
    + examples/config.spy +
    + +
    [[\
    +  import spyceConfig
    +  home = spyceConfig.SPYCE_HOME
    +]]
    +
    +[[= home ]]
    +
    +
    +
    + + Run this code + +
    +

    +Below is the configuration file that this server is running. The length of the +file is primarily due to the thoroughness of the comments:

    +
    + + + + +
    +# NOTE: do note write code that directly imports this module 
    +# (except when you are writing a custom configuration module.)
    +# This is a recipe for trouble, since spyce allows the user
    +# to specify the configuration module filename on the commandline.
    +# Instead, use import spyce; spyce.getServer().config.
    +
    +import os, sys
    +import spycePreload
    +
    +# Determine SPYCE_HOME dynamically.
    +# (you can hardcode SPYCE_HOME if you really want to, but it shouldn't be necessary.)
    +SPYCE_HOME = spycePreload.guessSpyceHome()
    +
    +# The spyce path determines which directories are searched for when
    +# loading modules (with [[.import]]) and tag libraries (with [[.taglib]]
    +# and the globaltags configuration later in this file.
    +#
    +# By default, the Spyce installation directory is always searched
    +# first. Any directories in the SPYCE_PATH environment are also
    +# searched.
    +#
    +# If you need to import from .py modules in nonstandard locations
    +# (i.e., not in your python installation's library directory),
    +# you will want to add their directories to sys.path as well, as
    +# done here for the error module.  (However, Spyce automagically
    +# changes sys.path dynamically so you will always be able to import
    +# modules in the same directory as your currently-processing .spy file.)
    +#
    +# path += ['/usr/spyce/inc/myapplication', '/var/myapp/lib']
    +path = [os.path.join(SPYCE_HOME, 'modules'), os.path.join(SPYCE_HOME, 'contrib', 'modules')]
    +path.append(os.path.join(SPYCE_HOME, 'tags'))
    +path.append(os.path.join(SPYCE_HOME, 'contrib', 'tags'))
    +if os.environ.has_key('SPYCE_PATH'):
    +    path += os.environ['SPYCE_PATH'].split(os.pathsep)
    +# provide originalsyspath so if someone wants to maintain a config file via
    +# "from spyceconf import *"
    +# he can remove the above modifications if desired.
    +originalsyspath = list(sys.path)
    +sys.path.extend(path)
    +
    +# The globaltags option specifies a group of tag libraries that will be autoloaded
    +# as if [[.taglib name=libname from=file as=prefix]] were specified in every .spy
    +# file.  (There is no performance hit if the library is not used on a page;
    +# the Spyce compiler optimizes it out.)
    +#
    +# The format is ('libname', 'file', 'prefix').
    +# For a 2.0-style tag library, the libname attribute is ignored.  Passing None is fine.
    +#
    +# globaltags.append(('mytag', 'mytaglib.py', 'my'))
    +# globaltags.append((None, 'taglib2.py', 'my2'))
    +globaltags = [
    +    ('core', 'core.py', 'spy'),
    +    ('form', 'form.py', 'f'),
    +    (None, 'render.spi', 'render'),
    +    ]
    +
    +# The default parent template is the one that is used by <spy:parent>
    +# if no src attribute is given, specified as an absolute url.
    +defaultparent = "/parent.spi"
    +
    +# The errorhandler option sets the server-level error handler.  These
    +# errors include spyce.spyceNotFound, spyce.spyceForbidden,
    +# spyce.spyceSyntaxError and spyce.pythonSyntaxError.  (file-level
    +# error handling is defined within Spyce scripts using the error module.)
    +#
    +# The server will call the error handler as errorhandler(request, response, error).
    +#
    +# Please look at the default function if you are considering writing your own
    +# server error handler.
    +import error
    +errorhandler = error.serverHandler
    +
    +# The pageerror option sets the default page-level error handler.
    +# "Page-level" means all runtime errors that occur during the
    +# processing of a Spyce script (i.e. after the compilation phase has
    +# completed successfully)
    +#
    +# The format of this option is one of:
    +#   ('string', 'MODULE', 'VARIABLE')
    +#   ('file', 'URL')
    +# (This format is used since the error template must be transformed into
    +# compiled spyce code, which can't be done before the configuration file
    +# is completely loaded.)
    +#
    +# Please refer to the default template to see how to define your own
    +# page-level error handlers.
    +#
    +# pageerrortemplate = ('file', '/error.spy')
    +pageerrortemplate = ('string', 'error', 'defaultErrorTemplate')
    +
    +# The cache option affects the underlying cache mechanism that the
    +# server uses to maintain compiled Spyce scripts. Currently, Spyce
    +# supports two cache handlers:
    +#
    +#   cache = 'memory'
    +#   OR
    +#   cache = 'file'
    +#   cachedir = '/tmp' # REQUIRED: directory in which to store compiled files
    +#
    +# Why store the cache in the filesystem instead of memory?  The main
    +# reason is if you are running under CGI or mod_python.  Under
    +# mod_python, Apache will kick off a number of separate spyce
    +# processes; each would have its own memory cache; using the
    +# filesystem avoids wasteful duplication.  (Pointing the cachedir to a
    +# ramdisk makes it almost as fast as a memory cache.)  Under CGI of
    +# course, the python process isn't persistent so file caching is the
    +# only option to avoid expensive recompilation with each request.
    +#
    +# If you are running multiple Spyce instances on the same machine,
    +# they cannot share the same cachedir.  Give each a different cachedir,
    +# or use the in-memory cache type.
    +cache = 'memory'
    +
    +# The check_mtime option affects the caching of compiled Spyce code.
    +# When True, Spyce will check file timestamps with each request and
    +# recompile if they have been modified.  Setting this to False can
    +# speed up a "production server" but you will have to restart the
    +# server and (if using a file cache) clear out the cachedir
    +# to have changes made in the spyce code take effect.
    +check_mtime = True
    +
    +# The debug option turns on a LOT of logging to stderr.
    +debug = False
    +
    +# The globals section defines server-wide constants. The hashtable is
    +# accessible as "pool" within any Spyce file (with the pool
    +# method loaded), or as self._api.getServerGlobals() within any Spyce
    +# module.
    +#
    +# globals = {'name': "My Website", 'four': 2+2}
    +globals = {}
    +
    +# You may wish to pre-load various Python modules during engine initialization.
    +# Once imported, they will be in the python module cache.
    +#
    +# (You may of course use normal imports at any time in this configuration script;
    +# however, imports specified here are run after the Spyce server is
    +# completely initialized, making it safe to access the server internals via
    +# import spyce; spyce.getServer()...)
    +#
    +# imports = ['myModule', 'myModule2']
    +imports = []
    +
    +# The root option defines the path from which Spyce requests are processed.
    +# I.e., when a request for http://yourserver/path/foo.spy arrives,
    +# Spyce looks for foo.spy in <root>/path/.
    +# 
    +# root = '/var/www/html'
    +root = os.path.join(SPYCE_HOME, 'www')
    +# feel free to comment this next line out if you don't have python modules (.py) in your web root
    +sys.path.append(root)
    +
    +# some parts of spyce may need to create temporary files; usually the default is fine.
    +# BUT if you do override this, be sure to also override other parts of the config
    +# that reference it.  (currently just session_store)
    +import tempfile
    +tmp = tempfile.gettempdir()
    +
    +# active tag to render form validation errors
    +validation_render = 'render:validation'
    +
    +
    +#####
    +# database connection
    +#####
    +
    +from sqlalchemy.ext.sqlsoup import SqlSoup
    +
    +# Examples:
    +# db = SqlSoup('postgres://user:pass@localhost/dbname')
    +# db = SqlSoup('sqlite:///my.db')
    +# db = SqlSoup('mysql://user:pass@localhost/dbname')
    +#
    +# SqlSoup takes the same URLs as an SqlAlchemy Engine.  See 
    +# http://www.sqlalchemy.org/docs/dbengine.myt#dbengine_establishing
    +# for more examples.
    +try:
    +  db = SqlSoup('sqlite:///www/demos/to-do/todo.db')
    +except:
    +  db = None
    +
    +#####
    +# session options -- see docs/mod_session.html for details
    +#####
    +
    +import session
    +
    +session_store = session.DbmStore(tmp)
    +# session_store = session.MemoryStore()
    +
    +session_path = '/'
    +
    +session_expire = 24 * 60 * 60 # seconds
    +
    +#####
    +# login options
    +#####
    +
    +# The spyce login system uses the session storage
    +# defined above; a key called _spy_login will be added to each session.
    +
    +# validators must be a function that takes login and password as
    +# arguments, and returns a pickle-able object (usually int or string)
    +# representing the ID of the logged in user, or None if login fails.
    +#
    +# You'll need to supply your own to hook into your database or other
    +# validation system; see the pyweboff config.py for an example of doing
    +# this.
    +def nevervalidator(login, password):
    +        return None
    +
    +def testvalidator(login, password):
    +    if login == 'spyce' and password == 'spyce':
    +        return 2
    +    return None
    +
    +login_defaultvalidator = testvalidator
    +
    +# How to store login tokens.  FileStorage comes with Spyce,
    +# but it's easy to create your own Storage class if you want to put them
    +# in your database, for instance.
    +from _coreutil import FileStorage
    +# It's not a good idea to put login-tokens off of www/ in production!
    +# It's just done this way here to keep the spyce source tree relatively clean.
    +login_storage = FileStorage(os.path.join(SPYCE_HOME, 'www', 'login-tokens'))
    +
    +# tags to render login form; must be visible in the globaltags search space.
    +# These come from tags/render.spi; to make your own, just create a tag 
    +# that takes the same parameters and add the library to the globaltags list above.
    +login_render = 'render:login'
    +loginrequired_render = 'render:login_required'
    +
    +######
    +# webserver options -- does not affect mod_python or *CGI configurations
    +######
    +
    +# indexFiles specifies a list of files to look for
    +# in a directory if directory itself is requested.
    +# The first matching file will be selected.
    +# If empty, a directory listing will be served instead.
    +#
    +# indexFiles = ['index.spy', 'index.html', 'index.txt']
    +indexFiles = ['index.spy', 'index.html']
    +
    +# The Spyce webserver uses a threaded concurrency model.  (Historically,
    +# it also offered "no concurrency" and forking.  If for some strange
    +# reason you really want no concurrency, set minthreads=maxthreads=1.
    +# Forking was removed entirely because it performed over 10x slower
    +# than threading on Linux and Windows.)
    +#
    +# Do note that because of the Python GIL (global interpeter lock),
    +# only one CPU of a multi-CPU machine can execute Python code at a time.
    +# If this is your situation, mod_python may be a better option for you.
    +minthreads = 5
    +maxthreads = 10
    +# number of pending requests to accept
    +maxqueuesize = 50
    +
    +# Restart the webserver if a python module changes.
    +# Spyce does this by running the "real" server in a subprocess; when that
    +# server detects changed modules, it exits and Spyce starts another one.
    +#
    +# Spyce will check for changes every second, or when a request
    +# is made, whichever comes first.
    +#
    +# It's highly recommended to turn this off for "production" servers,
    +# since checking each module for each request is a performance hit.
    +check_modules_and_restart = True
    +
    +# The ipaddr option defines which IP addresses the server will listen on.
    +# empty string for all.
    +ipaddr = ''
    +
    +# The port option defines which TCP port the server will listen on.
    +port = 8000
    +# Port that provides an interactive Python console interface to
    +# the webserver's guts.  Currently no password protection is offered;
    +# don't expose this to the outside world!
    +adminport = None
    +
    +# The mime option is a list of filenames. The files should
    +# be definitions of mime-types for common file extensions in the
    +# standard Apache format.
    +#
    +# mime: ['/etc/mime.types']
    +mime = [os.path.join(SPYCE_HOME, 'spyce.mime')]
    +
    +# The www_handlers option defines the hander used for files of
    +# arbitrary extensions.  (The None key specifies the default.)
    +# The currently supported handlers are:
    +#   spyce     - process the file at the requested path as a spyce script
    +#   directory - display directory listing
    +#   dump      - transfer the file at the requested path verbatim, 
    +#     providing an appropriate "Content-type" header, if it is known.
    +# (It's difficult to use the actual instance methods here since we
    +# don't have a handle to the WWW server object.  So, we use strings
    +# and let spyceWWW eval them later.)
    +www_handlers = {
    +  'spy': 'spyce',
    +  '/':   'directory',
    +  None:  'dump'
    +}
    +
    +
    +######
    +# (F)CGI options
    +######
    +
    +# Forbid direct requests to the cgi script and discard command line arguments.
    +#
    +# Reason: http://www.cert.org/advisories/CA-1996-11.html
    +#
    +# You may need to disable this for
    +#  1) non-Apache servers that do not set the REDIRECT_STATUS environment
    +#     variable.
    +#  2) when using the alternative #! variant of CGI configuration
    +cgi_allow_only_redirect = False
    +
    +
    +######
    +# standard module customization
    +######
    +
    +# (request module)
    +
    +# param filters and file filters are lists of functions that are called
    +# for all GET and POST parameters or files, respectively.
    +# Each callable should expect two arguments: a reference to the
    +# request object/spyce module, and the dictionary being filtered.
    +# (Thus, each callable in param_filters will be called twice; once for the
    +# GET dict and once for POST. Each callable in file_filters will only be called once.)
    +param_filters = []
    +file_filters = []
    +
    +
    + +
    +

    +


    + + + + + +
    Prev: 3.5 - Command lineUp: 3 - RuntimeNext: 3.7 - Server utilities
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_except.html b/spyce-2.1/www/demo-site/docs/doc-runtime_except.html new file mode 100755 index 0000000000000000000000000000000000000000..ac362189815552c0f76a70c40d41dee7d066ccca --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_except.html @@ -0,0 +1,262 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3 - RuntimeUp: 3 - RuntimeNext: 3.2 - Code Transformation
    +
    +3.1. Exceptions

    + + +The Spyce file is executed top-down, until the end of the file is reached, a +valued is returned, or an exception is thrown, +whichever comes first. If the code terminates via an unhandled exception, then +it is caught by the Spyce engine. Depending on the exception type, different +actions are taken: +

      +
    • spyceDone can be raised at any time to stop the Spyce processing +(without error) at that point. It is often used to stop further output, as +in the example below that emits a binary image file. The spyceDone +exception, however, is more useful for modules writers. In regular Spyce +code one could simply issue a return statement, +with the same effect.
    • +

    • spyceRedirect is used by the +redirect module. It causes the +Spyce engine to immediately redirect the request to another Spyce file +internally. Internally means that we do not send back a redirect to +the browser, but merely clear the output buffer and start processing a new +script.
    • +

    • All other exceptions that occur at runtime will be processed via +the Spyce error module. This +module will emit a default error message, unless the user has installed some +other error handler.
    • +

    +Note that non-runtime exceptions, such as exceptions caused by compile errors, +missing files, access restrictions and the like, are handled by the server. +The default server error handler +can be configured via the server configuration file.

    + +
    + examples/gif.spy +
    + +
    [[.import name=include ]]
    +[[\
    +  # Spyce can also generate other content types
    +  # The following code displays the Spyce logo
    +  response.setContentType('image/gif')
    +  import os.path, spyce
    +  path = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'spyce.gif')
    +  response.write(include.dump(path, 1))
    +  raise spyceDone
    +]]
    +
    +
    +
    + + Run this code + +
    +

    +


    + + + + + +
    Prev: 3 - RuntimeUp: 3 - RuntimeNext: 3.2 - Code Transformation
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_prog.html b/spyce-2.1/www/demo-site/docs/doc-runtime_prog.html new file mode 100755 index 0000000000000000000000000000000000000000..1d6c5f01752398c889e5f23a82c17e8be3200136 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_prog.html @@ -0,0 +1,235 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.10.8 - Starting your first projectUp: 3 - RuntimeNext: 3.11.1 - Basic usage
    +
    +3.11. Programmatic Interface

    + + +It is also possible to embed Spyce into another program. All you need is to +run or embed + a Python interpreter. Although other entry points into the engine code +are possible, the most convenient entry points are in spyce.py: +

      +
    • spyceFileHandler( request, response, filename, [sig], [args], [kwargs], [config] )
      +
    • +

    • spyceStringHandler( request, response, code, [sig], [args], [kwargs], [config] )
      +
    • +

    +Either of these functions will execute some Spyce code within the context of some request object and send the output to the corresponding response object. spyceFileHandler gets the Spyce code to be executed from a file, while spyceStringHandler is passed Spyce code in a string. + + + + +


    + + + + + +
    Prev: 3.10.8 - Starting your first projectUp: 3 - RuntimeNext: 3.11.1 - Basic usage
    +

    +Sub-sections: +

    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_prog_basicUsage.html b/spyce-2.1/www/demo-site/docs/doc-runtime_prog_basicUsage.html new file mode 100755 index 0000000000000000000000000000000000000000..cac17afc8101e100a93cafd2926dc8da7575114a --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_prog_basicUsage.html @@ -0,0 +1,231 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.11 - Programmatic InterfaceUp: 3.11 - Programmatic InterfaceNext: 3.11.1 - Passing parameters
    +
    +3.11.1. Basic usage

    + + +For basic usage, the following arguments need to be understood: +

      +
    • request
      +is an object derived from spyce.spyceRequest, denoting the current HTTP request. +
    • +

    • response
      +is an object derived from spyce.spyceResponse, denoting the HTTP response resulting from the invocation. +
    • +

    • filename
      +is the name of a file which contains spyce source code (it will be read, compiled, cached and executed when spyceFileHandler is called). +
    • +

    • code
      +is a string which contains spyce source code (it will be executed when spyceStringHandler is called).
      +TODO: how is caching handled in this case? +
    • +

    • config
      +is an object which contains the configuration to be used. The normal usage is to have a configuration file which can be imported as a normal python module. The imported module then passed as conf. +
    • +

    +


    + + + + + +
    Prev: 3.11 - Programmatic InterfaceUp: 3.11 - Programmatic InterfaceNext: 3.11.1 - Passing parameters
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_prog_example.html b/spyce-2.1/www/demo-site/docs/doc-runtime_prog_example.html new file mode 100755 index 0000000000000000000000000000000000000000..5519e750150d2bfebd290e412b3a5276c2693a38 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_prog_example.html @@ -0,0 +1,311 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.11.1 - Customized Request/Response classesUp: 3.11 - Programmatic InterfaceNext: 4 - Addenda
    +
    +3.11.1. Example

    + + +Here's an example that demonstrates such programmatic usage:

    + +
    + examples/programmaticUsage.py +
    + + + +
    +import os
    +import spyce
    +
    +#--------------------------------------------------[ a hardcoded fake request ]
    +class TestRequest(spyce.spyceRequest):
    +    environment = {'QUERY_STRING': '',
    +            'REQUEST_METHOD': 'get'}
    +    headers = {}
    +    
    +    def env(self, name=None):
    +        if not name:
    +            return self.environment
    +        return self.environment[name]
    +    
    +    def getHeader(self, type=None):
    +        return self.headers[type]
    +    
    +    def getServerID(self):
    +        os.getpid()
    +
    +
    +#--------------------------------------------------[ a hardcoded fake response ]
    +class TestResponse(spyce.spyceResponse):
    +    returncode = spyce.spyceResponse.RETURN_OK
    +    out = ''
    +    err = ''
    +    headers = {}
    +    def write(self, s):
    +        self.out = self.out+s
    +    def writeErr(self, s):
    +        self.err = self.err+s
    +    def close(self):
    +        pass
    +    def clear(self):
    +        self.out = ''
    +    def sendHeaders(self):
    +        pass
    +    def clearHeaders(self):
    +        self.headers = {}
    +    def setContentType(self, content_type):
    +        self.headers['content-type'] = content_type
    +    def setReturnCode(self, code):
    +        self.returncode = code
    +    def addHeader(self, type, data, replace=0):
    +        self.headers[type] = data
    +    def flush(self, *args):
    +        pass
    +    def unbuffer(self):
    +        pass
    +
    +
    +#--------------------------------------------------[ invoking Spyce code ]
    +import spyceConfig
    +    
    +req = TestRequest()
    +resp = TestResponse()
    +
    +spyceCode  = 'Hello, the following names are defined: "[[print dir(),]]", and '
    +spyceCode += 'these were the parameters passed: [[print (a, b, c, d)]]\n',
    +
    +spyce.spyceStringHandler(
    +             req,
    +             resp,
    +             spyceCode,
    +             sig='a, b, c="asd", d=None',
    +             args=(1, 'two'),
    +             kwargs={'c': 'aa'},
    +             config=spyceConfig)
    +print resp.err
    +print resp.out
    +
    +
    + +
    +
    + +


    + + + + + +
    Prev: 3.11.1 - Customized Request/Response classesUp: 3.11 - Programmatic InterfaceNext: 4 - Addenda
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_prog_passingParameters.html b/spyce-2.1/www/demo-site/docs/doc-runtime_prog_passingParameters.html new file mode 100755 index 0000000000000000000000000000000000000000..bf57ddea958d75b634076131039a0e8ddc869861 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_prog_passingParameters.html @@ -0,0 +1,226 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.11.1 - Basic usageUp: 3.11 - Programmatic InterfaceNext: 3.11.1 - Customized Request/Response classes
    +
    +3.11.1. Passing parameters

    + + +One may wish to also pass a parameter from the calling code into the name space used inside the Spyce code so that it may be accessed from Python Statements, Chunks, Expressions, etcetera. Something analogous to Python's own execfile built-in function where you can pass a locals and globals dictionary.

    +Spyce code to be invoked is dealt with similar to a function body, and hence one can send parameters to that "function" upon invocation. The arguments sig, args and kwargs are used to control such parameter passing. With sig, a signature for this external code is specified, with args and kwargs values for the actual arguments are passed. Thus, sig controls what can go into args and kwargs: +

      +
    • sig
      +is a string containing a function signature in usual python syntax, without surrounding brackets.
      +For example: 'x, y, a=None, b={}'. +
    • +

    • args
      +is a list of values, each value corresponding to a positional argument specified in sig, in order. +
    • +

    • kwargs
      +is a dictionary of name to value mappings, its keys should be in the list of keyword argument names specified in sig, its values provide actual values to be passed. +
    • +

    +


    + + + + + +
    Prev: 3.11.1 - Basic usageUp: 3.11 - Programmatic InterfaceNext: 3.11.1 - Customized Request/Response classes
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_prog_requestAndResponse.html b/spyce-2.1/www/demo-site/docs/doc-runtime_prog_requestAndResponse.html new file mode 100755 index 0000000000000000000000000000000000000000..6bacbbd1578c684f286a26ab71b7d71b81b26c76 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_prog_requestAndResponse.html @@ -0,0 +1,213 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.11.1 - Passing parametersUp: 3.11 - Programmatic InterfaceNext: 3.11.1 - Example
    +
    +3.11.1. Customized Request/Response classes

    + + +explanation forthcoming; read the code for now, or send an email +


    + + + + + +
    Prev: 3.11.1 - Passing parametersUp: 3.11 - Programmatic InterfaceNext: 3.11.1 - Example
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_static.html b/spyce-2.1/www/demo-site/docs/doc-runtime_static.html new file mode 100755 index 0000000000000000000000000000000000000000..f3de83225c3fd78f7ba06928f84918682abee222 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_static.html @@ -0,0 +1,236 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.3 - Dynamic ContentUp: 3 - RuntimeNext: 3.5 - Command line
    +
    +3.4. Static Content

    + + +A nice feature of Spyce is that it can be invoked both from within a web +server to process a web request dynamically and also from the command-line. +The processing engine itself is the same in both cases. The command-line +option is actually just a modified CGI client, and is often used to +pre-process static content, such as this manual.

    +Some remarks regarding command-line execution specifics are in order. The +request and response objects for a command-line request are connected to +standard input and output, as expected. A minimal CGI-like environment is +created among the other shell environment variables. Header and cookie lookups +will return None and the engine will accept input on stdin for POST +information, if requested. There is also no compiler cache, since the process +memory is lost at the end of every execution.

    +Most commonly, Spyce is invoked from the command-line to generate static .html +ouput. Spyce then becomes a rather handy and powerful .html preprocessing +tool. It was used on this documentation to produce the consistent headers and +footers, to include and highlight the example code snippets, etc...

    +The following makefile rule comes in handy:

    + +
    +
    +  %.html: %.spy
    +    spyce -o $@ $<
    +
    +

    +


    + + + + + +
    Prev: 3.3 - Dynamic ContentUp: 3 - RuntimeNext: 3.5 - Command line
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_transform.html b/spyce-2.1/www/demo-site/docs/doc-runtime_transform.html new file mode 100755 index 0000000000000000000000000000000000000000..7151d81fe38b4a88b178e0d5e1acdaf6587801a2 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_transform.html @@ -0,0 +1,239 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.1 - ExceptionsUp: 3 - RuntimeNext: 3.3 - Dynamic Content
    +
    +3.2. Code Transformation

    + + +While the minutia of the code transformation that produces Python code from +the Spyce sources is of no interest to the casual user, it has some slight, +but important, ramifications on certain aspects of the Python language +semantics when used inside a Spyce file.

    +The result of the Spyce compilation is some Python code, wherein the majority +of the Spyce code actually resides in a single function called +spyceProcess. If you are curious to see the result of a Spyce +compilation, execute: "spyce -c".

    +It follows from the compilation transformation that: +

      +
    • Any functions defined within the Spyce file are actually nested +functions within the spyceProcess function.
    • +

    • The use of global variables within Spyce code is not supported, +but also not needed. If nested scoping is available (Python versions +>2.1) then these variables will simply be available. If not, then you +will need to pass variables into functions as default parameters, and will +not be able to update them by value (standard Python limitations). It is +good practice to store constants and other globals in a single class, or to +to place them in a single, included file, or both.
    • +

    • The global Spyce namespace is reserved for special variables, such as +Spyce and Python modules. While the use of the keyword global is not explicitly checked, it will pollute this +space and may result in unexpected behaviour or runtime errors.

    • +

    • The lifetime of variables is the duration of a request. Variables with +lifetimes longer than a single request can be stored using the pool module.
    • +

    +


    + + + + + +
    Prev: 3.1 - ExceptionsUp: 3 - RuntimeNext: 3.3 - Dynamic Content
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_util.html b/spyce-2.1/www/demo-site/docs/doc-runtime_util.html new file mode 100755 index 0000000000000000000000000000000000000000..e9708fcf2a31b22ea0e3a11353008f0ef318b8b1 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_util.html @@ -0,0 +1,221 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.6 - ConfigurationUp: 3 - RuntimeNext: 3.7.1 - The Spyce scheduler
    +
    +3.7. Server utilities

    + + +Like any application server, the Spyce server provides several facilities +that can aid development. + +


    + + + + + +
    Prev: 3.6 - ConfigurationUp: 3 - RuntimeNext: 3.7.1 - The Spyce scheduler
    +

    +Sub-sections: +

    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_util_scheduler.html b/spyce-2.1/www/demo-site/docs/doc-runtime_util_scheduler.html new file mode 100755 index 0000000000000000000000000000000000000000..3f6733335c27e39869bdb23c312d8fe63a1a0c69 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_util_scheduler.html @@ -0,0 +1,354 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.7 - Server utilitiesUp: 3.7 - Server utilitiesNext: 3.7.2 - spyceUtil
    +
    +3.7.1. The Spyce scheduler

    + + +Spyce provides a scheduler that allows you to easily define tasks to +run at specified times or intervals inside the Spyce server. This +allows your tasks to leverage the tools Spyce gives you, as well as +any global data your application maintains within Spyce, such as +cached data or database connection pools. This also has the advantage +(over, say, crontab entries) of keeping your application self-contained, +making it easier to deploy changes from a development machine to production. +

    +The Spyce scheduler is currently only useful if you are running +in webserver mode. If you run under mod_python, CGI, or FastCGI, +you could approximate scheduler behavior by storing tasks and checking +to see if any are overdue with every request received; this would +be an excellent project for someone wishing to get started in Spyce +development. +

    + +

    + + + + +
     
    + 
    scheduler
    index
    y:\develop\pcwprojects\pcwpdfconvert\spyce-2.1\scheduler.py
    +

    A module for scheduling arbitrary callables to run at given times
    +or intervals, modeled on the naviserver API.  Scheduler runs in
    +its own thread; callables run in this same thread, so if you have
    +an unusually long callable to run you may wish to give it its own
    +thread, for instance,

    +schedule(3600, lambda: threading.Thread(target=longcallable).start())

    +Scheduler does not provide subsecond resolution.

    +Public functions are threadsafe.

    +

    + + + + + +
     
    +Modules
           
    atexit
    +sys
    +
    threading
    +time
    +
    traceback
    +

    + + + + + +
     
    +Classes
           
    +
    Task +
    +

    + + + + + + + +
     
    +class Task
       Instantiated by the schedule methods.

    +Instance variables:
    +  nextrun: epoch seconds at which to run next
    +  interval: seconds before repeating
    +  callable: function to invoke
    +  last: if True, will be unscheduled after nextrun

    +(Note that by manually setting last on a Task instance, you
    +can cause it to run an arbitrary number of times.)
     
     Methods defined here:
    +
    __init__(self, firstrun, interval, callable, once)
    + +
    __repr__(self)
    + +

    + + + + + +
     
    +Functions
           
    debuglogger(s)
    +
    logger lambda s
    +
    pause()
    Temporarily suspend running scheduled tasks
    +
    schedule(interval, callable, once=False)
    Schedules callable to be run every interval seconds.
    +Returns the scheduled Task object.
    +
    schedule_daily(hours, minutes, callable, once=False)
    Schedules callable to be run at hours:minutes every day.
    +(Hours is a 24-hour format.)
    +Returns the scheduled Task object.
    +
    schedule_weekly(day, hours, minutes, callable, once=False)
    Schedules callable to be run at hours:minutes on the given
    +zero-based day of the week.  (Monday is 0.)
    +Returns the scheduled Task object.
    +
    unpause()
    Resume running scheduled tasks.  If a task came due while
    +it was paused, it will run immediately after unpausing.
    +
    unschedule(task)
    Removes the given task from the scheduling queue.
    +
    +

    + + +
    + examples/scheduling.py +
    + + + +
    +import spyce, scheduler
    +
    +def delete_unsubmitted():
    +    db = spyce.SPYCE_GLOBALS['dbpool'].connection()
    +    sql = "DELETE FROM alerts WHERE status = 'unsubmitted' AND created < now() - '1 week'::interval"
    +    db.execute(sql)
    +
    +# delete alerts that were created over a week ago but but still not submitted 
    +scheduler.schedule_daily(00, 10, delete_unsubmitted)
    +
    +
    + +
    +
    + +
    + + + + + +
    Prev: 3.7 - Server utilitiesUp: 3.7 - Server utilitiesNext: 3.7.2 - spyceUtil
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_util_su.html b/spyce-2.1/www/demo-site/docs/doc-runtime_util_su.html new file mode 100755 index 0000000000000000000000000000000000000000..68b5a5e565d4ee24ee147c5b7299da6e3a13072e --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_util_su.html @@ -0,0 +1,225 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.7.1 - The Spyce schedulerUp: 3.7 - Server utilitiesNext: 3.8 - Modules
    +
    +3.7.2. spyceUtil

    + + +Most of the spyceUtil module is interesting only to internal operations, +but several functions are more generally applicable: +

      +
    • url2file( url, relativeto=None ) +
      Returns the filesystem path of the file represented by url, relative +to a given path. For example, url2file('/index.spy') or +url2file('img/header.png', request.filename()). +
    • +

    • exceptionString( ) +
      Every python programmer writes this eventually: returns a string containing +the description and stacktrace for the most recent exception. +
    • +

    +


    + + + + + +
    Prev: 3.7.1 - The Spyce schedulerUp: 3.7 - Server utilitiesNext: 3.8 - Modules
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-runtime_web.html b/spyce-2.1/www/demo-site/docs/doc-runtime_web.html new file mode 100755 index 0000000000000000000000000000000000000000..7604693e7b4df662cc960639643c9e3f5b4a4d70 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-runtime_web.html @@ -0,0 +1,222 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Runtime
    +
    + + + + + + + +
    Prev: 3.2 - Code TransformationUp: 3 - RuntimeNext: 3.4 - Static Content
    +
    +3.3. Dynamic Content

    + + +The most common use of Spyce is to serve dynamic HTML content, but it should +be noted that Spyce can be used as a general purpose text engine. It can be +used to generate XML, text and other output, as easily as HTML. In fact, the +engine can also be used to generate dynamic binary data, such as images, PDF +files, etc., if needed.

    +The Spyce engine can be installed +in a number of different configurations that can produce dynamic output. +Proxy server, mod_python, and FastCGI exhibit high performance; the CGI approach is +slower, since a new engine must be created for each request. See the +configuration section for details. +


    + + + + + +
    Prev: 3.2 - Code TransformationUp: 3 - RuntimeNext: 3.4 - Static Content
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-single.html b/spyce-2.1/www/demo-site/docs/doc-single.html new file mode 100755 index 0000000000000000000000000000000000000000..400aa86b58d9ee64f53e1538b1e125849bd627b5 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-single.html @@ -0,0 +1,5118 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation
    +
    + + + + + + + + +
    +Spyce - Python Server Pages (PSP)
    +User Documentation

    +Release 2.1

    +

    +
    + [ Multi-Page Format ] +
    +

    +TABLE OF CONTENTS
    +

    + +
    + +

    1. INTRODUCTION

    + + +This document aims to be the authoritative source of +information about Spyce, usable as a comprehensive refence, a user guide and a +tutorial. It should be at least skimmed from beginning to end, +so you have at least an idea of the functionality available and can refer +back for more details as needed.

    + +Spyce is a server-side language that supports elegant and +efficient Python-based dynamic HTML generation. +Spyce allows embedding Python in pages similar to how JSP embeds Java, +but Spyce is far more than a JSP clone. Out of the box, Spyce provides +development as rapid as other modern frameworks like Rails, but with an +cohesive design rather than a morass of special cases. + +

    +Spyce's modular design makes it very flexible +and extensible. It can also be used as a command-line utility for static text +pre-processing or as a web-server proxy. +

    +Spyce's performance is comparable to the +other solutions in its class.

    +Note: This manual assumes a knowledge of Python and focusses +exclusively on Spyce. If you do not already know Python, it is easy to +learn via this short tutorial, and has +extensive documentation.

    +1.1. Rationale / competitive analysis

    + + +This section is somewhat dated. We plan to update it soon. +

    +A natural question to ask is why one would choose Spyce over JSP, ASP, PHP, +or any of the other HTML scripting languages that +perform a similar function. We compare Spyce with an array of exising tools: +

      +
    • Java Server Pages, JSP, is a widely popular, effective and +well-supported solution based on Java Servlet technology. Spyce differs from +JSP in that it embeds Python code among the HTML, thus providing a number of +advantages over Java. +
        +
      • Python is a high-level scripting language, +where rapid prototyping is syntactically easier to perform. +
      • There is no need for a separate "expression langauge" in Spyce; +Python is well-suited for both larger modules and active tag scripting. +
      • Python +is interpreted and latently typed, which can be advantageous for +prototyping, especially in avoiding unnecessary binary incompatibility of +classes for minor changes. +
      • Spyce code is of first-order in the Spyce +language, unlike JSP, which allows you to create useful Spyce lambda +functions. +
      • Creating new active tags and modules is simpler in +Spyce than in JSP. +
      • Spyce is better-integrated than JSP; to get similar functionality +in JSP, you have to add JSF (Java Server Faces) and Tiles, or +equivalents. +
      +

      +

    • +
    • PHP is another popular webserver module for dynamic +content generation. The PHP interpreter engine and the language itself were +explicitly designed for the task of dynamic HTML generation, while Python is +a general-purpose scripting language. +
        +
      • Spyce leverages from the extensive +development effort in Python: since any Python library can be imported and +reused, Spyce does not need to rebuild many of the core function libraries +that have been implemented by the PHP project. +
      • Use of Python +often simplifies integration of Spyce with existing system environments. +
      • Spyce code is also first-order in the Spyce language and Spyce supports +active tags. +
      • Spyce is modular in its design, allowing +users to easily extend its base functinality with add-on modules. +
      • The Spyce engine can be run from the command-line, which allows +Spyce to be used as an HTML preprocessor. +
      +Spyce, +like PHP, can run entirely within the process space of a webserver or via +CGI (as well as other web server adapters), and has been benchmarked to be +competitive in performance.
    • +

    • ASP.NET is a Microsoft technology +popular with Microsoft Internet Information Server (IIS) users. Visual +Basic .NET and C# are both popular implementation languages. +
        +
      • Spyce provides the power of the ASP.NET "component" development style +without trying to pretend that web applications live in a stateful, +event-driven environment. This is a leaky abstraction that causes +ASP.NET to have a steep learning curve while the user learns +where the rough edges are. +
      • ASP.NET is not well-supported outside the IIS environment. Spyce can +currently run as a standalone or proxy server, under mod_python (Apache), +or under CGI and FastCGI, which are +supported in the majority of web server environments. Adapters have also +been written for Xitami, Coil, Cheetah -- other web servers and frameworks. +
      • Spyce is open-source, and free. +
      +
    • +

      +

    • WebWare with Python Server Pages, PSP, is another +Python-based open-source development. PSP is similar in design to the Spyce +language, and shares many of the same benefits. Some important differences +include +
        +
      • Spyce supports both +Python chunks (indented Python) as well as PSP-style statements (braced +Python). +
      • Spyce supports active tags and component-based development +
      • Spyce code is first-order in the Spyce language +
      +PSP is also an integral part of +WebWare, an application-server framework similar to Tomcat Java-based +application server of the Apache Jakarta project. Spyce is to WebWare as JSP +is to Tomcat. Spyce is far simpler to install and run than WebWare (in the +author's humble opinion), and does not involve notions such as application +contexts. It aims to do only one thing well: provide a preprocessor and +runtime engine for the dynamic generation of HTML using embedded Python. +
    • +

    • Zope is an object-oriented open-source application server, +specializing in "content management, portals, and custom applications." Zope +is the most mature Python web application development environment, but +to a large degree suffers from +second-system syndrome. +In the author's opinion, Zope is to a large degree responsible for the +large number of python +web environments: a few years ago, it was de rigeur for talented programmers +to try Zope, realize it was a mess, and go off to write their own framework. +

      +Zope provides a scripting language called DHTML and can call +extensions written in Perl or Python. Spyce embeds Python directly in the +HTML, and only Python. It is an HTML-embedded language, not an application +server.

    • +

    +Spyce strikes a unique balance between power and simplicity. +Many users have said that this +is "exactly what they have been waiting for". Hopefully, this is the correct +point in the design space for your project as well. +

    +1.2. Design Goals

    + + +As a Spyce user, it helps to understand the broad design goals of this tool. +Spyce is designed to be: +

      +
    • Minimalist: The philosophy behind the design of Spyce is only +to include features that particularly enhance its functionality over the +wealth that is already available from within Python. One can readily import +and use Python modules for many functions, and there is no need to recode +large bodies of functionality.
    • +

    • Powerful: Spyce aims to free the programmer from as much +"plumbing"-style drudgery as possible through features such as +Active Handlers +and reusable Active Tags.
    • +

    • Modular: Spyce is built to be extended with +Spyce modules and +Active Tags +that provide additional functionality over the core engine +capabilities and standard Python modules. New features in the core engine +and language are rationalised against the option of creating a new module or +a new tag library. Standard Spyce modules and tag libraries are those that +are considered useful in a general setting and are included in the default +Spyce distribution. Users and third-parties are encouraged to develop their +own Spyce modules.
    • +

    • Intuitive: Obey user expectations. Part of this is avoiding +special cases.
    • +

    • Convenient: Using Spyce should be made as efficient as possible. +This, for example, is the reason behind the choice of [[ as delimeters over alternatives such as <? (php) and <% (jsp). +(However, ASP/JSP-style delimeters are also supported, so if you're +used to that style and like it, feel free to continue using it with Spyce.) +Functions and +modules are also designed with as many defaults as possible. +There are no XML configuration files in Spyce. +
    • +

    • Single-purpose: To be the best, most versatile, wildly-popular +Python-based dynamic HTML engine. Nothing more; nothing less.
    • +

    • Fast: Performance is +important. It is expected that Spyce will perform comparably with any other +dynamic, scripting solutions available. +
    • +

    +Now, let's start using Spyce...

    + +

    2. LANGUAGE

    + + +The basic structure of a Spyce script is an HTML file with embeddings. There +are six types of possible embeddings among the plain HTML text: +

      +
    • <taglib:name attr1=val1 ...> +
      Active tags may include presentation and action code. +
    • [[-- Spyce comment      --]] +
      Enclosed code is elided from the compiled Spyce class. +
    • [[\  Python chunk         ]] +
      Embed python in your code. +
    • [[!  Python class chunk  ]] +
      Like chunks, but at the class level rather than the spyceProcess method. +
    • [[   Python statement(s)  ]] +
      Like chunks, but may include braces to indicate that the block should continue after the ]]. +
    • [[=  Python expression    ]] +
      Output the result of evaluating the given expression. +
    • [[.  Spyce directive      ]] +
      Pass options to the Spyce compiler. +
    • [[spy  lambda             ]] +
      Allows dynamic compilation of Spyce code to a python function. +
    +Each Spyce tag type has a +unique beginning delimeter, namely [[, [[\, [[=, [[. or [[--. All +tags end with ]], except comment tags, which +end with --]]. +

    +Since +[[ and +]] +are special Spyce delimeters, one would escape them as +\[[ and +\]] +for use in HTML text. They can not be escaped within Python code, but the +string expressions +("["*2) and +("]"*2), or equivalent expressions, +can be used instead, or the brackets can be conveniently separated with a +space in the case of list or slicing expressions.

    +2.1. Plain HTML and Active Tags

    + + +Static plain HTML strings are printed as they are encountered. Depending on +the compacting mode of the +Spyce compiler, some whitespace may be eliminated. The Spyce transform module, for example, may +further pre-processes this string, by inserting transformations into the +output pipe. This is useful, for example, for dynamic compression of the +script result.

    +The Spyce language supports tag libraries. +Once a tag library is imported under some name, mytags, then all static +HTML tags of the form <mytags:foo ... > become "active". +That is, code from the tag library is executed at that point in the document. +Tags can control their output, conditionally skip or loop the execution of +their bodies, and can interact with other active tags in the document. They +are similar, in spirit and functionality, to JSP tags. Tag libraries and modules (discussed later) can both +considerably reduce the amount of code on a Spyce page, and increase code +reuse and modularity.

    +2.2. Spyce Comments

    + + +Syntax: [[-- comment --]]

    +Spyce comments are ignored, and do not produce any output, meaning that they +will not appear at the browser even in the HTML source. The first line of a +Spyce file, if it begins with the characters #!, is also considered a +comment, by Unix scripting convention. Spyce comments do not nest.

    +2.3. Spyce Directives

    + + +Syntax: [[. directive ]] +

    +Spyce directives directly affect the operation of the Spyce compiler. There +is a limited set of directives, listed and explained below: +

      +
    • +[[.compact mode=mode]] :
      +Spyce can output the static HTML strings in various modes of compaction, +which can both save bandwidth and improve download times without visibly +affecting the output. Compaction of static HTML strings is performed once +when the input Spyce file is compiled, and there is no additional run-time +overhead beyond that. Dynamically generated content from Python code tags +and expressions is not compacted nor altered in any way. Spyce can operate +in one of the compaction modes listed below. One can use the compact +tag to change the compaction mode from that point in the file forwards.

      +

        +
      • off: No compaction is performed. Every space and newline in the +static HTML strings is preserved.
      • +

      • space: Space compaction involves reducing any consecutive runs +of spaces or tabs down to a single space. Any spaces or tabs at the +beginning of a line are eliminated. These transformations will not affect +HTML output, barring the <pre> tag, but can considerably reduce the +size of the emitted text.
      • +

      • line: Line compaction eliminates any (invisible) trailing +whitespace at the end of lines. More significantly it improves the indented +presentation of HTML, by ignoring any lines that do not contain any static +text or expression tags. Namely, it removes all the whitespace, including +the line break, surrounding the code or directives on that line. This +compaction method usually "does the right thing", and produces nice HTML +without requiring tricky indentation tricks by the developer. It is, +therefore, the initial compaction mode.
      • +

      • full: Full compaction applies both space and line compaction. If +the optional mode attribute is omitted, full compaction mode is the +default value assumed.
      • +

      +
    • +

    • +[[.import name=name from=file as=name args=arguments]] :
      +The import directive loads and defines a Spyce module into the global +context. (The [[.module ... ]]directive +is synonymous.) A Spyce module is a +Python file, written specifically to interact with Spyce. The name +parameter is required, specifying the name of the Python class to load. The +file parameter is optional, specifying the file where the named class +is to be found. If omitted, file will equal name.py. The file path can be absolute or +relative. Relative paths are scanned in the Spyce home, user-configurable server path directories and current +script directory, in that order. Users are encouraged to name or prefix their +modules uniquely so as not to be masked by system modules or tag libraries. +The as parameter is optional, and specifies the name under which the +module will be installed in the global context. If omitted, this parameter +defaults to the name parameter. Lastly, the optional args parameter +provides arguments to be passed to the module initialization function. All +Spyce modules are start()ed before Spyce processing begins, +init()ed at the point where the directive is placed in the code, and +finish()ed after Spyce processing terminates. It is convention to +place modules at, or near, the very top of the file unless the location of +initialization is relevant for the functioning of the specific module.

      + +[[.import names="name1,name2,..."]] :
      +An alternative syntax allows convenient loading of multiple Spyce modules. +One can not specify non-standard module file locations, nor rename the +modules using this syntax.

    • +

    • +[[.taglib name=name from=file as=name]] :
      +The taglib directive loads a Spyce tag library. A Spyce tag library is a Python file, written +specifically to interact with Spyce. The name parameter specifies +the name of the Python class to load if using a 1.x-style taglib; +otherwise it is ignored. The file parameter is +optional, specifying the file where the named class is to be found. If +omitted, file will equal name.py. The file +path can be absolute or relative. Relative paths are scanned in the Spyce +home, user-configurable server +path directories and current script directory, in that order. Users are +encouraged to name or prefix their tag libraries uniquely so as not to be +masked by system tag libraries and modules. The as parameter is +optional, and specifies the unique tag prefix that will be used to identify +the tags from this library. If omitted, this parameter defaults to the name +parameter. It is convention to place tag library directives at, or near, the +very top of the file. The tags only become active after the point of the tag +library directive.

      +Also note that the configuration parameter globaltags allows you +to set up tag libraries globally, freeing you from having to specify the +taglib directive on each page that uses a tag. By default, globaltags +installs core under the spy: prefix, and form under the f: prefix. +(Tag libraries specified in globaltags are only loaded if the Spyce compiler +determines they are actually used on the page, so there is no performance +difference between globaltags and manually setting up taglib for each page.) +

      There are some additional directives that are only legal when +defining an active tag library. +

    +It is important to note that Spyce directives are processed at compile +time, not during the execution of the script, much like directives in C, and +other languages. In other words, they are processed as the Python code for the +Spyce script is being produced, not as it is being executed. Consequently, it +is not possible to include runtime values as parameters to the various +directives.

    +2.4. Python Statements

    + + +Syntax: [[ statement(s) ]]

    +The contents of a code tag is one or more Python statements. The statements +are executed when the page is emitted. There will be no output unless the +statements themselves generate output.

    +The statements are separated with semi-colons or new lines, as in regular +Python scripts. However, unlike regular Python code, Python statements do +not nest based on their level of indentation. This is because +indenting code properly in the middle of HTML is difficult on the developer. +To alleviate this problem, Spyce supports a slightly modifed Python syntax: +proper nesting of Spyce statements is achieved using begin- and end-braces: +{ and }, +respectively. These MUST be used, because the compiler regenerates the +correct indentation based on these markers alone. Even single-statement blocks +of code must be wrapped with begin and end braces. (If you prefer to use +Python-like indentation, read about chunks).

    +The following Spyce code, from the Hello World! +example above:

    + +
    + +
    +  [[ for i in range(10): { ]]
    +    [[=i]]
    +  [[ } ]]
    +
    +
    +
    +

    +produces the following indented Python code:

    + +
    + +
    +  for i in range(10):
    +    response.writeStatic('  ')
    +    response.writeExpr(i)
    +    response.writeStatic('\n')
    +
    +
    +
    +

    +Without the braces, the code produced would be unindented and, in this case, +also invalid:

    + +
    + +
    +  for i in range(10):
    +  response.writeStatic('  ')
    +  response.writeExpr(i)
    +  response.writeStatic('\n')
    +
    +
    +
    +

    +Note how the indentation of the expression does not affect the indentation of +the Python code that is produced; it merely changes the number of spaces in +the writeStatic string. Also note that unbalanced +open and close braces within a single tag are allowed, as in the example +above, and they modify the indentation level outside the code tag. However, +the braces must be balanced across an entire file. Remember: inside the [[ ... ]] delimiters, braces are always +required to change the indentation level.

    +2.5. Python Chunks

    + + +Syntax: [[\ Python chunk ]] +

    +There are many Python users that experience anguish, disgust or dismay upon +reading the previous section: "Braces!? Give me real, indented Python!". These +intendation zealots will be more comfortable using Python chunks, which is why +Spyce supports them. Feel free to use Spyce statements or chunks +inter-changeably, as the need arises.

    +A Python chunk is straight Python code, and the internal indentation is +preserved. The entire block is merely outdented (or indented) as a whole, such +that the first non-empty line of the block matches the indentation level of +the context into which the chunk was placed. Thus, a Python chunk can not +affect the indentation level outside its scope, but internal indentation is +fully respected, relative to the first line of code, and braces ({, }) are not required, nor +expected for anything but Python dictionaries. Since the first line of code is +used as an indentation reference, it is recommended that the start delimeter +of the tag (i.e. the [[\) be placed on its own +line, above the code chunk, as shown in the following example:

    + +
    + +
    [[\
    +    def printHello(num):
    +      for i in range(num):
    +        response.write('hello<br>')
    +
    +    printHello(5)
    +]]
    +
    +
    +

    +Naturally, one should not use braces here for purposes of indentation, +only for Python dictionaries. Additional braces will merely generate Python +syntax errors in the context of chunks. To recap: a Python statement tag +should contain braced Python; A Python chunk tag should contain regular +indented Python.

    +2.6. Python Class Chunks

    + + +Syntax: [[! Python class chunk ]] +

    +Behind the scenes, your Spyce files are compiled into a class called spyceImpl. Your Spyce script runs in a method of this class +called spyceProcess. Class chunks allow you to +specify code to be placed inside the class, but outside the main method, +analogously to the "<%!" token in JSP code. (If you would like to +see your Spyce file in compiled Python form, use the following command-line: +spyce.py -c myfile.spy.)

    +

    +This is primarily useful when defining active +handlers without using a separate .py file: active handlers are the first +thing that the spyceProcess calls, even before any "python chunks." +For a handler callback to be visible at this stage, it needs to be defined +at the class level. Class chunks to the rescue: +

    + +
    + examples/handlerintro.spy +
    + +
    [[!
    +def calculate(self, api, x, y):
    +    self.result = x * y
    +]]
    +
    +<spy:parent title="Active Handler example" />
    +<f:form>
    +    <f:text name="x:float" default="2" label="first value" />
    +    <f:text name="y:float" default="3" label="second value" />
    +
    +    <f:submit handler="self.calculate" value="Multiply" />
    +</f:form>
    +
    +<p>
    +Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]]
    +</p>
    +
    +
    +
    + + Run this code + +
    +

    +2.7. Python Expressions

    + + +Syntax: [[= expression ]] +

    +The contents of an expression tag is a Python expression. The result of that +expression evaluation is printed using the its string representation. +The Python object None is special cased to output as the empty string +just as it is in the Python interactive shell. This is almost always +more convenient when working with HTML. (If you really want a literal +string 'None' emitted instead, use response.write in a statement or chunk.) +

    +The Spyce transform module, can +pre-processes this result, to assist with mundane tasks such as ensuring that +the string is properly HTML-encoded, or formatted.

    +2.8. Spyce Lambdas

    + + +Syntax: [[spy [params] : spyce lambda code ]] +
    +or: [[spy! [params] : spyce lambda code ]] +

    +A nice feature of Spyce is that Spyce scripts are first-class members of the +language. In other words, you can create a Spyce lambda (or function) in any +of the Spyce Python elements (statements, chunks and expressions). These can +then be invoked like regular Python functions, stored in variables for later +use, or be passed around as paramaters. This feature is often very useful for +templating (example shown below), and can also be used to implement more +esoteric processing functionality, such as internationalization, multi-modal +component frameworks and other kinds of polymorphic renderers.

    +It is instructive to understand how these functions are generated. The [[spy ... : ... ]] syntax is first +translated during compilation into a call to the define() function of the spylambda module. At runtime, this +call compiles the Spyce code at the point of its definition, and returns a +function. While the invocation of a Spyce lambda is reasonably efficient, it +is certainly not as fast as a regular Python function invocation. The +spycelambda can be memoized (explained in the spylambda module section) by using +the [[spy! ... : ... ]] +syntax. However, even with this optimization one should take care to use +Python lambdas and functions when the overhead of Spyce parsing and invocation +is not needed.

    +Note that Spyce lambdas do not currently support nested variable scoping, nor +default parameters. The global execution context (specifically, Spyce modules) +of the Spyce lambda is defined at the point of its execution.

    + +
    + examples/spylambda.spy +
    + +
    [[\
    +  # table template
    +  table = [[spy! title, data: 
    +    <table>
    +      <tr>
    +        [[for cell in title: {]]
    +          <td><b>[[=cell]]</b></td>
    +        [[}]]
    +      </tr>
    +      [[for row in data: {]]
    +        <tr>
    +          [[for cell in row: {]]
    +            <td>[[=cell]]</td>
    +          [[}]]
    +        </tr>
    +      [[}]]
    +    </table> 
    +  ]]
    +
    +  # table information
    +  title = ['Country', 'Size', 'Population', 'GDP per capita']
    +  data = [
    +    [ 'USA', '9,158,960', '280,562,489', '$36,300' ],
    +    [ 'Canada', '9,220,970', '31,902,268', '$27,700' ],
    +    [ 'Mexico', '1,923,040', '103,400,165', '$9,000' ],
    +  ]
    +]]
    +
    +[[-- emit web page --]]
    +<html><body>
    +  [[ table(title, data) ]]
    +</body></html>
    +
    +
    +
    +
    + + Run this code + +
    +

    +2.9. ASP/JSP syntax

    + + +Finally, due to popular demand, because of current editor support and people +who actually enjoy pains in their wrists, the Spyce engine will respect +ASP/JSP-like delimeters. In other words, it will also recognize the following +syntax: +

    +The two sets of delimeters may be used interchangeably within the same file, +though for the sake of consistency this is not recommended. + +

    3. RUNTIME

    + + +Having covered the Spyce language syntax, we now move to describing the +runtime processing. Each time a request comes in, the cache of compiled Spyce +files is checked for the compiled version of the requisite Spyce file. If one +is not found, the Spyce file is quickly read, transformed, compiled and cached +for future use.

    +The compiled Spyce is initialized, then processed, then finalized. The +initialization consists of initializing all the Spyce modules. The Spyce file +is executed top-down, until the end is reached or an exception is thrown, +whichever comes first. The finalization step then finalizes each module in +reverse order of initialization, and any buffered output is automatically +flushed.

    +3.1. Exceptions

    + + +The Spyce file is executed top-down, until the end of the file is reached, a +valued is returned, or an exception is thrown, +whichever comes first. If the code terminates via an unhandled exception, then +it is caught by the Spyce engine. Depending on the exception type, different +actions are taken: +

      +
    • spyceDone can be raised at any time to stop the Spyce processing +(without error) at that point. It is often used to stop further output, as +in the example below that emits a binary image file. The spyceDone +exception, however, is more useful for modules writers. In regular Spyce +code one could simply issue a return statement, +with the same effect.
    • +

    • spyceRedirect is used by the +redirect module. It causes the +Spyce engine to immediately redirect the request to another Spyce file +internally. Internally means that we do not send back a redirect to +the browser, but merely clear the output buffer and start processing a new +script.
    • +

    • All other exceptions that occur at runtime will be processed via +the Spyce error module. This +module will emit a default error message, unless the user has installed some +other error handler.
    • +

    +Note that non-runtime exceptions, such as exceptions caused by compile errors, +missing files, access restrictions and the like, are handled by the server. +The default server error handler +can be configured via the server configuration file.

    + +
    + examples/gif.spy +
    + +
    [[.import name=include ]]
    +[[\
    +  # Spyce can also generate other content types
    +  # The following code displays the Spyce logo
    +  response.setContentType('image/gif')
    +  import os.path, spyce
    +  path = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'spyce.gif')
    +  response.write(include.dump(path, 1))
    +  raise spyceDone
    +]]
    +
    +
    +
    + + Run this code + +
    +

    +3.2. Code Transformation

    + + +While the minutia of the code transformation that produces Python code from +the Spyce sources is of no interest to the casual user, it has some slight, +but important, ramifications on certain aspects of the Python language +semantics when used inside a Spyce file.

    +The result of the Spyce compilation is some Python code, wherein the majority +of the Spyce code actually resides in a single function called +spyceProcess. If you are curious to see the result of a Spyce +compilation, execute: "spyce -c".

    +It follows from the compilation transformation that: +

      +
    • Any functions defined within the Spyce file are actually nested +functions within the spyceProcess function.
    • +

    • The use of global variables within Spyce code is not supported, +but also not needed. If nested scoping is available (Python versions +>2.1) then these variables will simply be available. If not, then you +will need to pass variables into functions as default parameters, and will +not be able to update them by value (standard Python limitations). It is +good practice to store constants and other globals in a single class, or to +to place them in a single, included file, or both.
    • +

    • The global Spyce namespace is reserved for special variables, such as +Spyce and Python modules. While the use of the keyword global is not explicitly checked, it will pollute this +space and may result in unexpected behaviour or runtime errors.

    • +

    • The lifetime of variables is the duration of a request. Variables with +lifetimes longer than a single request can be stored using the pool module.
    • +

    +3.3. Dynamic Content

    + + +The most common use of Spyce is to serve dynamic HTML content, but it should +be noted that Spyce can be used as a general purpose text engine. It can be +used to generate XML, text and other output, as easily as HTML. In fact, the +engine can also be used to generate dynamic binary data, such as images, PDF +files, etc., if needed.

    +The Spyce engine can be installed +in a number of different configurations that can produce dynamic output. +Proxy server, mod_python, and FastCGI exhibit high performance; the CGI approach is +slower, since a new engine must be created for each request. See the +configuration section for details. +3.4. Static Content

    + + +A nice feature of Spyce is that it can be invoked both from within a web +server to process a web request dynamically and also from the command-line. +The processing engine itself is the same in both cases. The command-line +option is actually just a modified CGI client, and is often used to +pre-process static content, such as this manual.

    +Some remarks regarding command-line execution specifics are in order. The +request and response objects for a command-line request are connected to +standard input and output, as expected. A minimal CGI-like environment is +created among the other shell environment variables. Header and cookie lookups +will return None and the engine will accept input on stdin for POST +information, if requested. There is also no compiler cache, since the process +memory is lost at the end of every execution.

    +Most commonly, Spyce is invoked from the command-line to generate static .html +ouput. Spyce then becomes a rather handy and powerful .html preprocessing +tool. It was used on this documentation to produce the consistent headers and +footers, to include and highlight the example code snippets, etc...

    +The following makefile rule comes in handy:

    + +
    +
    +  %.html: %.spy
    +    spyce -o $@ $<
    +
    +

    +3.5. Command line

    + + +The full command-line syntax is:

    + +
    + +
    Spyce 2.1
    +Command-line usage:
    +  spyce -c [-o filename.html] <filename.spy>
    +  spyce -w <filename.spy>                <-- CGI
    +  spyce -O filename(s).spy               <-- batch process
    +  spyce -l [-d file ]                    <-- proxy server
    +  spyce -h | -v
    +    -h, -?, --help       display this help information
    +    -v, --version        display version
    +    -o, --output         send output to given file
    +    -O                   send outputs of multiple files to *.html
    +    -c, --compile        compile only; do not execute
    +    -w, --web            cgi mode: emit headers (or use run_spyceCGI.py)
    +    -q, --query          set QUERY_STRING environment variable
    +    -l, --listen         run in HTTP server mode
    +    -d, --daemon         run as a daemon process with given pidfile
    +    --conf [file]        Spyce configuration file
    +To configure Apache, please refer to: spyceApache.conf
    +For more details, refer to the documentation.
    +  http://spyce.sourceforge.net
    +Send comments, suggestions and bug reports to <rimon-AT-acm.org>.
    +
    +
    +
    +

    +3.6. Configuration

    + + +Since there are a variety of very different +installation +alternatives for the Spyce +engine, effort has been invested in consolidating all the various runtime +configuration options. By default, the Spyce engine will search for a file +called spyceconf.py in its installation directory. An alternative file +location may be specified via the --conf command-line option. +

    +The spyce configuration file is a valid python module; any python code +may be used. To avoid duplication, we recommend starting with +"from spyceconf import *" and +override only select settings. One thing you cannot do from the config module +is access the Spyce server object spyce.getServer(), +since it has not been initialized yet.

    +You may access the loaded configuration module from Spyce scripts and from +Python modules using the config attribute of the +server object. Or, simply as the spyceConfig module, +regardless of it actual file name. For example:

    + +
    + examples/config.spy +
    + +
    [[\
    +  import spyceConfig
    +  home = spyceConfig.SPYCE_HOME
    +]]
    +
    +[[= home ]]
    +
    +
    +
    + + Run this code + +
    +

    +Below is the configuration file that this server is running. The length of the +file is primarily due to the thoroughness of the comments:

    +
    + + + + +
    +# NOTE: do note write code that directly imports this module 
    +# (except when you are writing a custom configuration module.)
    +# This is a recipe for trouble, since spyce allows the user
    +# to specify the configuration module filename on the commandline.
    +# Instead, use import spyce; spyce.getServer().config.
    +
    +import os, sys
    +import spycePreload
    +
    +# Determine SPYCE_HOME dynamically.
    +# (you can hardcode SPYCE_HOME if you really want to, but it shouldn't be necessary.)
    +SPYCE_HOME = spycePreload.guessSpyceHome()
    +
    +# The spyce path determines which directories are searched for when
    +# loading modules (with [[.import]]) and tag libraries (with [[.taglib]]
    +# and the globaltags configuration later in this file.
    +#
    +# By default, the Spyce installation directory is always searched
    +# first. Any directories in the SPYCE_PATH environment are also
    +# searched.
    +#
    +# If you need to import from .py modules in nonstandard locations
    +# (i.e., not in your python installation's library directory),
    +# you will want to add their directories to sys.path as well, as
    +# done here for the error module.  (However, Spyce automagically
    +# changes sys.path dynamically so you will always be able to import
    +# modules in the same directory as your currently-processing .spy file.)
    +#
    +# path += ['/usr/spyce/inc/myapplication', '/var/myapp/lib']
    +path = [os.path.join(SPYCE_HOME, 'modules'), os.path.join(SPYCE_HOME, 'contrib', 'modules')]
    +path.append(os.path.join(SPYCE_HOME, 'tags'))
    +path.append(os.path.join(SPYCE_HOME, 'contrib', 'tags'))
    +if os.environ.has_key('SPYCE_PATH'):
    +    path += os.environ['SPYCE_PATH'].split(os.pathsep)
    +# provide originalsyspath so if someone wants to maintain a config file via
    +# "from spyceconf import *"
    +# he can remove the above modifications if desired.
    +originalsyspath = list(sys.path)
    +sys.path.extend(path)
    +
    +# The globaltags option specifies a group of tag libraries that will be autoloaded
    +# as if [[.taglib name=libname from=file as=prefix]] were specified in every .spy
    +# file.  (There is no performance hit if the library is not used on a page;
    +# the Spyce compiler optimizes it out.)
    +#
    +# The format is ('libname', 'file', 'prefix').
    +# For a 2.0-style tag library, the libname attribute is ignored.  Passing None is fine.
    +#
    +# globaltags.append(('mytag', 'mytaglib.py', 'my'))
    +# globaltags.append((None, 'taglib2.py', 'my2'))
    +globaltags = [
    +    ('core', 'core.py', 'spy'),
    +    ('form', 'form.py', 'f'),
    +    (None, 'render.spi', 'render'),
    +    ]
    +
    +# The default parent template is the one that is used by <spy:parent>
    +# if no src attribute is given, specified as an absolute url.
    +defaultparent = "/parent.spi"
    +
    +# The errorhandler option sets the server-level error handler.  These
    +# errors include spyce.spyceNotFound, spyce.spyceForbidden,
    +# spyce.spyceSyntaxError and spyce.pythonSyntaxError.  (file-level
    +# error handling is defined within Spyce scripts using the error module.)
    +#
    +# The server will call the error handler as errorhandler(request, response, error).
    +#
    +# Please look at the default function if you are considering writing your own
    +# server error handler.
    +import error
    +errorhandler = error.serverHandler
    +
    +# The pageerror option sets the default page-level error handler.
    +# "Page-level" means all runtime errors that occur during the
    +# processing of a Spyce script (i.e. after the compilation phase has
    +# completed successfully)
    +#
    +# The format of this option is one of:
    +#   ('string', 'MODULE', 'VARIABLE')
    +#   ('file', 'URL')
    +# (This format is used since the error template must be transformed into
    +# compiled spyce code, which can't be done before the configuration file
    +# is completely loaded.)
    +#
    +# Please refer to the default template to see how to define your own
    +# page-level error handlers.
    +#
    +# pageerrortemplate = ('file', '/error.spy')
    +pageerrortemplate = ('string', 'error', 'defaultErrorTemplate')
    +
    +# The cache option affects the underlying cache mechanism that the
    +# server uses to maintain compiled Spyce scripts. Currently, Spyce
    +# supports two cache handlers:
    +#
    +#   cache = 'memory'
    +#   OR
    +#   cache = 'file'
    +#   cachedir = '/tmp' # REQUIRED: directory in which to store compiled files
    +#
    +# Why store the cache in the filesystem instead of memory?  The main
    +# reason is if you are running under CGI or mod_python.  Under
    +# mod_python, Apache will kick off a number of separate spyce
    +# processes; each would have its own memory cache; using the
    +# filesystem avoids wasteful duplication.  (Pointing the cachedir to a
    +# ramdisk makes it almost as fast as a memory cache.)  Under CGI of
    +# course, the python process isn't persistent so file caching is the
    +# only option to avoid expensive recompilation with each request.
    +#
    +# If you are running multiple Spyce instances on the same machine,
    +# they cannot share the same cachedir.  Give each a different cachedir,
    +# or use the in-memory cache type.
    +cache = 'memory'
    +
    +# The check_mtime option affects the caching of compiled Spyce code.
    +# When True, Spyce will check file timestamps with each request and
    +# recompile if they have been modified.  Setting this to False can
    +# speed up a "production server" but you will have to restart the
    +# server and (if using a file cache) clear out the cachedir
    +# to have changes made in the spyce code take effect.
    +check_mtime = True
    +
    +# The debug option turns on a LOT of logging to stderr.
    +debug = False
    +
    +# The globals section defines server-wide constants. The hashtable is
    +# accessible as "pool" within any Spyce file (with the pool
    +# method loaded), or as self._api.getServerGlobals() within any Spyce
    +# module.
    +#
    +# globals = {'name': "My Website", 'four': 2+2}
    +globals = {}
    +
    +# You may wish to pre-load various Python modules during engine initialization.
    +# Once imported, they will be in the python module cache.
    +#
    +# (You may of course use normal imports at any time in this configuration script;
    +# however, imports specified here are run after the Spyce server is
    +# completely initialized, making it safe to access the server internals via
    +# import spyce; spyce.getServer()...)
    +#
    +# imports = ['myModule', 'myModule2']
    +imports = []
    +
    +# The root option defines the path from which Spyce requests are processed.
    +# I.e., when a request for http://yourserver/path/foo.spy arrives,
    +# Spyce looks for foo.spy in <root>/path/.
    +# 
    +# root = '/var/www/html'
    +root = os.path.join(SPYCE_HOME, 'www')
    +# feel free to comment this next line out if you don't have python modules (.py) in your web root
    +sys.path.append(root)
    +
    +# some parts of spyce may need to create temporary files; usually the default is fine.
    +# BUT if you do override this, be sure to also override other parts of the config
    +# that reference it.  (currently just session_store)
    +import tempfile
    +tmp = tempfile.gettempdir()
    +
    +# active tag to render form validation errors
    +validation_render = 'render:validation'
    +
    +
    +#####
    +# database connection
    +#####
    +
    +from sqlalchemy.ext.sqlsoup import SqlSoup
    +
    +# Examples:
    +# db = SqlSoup('postgres://user:pass@localhost/dbname')
    +# db = SqlSoup('sqlite:///my.db')
    +# db = SqlSoup('mysql://user:pass@localhost/dbname')
    +#
    +# SqlSoup takes the same URLs as an SqlAlchemy Engine.  See 
    +# http://www.sqlalchemy.org/docs/dbengine.myt#dbengine_establishing
    +# for more examples.
    +try:
    +  db = SqlSoup('sqlite:///www/demos/to-do/todo.db')
    +except:
    +  db = None
    +
    +#####
    +# session options -- see docs/mod_session.html for details
    +#####
    +
    +import session
    +
    +session_store = session.DbmStore(tmp)
    +# session_store = session.MemoryStore()
    +
    +session_path = '/'
    +
    +session_expire = 24 * 60 * 60 # seconds
    +
    +#####
    +# login options
    +#####
    +
    +# The spyce login system uses the session storage
    +# defined above; a key called _spy_login will be added to each session.
    +
    +# validators must be a function that takes login and password as
    +# arguments, and returns a pickle-able object (usually int or string)
    +# representing the ID of the logged in user, or None if login fails.
    +#
    +# You'll need to supply your own to hook into your database or other
    +# validation system; see the pyweboff config.py for an example of doing
    +# this.
    +def nevervalidator(login, password):
    +        return None
    +
    +def testvalidator(login, password):
    +    if login == 'spyce' and password == 'spyce':
    +        return 2
    +    return None
    +
    +login_defaultvalidator = testvalidator
    +
    +# How to store login tokens.  FileStorage comes with Spyce,
    +# but it's easy to create your own Storage class if you want to put them
    +# in your database, for instance.
    +from _coreutil import FileStorage
    +# It's not a good idea to put login-tokens off of www/ in production!
    +# It's just done this way here to keep the spyce source tree relatively clean.
    +login_storage = FileStorage(os.path.join(SPYCE_HOME, 'www', 'login-tokens'))
    +
    +# tags to render login form; must be visible in the globaltags search space.
    +# These come from tags/render.spi; to make your own, just create a tag 
    +# that takes the same parameters and add the library to the globaltags list above.
    +login_render = 'render:login'
    +loginrequired_render = 'render:login_required'
    +
    +######
    +# webserver options -- does not affect mod_python or *CGI configurations
    +######
    +
    +# indexFiles specifies a list of files to look for
    +# in a directory if directory itself is requested.
    +# The first matching file will be selected.
    +# If empty, a directory listing will be served instead.
    +#
    +# indexFiles = ['index.spy', 'index.html', 'index.txt']
    +indexFiles = ['index.spy', 'index.html']
    +
    +# The Spyce webserver uses a threaded concurrency model.  (Historically,
    +# it also offered "no concurrency" and forking.  If for some strange
    +# reason you really want no concurrency, set minthreads=maxthreads=1.
    +# Forking was removed entirely because it performed over 10x slower
    +# than threading on Linux and Windows.)
    +#
    +# Do note that because of the Python GIL (global interpeter lock),
    +# only one CPU of a multi-CPU machine can execute Python code at a time.
    +# If this is your situation, mod_python may be a better option for you.
    +minthreads = 5
    +maxthreads = 10
    +# number of pending requests to accept
    +maxqueuesize = 50
    +
    +# Restart the webserver if a python module changes.
    +# Spyce does this by running the "real" server in a subprocess; when that
    +# server detects changed modules, it exits and Spyce starts another one.
    +#
    +# Spyce will check for changes every second, or when a request
    +# is made, whichever comes first.
    +#
    +# It's highly recommended to turn this off for "production" servers,
    +# since checking each module for each request is a performance hit.
    +check_modules_and_restart = True
    +
    +# The ipaddr option defines which IP addresses the server will listen on.
    +# empty string for all.
    +ipaddr = ''
    +
    +# The port option defines which TCP port the server will listen on.
    +port = 8000
    +# Port that provides an interactive Python console interface to
    +# the webserver's guts.  Currently no password protection is offered;
    +# don't expose this to the outside world!
    +adminport = None
    +
    +# The mime option is a list of filenames. The files should
    +# be definitions of mime-types for common file extensions in the
    +# standard Apache format.
    +#
    +# mime: ['/etc/mime.types']
    +mime = [os.path.join(SPYCE_HOME, 'spyce.mime')]
    +
    +# The www_handlers option defines the hander used for files of
    +# arbitrary extensions.  (The None key specifies the default.)
    +# The currently supported handlers are:
    +#   spyce     - process the file at the requested path as a spyce script
    +#   directory - display directory listing
    +#   dump      - transfer the file at the requested path verbatim, 
    +#     providing an appropriate "Content-type" header, if it is known.
    +# (It's difficult to use the actual instance methods here since we
    +# don't have a handle to the WWW server object.  So, we use strings
    +# and let spyceWWW eval them later.)
    +www_handlers = {
    +  'spy': 'spyce',
    +  '/':   'directory',
    +  None:  'dump'
    +}
    +
    +
    +######
    +# (F)CGI options
    +######
    +
    +# Forbid direct requests to the cgi script and discard command line arguments.
    +#
    +# Reason: http://www.cert.org/advisories/CA-1996-11.html
    +#
    +# You may need to disable this for
    +#  1) non-Apache servers that do not set the REDIRECT_STATUS environment
    +#     variable.
    +#  2) when using the alternative #! variant of CGI configuration
    +cgi_allow_only_redirect = False
    +
    +
    +######
    +# standard module customization
    +######
    +
    +# (request module)
    +
    +# param filters and file filters are lists of functions that are called
    +# for all GET and POST parameters or files, respectively.
    +# Each callable should expect two arguments: a reference to the
    +# request object/spyce module, and the dictionary being filtered.
    +# (Thus, each callable in param_filters will be called twice; once for the
    +# GET dict and once for POST. Each callable in file_filters will only be called once.)
    +param_filters = []
    +file_filters = []
    +
    +
    + +
    +

    +3.7. Server utilities

    + + +Like any application server, the Spyce server provides several facilities +that can aid development. +3.7.1. The Spyce scheduler

    + + +Spyce provides a scheduler that allows you to easily define tasks to +run at specified times or intervals inside the Spyce server. This +allows your tasks to leverage the tools Spyce gives you, as well as +any global data your application maintains within Spyce, such as +cached data or database connection pools. This also has the advantage +(over, say, crontab entries) of keeping your application self-contained, +making it easier to deploy changes from a development machine to production. +

    +The Spyce scheduler is currently only useful if you are running +in webserver mode. If you run under mod_python, CGI, or FastCGI, +you could approximate scheduler behavior by storing tasks and checking +to see if any are overdue with every request received; this would +be an excellent project for someone wishing to get started in Spyce +development. +

    + +

    + + + + +
     
    + 
    scheduler
    index
    y:\develop\pcwprojects\pcwpdfconvert\spyce-2.1\scheduler.py
    +

    A module for scheduling arbitrary callables to run at given times
    +or intervals, modeled on the naviserver API.  Scheduler runs in
    +its own thread; callables run in this same thread, so if you have
    +an unusually long callable to run you may wish to give it its own
    +thread, for instance,

    +schedule(3600, lambda: threading.Thread(target=longcallable).start())

    +Scheduler does not provide subsecond resolution.

    +Public functions are threadsafe.

    +

    + + + + + +
     
    +Modules
           
    atexit
    +sys
    +
    threading
    +time
    +
    traceback
    +

    + + + + + +
     
    +Classes
           
    +
    Task +
    +

    + + + + + + + +
     
    +class Task
       Instantiated by the schedule methods.

    +Instance variables:
    +  nextrun: epoch seconds at which to run next
    +  interval: seconds before repeating
    +  callable: function to invoke
    +  last: if True, will be unscheduled after nextrun

    +(Note that by manually setting last on a Task instance, you
    +can cause it to run an arbitrary number of times.)
     
     Methods defined here:
    +
    __init__(self, firstrun, interval, callable, once)
    + +
    __repr__(self)
    + +

    + + + + + +
     
    +Functions
           
    debuglogger(s)
    +
    logger lambda s
    +
    pause()
    Temporarily suspend running scheduled tasks
    +
    schedule(interval, callable, once=False)
    Schedules callable to be run every interval seconds.
    +Returns the scheduled Task object.
    +
    schedule_daily(hours, minutes, callable, once=False)
    Schedules callable to be run at hours:minutes every day.
    +(Hours is a 24-hour format.)
    +Returns the scheduled Task object.
    +
    schedule_weekly(day, hours, minutes, callable, once=False)
    Schedules callable to be run at hours:minutes on the given
    +zero-based day of the week.  (Monday is 0.)
    +Returns the scheduled Task object.
    +
    unpause()
    Resume running scheduled tasks.  If a task came due while
    +it was paused, it will run immediately after unpausing.
    +
    unschedule(task)
    Removes the given task from the scheduling queue.
    +
    +

    + + +
    + examples/scheduling.py +
    + + + +
    +import spyce, scheduler
    +
    +def delete_unsubmitted():
    +    db = spyce.SPYCE_GLOBALS['dbpool'].connection()
    +    sql = "DELETE FROM alerts WHERE status = 'unsubmitted' AND created < now() - '1 week'::interval"
    +    db.execute(sql)
    +
    +# delete alerts that were created over a week ago but but still not submitted 
    +scheduler.schedule_daily(00, 10, delete_unsubmitted)
    +
    +
    + +
    +
    + +3.7.2. spyceUtil

    + + +Most of the spyceUtil module is interesting only to internal operations, +but several functions are more generally applicable: +

      +
    • url2file( url, relativeto=None ) +
      Returns the filesystem path of the file represented by url, relative +to a given path. For example, url2file('/index.spy') or +url2file('img/header.png', request.filename()). +
    • +

    • exceptionString( ) +
      Every python programmer writes this eventually: returns a string containing +the description and stacktrace for the most recent exception. +
    • +

    + +3.8. Modules

    + + +The Spyce language, as described above, is simple and small. Most +functionality is provided at runtime through Spyce modules and Python modules. +

    +The standard Spyce modules are documented here; some other modules are +also distributed in the contrib/ directory. +

    +Non-implicit modules are +imported using the Spyce [[.import]] directive. Python modules are +imported using the Python import keyword. Remember that +modules need to have the same read permissions as regular files that you +expect the web server to read. +

    +Modules may be imported with a non-default name using the as attribute +to the .import directive. This is discouraged for standard Spyce modules; the +session module, for example, expects to find or +otherwise load a module named cookie in the Spyce environment. +

    +Once included, a Spyce module may be accessed anywhere in the Spyce code. +3.8.1. DB (implicit)

    + + + + +Spyce integrates an advanced database module to make reading and modifying +your data faster and less repetitive. +

    +Just initialize the db reference in your Spyce config file following +the examples given there, and you're all set. + +

    The general idea is, db.tablename represents the tablename table +in your database, and provides hooks for reading and modifying data in that +table in pure Python, no SQL required. We find this gives 90% of the benefits +of an object-relational mapping layer, without making developers learn another +complex tool. And since the Spyce db module is part of +SQLAlchemy, probably the most advanced +database toolkit in the world, the full ORM approach is available to those +who want it. + +

    +Here's a quick example of reading +and inserting data from a table called todo_lists: + + +
    + examples/db.spy +
    + +
    <spy:parent title="To-do demo" />
    +
    +[[!
    +def list_new(self, api, name):
    +    if api.db.todo_lists.selectfirst_by(name=name):
    +        raise HandlerError('New list', 'a list with that description already exists')
    +    api.db.todo_lists.insert(name=name)
    +    api.db.flush()
    +]]
    +
    +(This is an self-contained example using the same database as the
    +<a href=/demos/to-do/index.spy>to-do demo</a>.)
    +
    +<h2>To-do lists</h2>
    +
    +[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]]
    +<spy:ul data="[L.name for L in lists]" />
    +
    +<h2>New list</h2>
    +<f:form>
    +<f:submit value="New list" handler=self.list_new />:
    +<f:text name=name value="" />
    +</f:form>
    +
    +
    +
    + + Run this code + +
    + + +

    Full SqlSoup documentation follows. + +

    Loading objects

    +

    +Loading objects is as easy as this: +

    +    >>> users = db.users.select()
    +    >>> users.sort()
    +    >>> users
    +    [MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0), 
    +     MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)]
    +
    + +

    +Of course, letting the database do the sort is better (".c" is short for ".columns"): +

    +    >>> db.users.select(order_by=[db.users.c.name])
    +    [MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1),
    +     MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0)]
    +
    + +

    +Field access is intuitive: +

    +    >>> users[0].email
    +    u'student@example.edu'
    +
    + +

    +Of course, you don't want to load all users very often. The common case is to +select by a key or other field: +

    +    >>> db.users.selectone_by(name='Bhargan Basepair')
    +    MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)
    +
    + +

    Select variants

    +All the SqlAlchemy Query select variants are available. +Here's a quick summary of these methods: + +- get(PK): load a single object identified by its primary key (either a scalar, or a tuple) +- select(Clause, **kwargs): perform a select restricted by the Clause argument; returns a list of objects. The most common clause argument takes the form "db.tablename.c.columname == value." The most common optional argument is order_by. +- select_by(**params): the *_by selects allow using bare column names. (columname=value)This feels more natural to most Python programmers; the downside is you can't specify order_by or other select options. +- selectfirst, selectfirst_by: returns only the first object found; equivalent to select(...)[0] or select_by(...)[0], except None is returned if no rows are selected. +- selectone, selectone_by: like selectfirst or selectfirst_by, but raises if less or more than one object is selected. +- count, count_by: returns an integer count of the rows selected. + +See the SqlAlchemy documentation for details: +- general info and examples +- details on constructing WHERE clauses + +

    Modifying objects

    + +

    +Modifying objects is intuitive: +

    +    >>> user = _
    +    >>> user.email = 'basepair+nospam@example.edu'
    +    >>> db.flush()
    +
    + +

    (SqlSoup leverages the sophisticated SqlAlchemy unit-of-work code, so +multiple updates to a single object will be turned into a single UPDATE +statement when you flush.) + +

    +To finish covering the basics, let's insert a new loan, then delete it: +

    +    >>> db.loans.insert(book_id=db.books.selectfirst(db.books.c.title=='Regional Variation in Moss').id, user_name=user.name)
    +    MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
    +    >>> db.flush()
    +
    +    >>> loan = db.loans.selectone_by(book_id=2, user_name='Bhargan Basepair')
    +    >>> db.delete(loan)
    +    >>> db.flush()
    +
    + +

    +You can also delete rows that have not been loaded as objects. Let's do our insert/delete cycle once more, +this time using the loans table's delete method. (For SQLAlchemy experts: +note that no flush() call is required since this +delete acts at the SQL level, not at the Mapper level.) The same where-clause construction rules +apply here as to the select methods: +

    +    >>> db.loans.insert(book_id=book_id, user_name=user.name)
    +    MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
    +    >>> db.flush()
    +    >>> db.loans.delete(db.loans.c.book_id==2)
    +
    + +

    +You can similarly update multiple rows at once. This will change the book_id to 1 in all loans whose book_id is 2: +

    +    >>> db.loans.update(db.loans.c.book_id==2, book_id=1)
    +    >>> db.loans.select_by(db.loans.c.book_id==1)
    +    [MappedLoans(book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
    +
    + + +

    Joins

    + +

    Occasionally, you will want to pull out a lot of data from related tables all at +once. In this situation, it is far +more efficient to have the database perform the necessary join. (Here +we do not have "a lot of data," but hopefully the concept is still clear.) +SQLAlchemy is smart enough to recognize that loans has a foreign key +to users, and uses that as the join condition automatically. +

    +    >>> join1 = db.join(db.users, db.loans, isouter=True)
    +    >>> join1.select_by(name='Joe Student')
    +    [MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
    +     book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
    +
    + +

    +You can compose arbitrarily complex joins by combining Join objects with +tables or other joins. +

    +    >>> join2 = db.join(join1, db.books)
    +    >>> join2.select()
    +    [MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
    +     book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0),
    +     id=1,title='Mustards I Have Known',published_year='1989',authors='Jones')]
    +
    + +

    +If you join tables that have an identical column name, wrap your join with "with_labels", +and all the columns will be prefixed with their table name: +

    +    >>> db.with_labels(join1).select()
    +    [MappedUsersLoansJoin(users_name='Joe Student',users_email='student@example.edu',
    +                          users_password='student',users_classname=None,users_admin=0,
    +                          loans_book_id=1,loans_user_name='Joe Student',
    +                          loans_loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
    +
    + +

    Advanced usage

    + +

    +You can access the SqlSoup's engine attribute to compose SQL directly. +The engine's execute method corresponds +to the one of a DBAPI cursor, and returns a ResultProxy that has fetch methods +you would also see on a cursor. + +

    +    >>> rp = db.engine.execute('select name, email from users order by name')
    +    >>> for name, email in rp.fetchall(): print name, email
    +    Bhargan Basepair basepair+nospam@example.edu
    +    Joe Student student@example.edu
    +
    + +

    +You can also pass this engine object to other SQLAlchemy constructs; see the SQLAlchemy documentation for details. + +

    +You can access SQLAlchemy Table and Mapper objects as db.tablename._table and db.tablename._mapper, respectively. + + +3.8.2. Request (implicit)

    + + +The request module is loaded implicitly into every Spyce environment. +

    +The spyce configuration file gives two lists that affect the request module: +param_filters and file_filters. param_filters is a list of functions to run +against the GET and POST variables in the request; file filters is the same, +only for files uploaded. Each function will be passed the request module +and a dictionary when it is called; each will be called once for GET and once for POST +with each new request. +

    +These hooks exist because the request dictionaries should not be modified +in an ad-hoc manner; these allow you to set an application-wide policy +in a well-defined manner. You might, for instance, disallow all file uploads +over 1 MB. +

    +Here's an example that calls either Html.clean +or Html.escape (not shown) to ensure that no potentially harmful html can +be injected in user-editable areas of a site: + +
    + examples/filter.py +
    + + + +
    +def htmlFilter(request, d):
    +  # note that spoofing __htmlfields doesn't help attacker get unsafe html in;
    +  # we always call either clean() or escape().
    +  try:
    +    # don't use request['__htmlfields'], or you will recurse infinitely
    +    toClean = request._post['__htmlfields'][0].split(',')
    +  except KeyError:
    +    toClean = []
    +  for key in d:
    +    if key in toClean:
    +      d[key] = [Html.clean(s) for s in d[key]]
    +    else:
    +      d[key] = [Html.escape(s) for s in d[key]]
    +
    +
    + +
    +
    + +

    +The request module provides the following methods: +

      +
    • login_id:
      +Returns the id generated by your validator function if the user has logged in +via spy:login or spy:login_required, or None if the user is unvalidated. (See the +core tag library for details on +the login tags.) +
    • uri( [component] ):
      Returns the request URI, or some +component thereof. If the optional component parameter is specified, +it should be one of the following strings: +'scheme', +'location', +'path', +'parameters', +'query' or +'fragment'. +
    • +

    • method():
      Returns request method type (GET, POST, +...)
    • +

    • query():
      Returns the request query string
    • +

    • get( [name], [default], [ignoreCase] ):
      Returns request GET +information. If name is specified then a single list of values is +returned if the parameter exists, or default, which defaults to an +empty list, +if the parameter does not exist. Parameters without values are skipped, +though empty string values are allowed. If name is omitted, then a +dictionary of lists is returned. If ignoreCase is true, then the +above behaviour is performed in a case insensitive manner (all parameters +are treated as lowercase).
    • +

    • get1( [name], [default], [ignoreCase] ):
      Returns request GET +information, similarly to (though slightly differently from) the function +above. If name is specified then a single string is returned if the +parameter exists, or default, which default to None, if the parameter +does not exist. If there is more than one value for a parameter, then only +one is returned. Parameters without values are skipped, though empty string +values are allowed. If name is omitted, then a dictionary of strings is +returned. If the optional ignoreCase flag is true, then the above +behaviour is performed in a case insensitive manner (all parameters are +treated as lowercase).
    • +

    • post( [name], [default], [ignoreCase] ):
      Returns request +POST information. If name is specified then a single list of values +is returned if the parameter exists, or default, which defaults to +an empty list, if the parameter does not exist. Parameters without values are +skipped, though empty string values are allowed. If name is omitted, then a +dictionary of lists is returned. If ignoreCase is true, then the +above behaviour is performed in a case insensitive manner (all parameters +are treated as lowercase). This function understands form information +encoded either as 'application/x-www-form-urlencoded' or +'multipart/form-data'. Uploaded file parameters are not included in this +dictionary; they can be accessed via the file method.
    • +

    • post1( [name], [default], [ignoreCase] ):
      Returns request +POST information, similarly to (though slightly differently from) the +function above. If name is specified then a single string is returned +if the parameter exists, or default, which defaults to None, if the +parameter does not exist. If there is more than one value for a parameter, +then only one is returned. Parameters without values are skipped, though +empty string values are allowed. If name is omitted, then a dictionary of +strings is returned. If the optional ignoreCase flag is true, then +the above behaviour is performed in a case insensitive manner (all +parameters are treated as lowercase). This function understands form +information encoded either as 'application/x-www-form-urlencoded' or +'multipart/form-data'. Uploaded file parameters are not included in this +dictionary; they can be accessed via the file method.
    • +

    • file( [name], [ignoreCase] ):
      Returns files POSTed in the +request. If name is specified then a single cgi.FieldStorage class is +returned if such a file parameter exists, otherwise None. If name is +omitted, then a dictionary of file entries is returned. If the optional +ignoreCase flag is true, then the above behaviour is performed in a +case insensitive manner (all parameters are treated as lowercase). The +interesting fields of the FieldStorage class are:

      +

        +
      • name: the field name, if specified; otherwise None
      • +
      • filename: the filename, if specified; otherwise None; this is +the client-side filename, not the filename in which the content is stored +- a temporary file you don't deal with +
      • value: the value as a string; for file uploads, this +transparently reads the file every time you request the value +
      • file: the file(-like) object from which you can read the data; +None if the data is stored a simple string +
      • type: the content-type, or None if not specified +
      • type_options: dictionary of options specified on the +content-type line +
      • disposition: content-disposition, or None if not specified +
      • disposition_options: dictionary of corresponding options +
      • headers: a dictionary(-like) object (sometimes rfc822.Message +or a subclass thereof) containing *all* headers +

      +

    • __getitem__( key ):
      The request module can be used as a +dictionary: i.e. request['foo']. This method first calls the get1() method, +then the post1() method and lastly the file() method trying to find the +first non-None value to return. If no value is found, then this method +returns None. Note: Throwing an exception seemed too strong a semantics, and +so this is a break from Python. One can also iterate over the request +object, as if over a dictionary of field names in the get1 and post1 +dictionaries. In the case of overlap, the get1() dictionary takes +precedence.
    • +

    • getpost( [name], [default], [ignoreCase] ):
      Using given +parameters, return get() result if not None, otherwise return post() result +if not None, otherwise default.
    • +

    • getpost1( [name], [default], [ignoreCase] ):
      Using given +parameters, return get1() result if not None, otherwise return post1() +result if not None, otherwise default.
    • +

    • postget( [name], [default], [ignoreCase] ):
      Using given +parameters, return post() result if not None, otherwise return get() result +if not None, otherwise default.
    • +

    • postget1( [name], [default], [ignoreCase] ):
      Using given +parameters, return post1() result if not None, otherwise return get1() +result if not None, otherwise default.
    • +

    • env( [name], [default] ):
      Returns a dictionary with CGI-like +environment information of this request. If name is specified then a +single entry is returned if the parameter exists, otherwise default, +which defaults to None, if omitted.
    • +

    • getHeader( [type] ):
      Return a specific header sent by the +browser. If optional type is omitted, a dictionary of all headers is +returned.
    • +

    • filename( [path] ):
      Return the Spyce filename of the request +currently being processed. If an optional path parameter is provided, +then that path is made relative to the Spyce filename of the request +currently being processed.
    • +

    • stack( [i] ):
      Returns a stack of files processed by the +Spyce runtime. If i is provided, then a given frame is returned, +with negative numbers wrapping from the back as per Python convention. +The first (index zero) item on the stack is the filename +corresponding to the URL originally requested. The last (index -1) item +on the stack is the current filename being processed. Items are added +to the stack by includes, +Spyce lambdas, and +internal redirects.

      +

    • default( value, value2 ):
      (convenience method) Return +value if it is not None, otherwise return value2.
    • +

    +The example below presents the results of all the method calls list above. Run +it to understand the information available.

    + +
    + examples/request.spy +
    + +
    <html><body>
    +  Using the Spyce request object, we can obtain 
    +  information sent along with the request. The 
    +  table below shows some request methods and their 
    +  return values. Use the form below to post form 
    +  data via GET or POST. <br>
    +  <hr>
    +  [[-- input forms --]]
    +  <form action="[[=request.uri('path')]]" method=get>
    +    get: <input type=text name=name>
    +    <input type=submit value=ok>
    +  </form>
    +  <form action="[[=request.uri('path')]]" method=post>
    +    post: <input type=text name=name>
    +    <input type=submit value=ok>
    +  </form>
    +  <hr>
    +  [[-- tabulate response information --]]
    +  <table border=1>
    +    <tr>
    +      <td><b>Method</b></td>
    +      <td><b>Return value</b></td>
    +    </tr>
    +    [[ for method in ['uri()', 'uri("path")',
    +      'uri("query")', 'method()','query()',
    +      'get()','get1()', 'post()','post1()',
    +      'getHeader()','env()', 'filename()']: { 
    +    ]]
    +      <tr>
    +        <td valign=top>request.[[=method]]</td>
    +        <td>[[=eval('request.%s' % method)]]</td>
    +      </tr>
    +    [[ } ]]
    +  </table>
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    +Lastly, the following example shows how to deal with uploaded files.

    + +
    + examples/fileupload.spy +
    + +
    [[\ 
    +if request.post('ct'):
    +  response.setContentType(request.post1('ct'))
    +  if request.file('upfile')!=None:
    +    response.write(request.file('upfile').value)
    +  else:
    +    print 'file not properly uploaded'
    +  raise spyceDone
    +]]
    +<html><body>
    +  Upload a file and it will be sent back to you.<br>
    +  [[-- input forms --]]
    +  <hr>
    +  <table>
    +    <form action="[[=request.uri('path')]]" method=post 
    +        enctype="multipart/form-data">
    +      <tr>
    +        <td>file:</td>
    +        <td><input type=file name=upfile></td>
    +      </tr><tr>
    +        <td>content-type:</td>
    +        <td><input type=text name=ct value="text/html"></td>
    +      </tr><tr>
    +        <td><input type=submit value=ok></td>
    +      </tr>
    +    </form>
    +  </table>
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    +3.8.3. Response (implicit)

    + + +Like the request module, the response module is also loaded implicitly into every +Spyce environment. It provides the following methods: +

      +
    • write( string ):
      Sends a string to the client. All +writes are buffered by default and sent at the end of Spyce processing to +allow appending headers, setting cookies and exception handling. Note that +using the print statement is often easier, and +stdout is implicitly redirected +to the browser.
    • +

    • writeln( string ):
      Sends a string to the client, and +appends a newline.
    • +

    • writeStatic( string ):
      All static HTML strings are +emitted to the client via this method, which (by default) simply calls +write(). This method is not commonly invoked by the user.
    • +

    • writeExpr( object ):
      All expression results are emitted to +the client via this method, which (by default) calls write() with the str() +of the result object. This method is not commonly invoked by +the user.
    • +

    • clear( ): Clears the output buffer.
    • +

    • flush( ): Sends buffered output to the client immediately. This +is a blocking call, and can incur a performance hit.
    • +

    • setContentType( contentType ):
      Sets the MIME content +type of the response.
    • +

    • setReturnCode( code ):
      Set the HTTP return code for this +response. This return code may be overriden if an error occurs or by +functions in other modules (such as redirects).
    • +

    • addHeader( type, data, [replace] ):
      Adds the header line +"type: data" to the outgoing response. The +optional replace flag determines whether any previous headers of the +same type are first removed.
    • +

    • unbuffer():
      Turns off buffering on the output stream. In +other words, each write is followed by a flush(). An unbuffered output +stream should be used only when sending large amounts of data (ie. file +transfers) that would take up server memory unnecessarily, and involve +consistently large writes. Note that using an unbuffered response stream +will not allow the output to be cleared if an exception occurs. It will also +immediately send any headers.
    • +

    • isCancelled():
      Returns true if it has been detected that the +client is no longer connected. This flag will turn on, and remain on, after +the first client output failure. However, the detection is best-effort, and +may never turn on in certain configurations (such as CGI) due to buffering. +
    • +

    • timestamp( [t] ):
      Timestamps the response with an HTTP +Date: header, using the optional t +parameter, which may be either be the number of seconds since the epoch +(see Python time +module), or a properly formatted HTTP date string. If t is omitted, +the current time is used.
    • +

    • expires( [t] ):
      Sets the expiration time of the +response with an HTTP Expires: header, using the +optional t parameter, which may be either the number of seconds +since the epoch (see Python time +module), or a properly formatted HTTP date string. If t is omitted, +the current time is used.
    • +

    • expiresRel( [secs] ):
      Sets the expiration time of the +response relative to the current time with an HTTP Expires: header. The optional secs (which may +also be negative) indicates the number of seconds to add to the current time +to compute the expiration time. If secs is omitted, it defaults to zero. +
    • +

    • lastModified( [t] ):
      Sets the last modification time of +the response with an HTTP Last-Modified: header, +using the optional t parameter, which can be either the number +of seconds since the epoch (see Python time +module), or a properly formatted HTTP date string, or None indicating the +current time. If t is omitted, this function will default to the last +modification time of the Spyce file for this request, and raise an exception +if this time can not be determined. Note that, as per the HTTP +specification, you should not set a last modification time that is beyond +the response timestamp.
    • +

    • uncacheable():
      Sets the HTTP/1.1 Cache-Control: and HTTP/1.0 Pragma: headers to inform clients and proxies that this +content should not be cached.
    • +

    +The methods are self-explanatory. One of the more interesting things that one could do is +to emit non-HTML content types. The example below emits the Spyce logo as a GIF.

    + +
    + examples/gif.spy +
    + +
    [[.import name=include ]]
    +[[\
    +  # Spyce can also generate other content types
    +  # The following code displays the Spyce logo
    +  response.setContentType('image/gif')
    +  import os.path, spyce
    +  path = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'spyce.gif')
    +  response.write(include.dump(path, 1))
    +  raise spyceDone
    +]]
    +
    +
    +
    + + Run this code + +
    +

    +3.8.4. Redirect

    + + +The redirect module allows requests to be redirected to different pages, by +providing the following methods: +

      +
    • internal( uri ):
      Performs an internal redirect. All +processing on the current page ends, the output buffer is cleared and +processing continues at the named uri. +The browser URI remains +unchanged, and does not realise that a redirect has even occurred during +processing.
    • +

    • external( uri, [permanent] ):
      Performs an external redirect +using the HTTP Location header to a new uri. Processing of the +current file continues unless you raise spyceDone, +but the content is ignored (ie. the buffer is +cleared at the end). The status of the document is set to 301 MOVED +PERMANENTLY or 302 MOVED TEMPORARILY, depending on the permanent +boolean parameter, which defaults to false or temporary. The redirect +document is sent to the browser, which requests the new relative uri. +
    • +

    • externalRefresh( uri, [seconds] ):
      Performs an external +redirect using the HTTP Refresh header a new uri. Processing of the +current file continues, and will be displayed on the browser as a regular +document. Unless interrupted by the user, the browser will request the new +URL after the specified number of seconds, which defaults to zero if +omitted. Many websites use this functionality to show some page, while a +file is being downloaded. To do this, one would show the page using Spyce, +and redirect with an externalRefresh to the download URI. Remember to set +the Content-Type on the target download file page +to be something that the browser can not display, only download.
    • +

    +The example below, shows the possible redirects in use:

    + +
    + examples/redirect.spy +
    + +
    [[.import name=redirect]]
    +<html><body>
    +  [[ type = request['type']
    +     url = request['url']
    +     if url and not type: {
    +       ]] 
    +       <font color=red><b>
    +         please select a redirect type
    +       </b></font><br> 
    +       [[
    +     }
    +     if type and url: {
    +       if type=='internal': redirect.internal(url)
    +       if type=='external': redirect.external(url)
    +       if type=='externalRefresh': redirect.externalRefresh(url, 3)
    +       ]] Received POST info: [[=request.post1()]] [[
    +     }
    +  ]]
    +  <form action="[[=request.uri('path')]]" method=post>
    +    Redirection url:
    +    <input type=text name=url value=hello.spy><br>
    +    Redirection type:
    +    <table border=0>
    +      <tr><td>
    +        <input type=radio name=type value=internal>
    +        internal
    +      </td></tr>
    +      <tr><td>
    +        <input type=radio name=type value=external>
    +        external
    +      </td></tr>
    +      <tr><td>
    +        <input type=radio name=type value=externalRefresh>
    +        externalRefresh (3 seconds)
    +      </td></tr>
    +    </table>
    +    <input type=submit value=redirect>
    +  </form>
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    +3.8.5. Cookie

    + + +This module provides cookie functionality. Its methods are: +

      +
    • get( [key] ):
      Return a specific cookie string sent by the +browser. If the optional cookie key is omitted, a dictionary of all +cookies is returned. The cookie module may also be accessed as an +associative array to achieve the same result as calling: namely, cookie['foo'] and cookie.get('foo') are equivalent.
    • +

    • set( key, value, [expire], [domain], [path], [secure] ):
      +Sends a cookie to the browser. The cookie will be sent back on +subsequent requests and can be retreived using the get function. The +key and value parameters are required; the rest are optional. +The expire parameter determines how long this cookie information will +remain valid. It is specified in seconds from the current time. If expire is +omitted, no expiration value will be provided along with the cookie header, +meaning that the cookie will expire when the browser is closed. The +domain and path parameters specify when the cookie will get +sent; it will be restricted to certain document paths at certain domains, +based on the cookie standard. If these are omitted, then path and/or domain +information will not be sent in the cookie header. Lastly, the secure +parameter, which defaults to false if omitted, determines whether the cookie +information can be sent over an HTTP connection, or only via HTTPS.
    • +

    • delete( key ):
      Send a cookie delete header to the browser to +delete the key cookie. The same may be achieved by: del cookie[key].
    • +

    +The example below shows to manage browser cookies.

    + +
    + examples/cookie.spy +
    + +
    [[.import name=cookie]]
    +<html><body>
    +  Managing cookies is simple. Use the following forms 
    +  to create and destroy cookies. Remember to refresh 
    +  once, because the cookie will only be transmitted on 
    +  the <i>following</i> request.<br>
    +  [[-- input forms --]]
    +  <hr>
    +  <form action="[[=request.uri('path')]]" method=post>
    +    <table><tr>
    +      <td align=right>Cookie name:</td>
    +      <td><input type=text name=name></td>
    +      <td>(required)</td>
    +    </tr><tr>
    +      <td align=right>value:</td>
    +      <td><input type=text name=value></td>
    +      <td>(required for set)</td>
    +    </tr><tr>
    +      <td align=right>expiration:</td>
    +      <td><input type=text name=exp> seconds.</td>
    +      <td>(optional)</td>
    +    </tr><tr>
    +      <td colspan=3>
    +        <input type=submit name=operation value=set>
    +        <input type=submit name=operation value=delete>
    +        <input type=submit name=operation value=refresh>
    +      </td>
    +    </tr></table>
    +  </form>
    +  <hr>
    +  [[-- show cookies --]]
    +  Cookies: [[=len(cookie.get().keys())]]<br>
    +  <table>
    +    <tr>
    +      <td><b>name</b></td>
    +      <td><b>value</b></td>
    +    </tr>
    +    [[for c in cookie.get().keys(): {]]
    +      <tr>
    +        <td>[[=c]]</td>
    +        <td>[[=cookie.get(c)]]</td>
    +      </tr>
    +    [[ } ]]
    +  </table>
    +  [[-- set cookies --]]
    +  [[\
    +    operation = request.post('operation')
    +    if operation:
    +      operation = operation[0]
    +      name = request.post('name')[0]
    +      value = request.post('value')[0]
    +      if operation == 'set' and name and value:
    +        cookie.set(name, value)
    +      if operation == 'delete' and name:
    +        cookie.delete(name)
    +  ]]
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    +3.8.6. Session

    + + +Sessions allow information to be efficiently passed from one user request to +the next via some browser mechanism: get, post or cookie. Potentially large or +sensitive information is stored at the server, and only a short identifier is +sent to the client to be returned on callback. Sessions are often used to +create sequences of stateful pages that represent an application or work-flow. +

    +This module automates sessioning for a Spyce web site. It emulates +a dictionary specific to each user (really each browser) accessing +your web site. You simply use session as if it were a dictionary +variable, and its contents automatically change depending upon the +user calling the page. For example: + +
    + examples/session2.spy +
    + +
    [[.import name=session ]]
    +[[\
    +  session['visited'] = session.get('visited', 0) + 1
    +]]
    +
    +<spy:parent title="Session example" />
    +
    +You visited us [[= session['visited'] ]] times.
    +
    +
    +
    + + Run this code + +
    + +

    +In the example above, the 'visited' key would now be valid for +all pages on your site, until the session expires. +

    +Global session options +

    +These options are configured only in the Spyce config file: +

      +
    • session_store: declares the backing storage for session +information. It should be either session.DbmStore(path) or session.MemoryStore(). In-memory sessions storage is +faster, but volatile, and does not work in multi-process server +configurations. Advanced users are welcome to create their own storage +manager, by subclassing session.SessionStore.
    • +
    +Advanced users can create their own storage manager by subclassing +session.SessionStore. +

    +Per-session options +

    +These options are set in the Spyce config file, but may be overridden +at module-import time on a per-page basis: +

      +
    • session_path: the default path to attach the session to. +This refers to +cookie semantics. For example, if the path is /myapp, the session +will only be valid under /myapp pages (and below). The default is +'/', which means the session is valid site-wide. +
    • session_expire: the number of seconds the session is good for. +The default is one day. +

      +You should clean up expired session state periodically. The easiest way +is to schedule session.clean_store every day or so in your config file: + +

      +import session, scheduler
      +scheduler.schedule_daily(0, 0, session.clean_store)
      +
      + +

      +(Note: for backwards compatibility, there is also a module called "session1." +New code should simply use the module described here.) +3.8.7. Pool

      + + +The pool module provides support for server-pooled variables. That is support +for variables whose lifetime begins when declared, and ends when explicitly +deleted or when the server dies. These variables are often useful for caching +information that would be expensive to +compute from scratch for each request. Another common use of pool variables is to store +file- or memory-based lock objects for concurrency control. A pooled variable +can hold any Python value.

      +The pool module may be accessed as a regular dictionary, supporting the usual +get, set, delete, has_key, keys, values and clear operations. +

      +The example below shows how the module is used:

      + +
      + examples/pool.spy +
      + +
      [[.import names="pool"]]
      +<html><body>
      +  The pool module supports long-lived server-pooled objects,<br>
      +  useful for database connections, and other variables<br>
      +  that are expensive to compute.<br>
      +  [[\
      +    if 'foo' in pool:
      +      print 'Pooled object foo EXISTS.'
      +    else:
      +      pool['foo'] = 1
      +      print 'Pooled object foo CREATED.'
      +  ]]
      +  <br>
      +  Value: [[=pool['foo'] ]] <p>
      +</body></html>
      +
      +
      +
      + + Run this code + +
      +

      +

      +Pool performance suffers when not used with the Spyce webserver in +threaded concurrency mode; Spyce has to un/pickle the shared pool with +each request since there is no single long-lived process that can keep +the data in-memory. +3.8.8. Transform

      + + +The transform module contains useful text transformation functions, commonly +used during web-page generation.

      +

        +
      • html_encode( string, [also] ):
        Returns a HTML-encoded +string, with special characters replaced by entity references as +defined in the HTML 3.2 and 4 specifications. The optional also +parameter can be used to encode additional characters.
      • +

      • url_encode( string, ):
        Returns an URL-encoded string, +with special characters replaced with %XX equivalents as defined by the URI +RFC document.
      • +

      +The transform module also be used to intercept and insert intermediate +processing steps when response.writeStatic(), +response.writeExpr() and response.write() are called to emit +static html, expressions and dynamic content, respectively. It can be useful, +for example, to automatically ensure that expressions never produce output +that is HTML-unsafe, in other words strings that contain characters such as +&, < and >. Many interesting processing +functions can be defined. By default, the transform module leaves all output +untouched. These processing functions, called filters, can be inserted via the +following module functions:

      +

        +
      • static( [ fn ] ):
        Defines the processing performed on all +static HTML strings from this point forwards. The fn parameter is +explained below.
      • +

      • expr( [ fn ] ):
        Defines the processing performed on all the +results of all expression tags from this point forwards. The fn +parameter is explained below.
      • +

      • dynamic( [ fn ] ):
        Defines the processing performed on all +dynamic content generated, i.e. content generated using response.write in the +code tags. The fn parameter is explained below.
      • +

      +

      +Each of the functions above take a single, optional parameter, which specifies +the processing to be performed. The parameter can be one of the following +types: +

        +
      • None:
        If the paramter is None, or omitted, then no processing +is performed other converting the output to a string.
      • +

      • Function:
        If a parameter of function type is specified, then +that function is called to process the output. The function input can be any +Python type, and the function output may be any Python type. The result is +then converted into a string and emitted. The first parameter to a filter +will always be the object to be processed for output. However, the function +should be properly defined so as to possibly accept other parameters. The +details of how to define filters are explained below.
      • +

      • String:
        If a paramter of string type is specified, then the +string should be of the following format: "file:name", where file is the location where +the function is defined and name is the name of the filter. The file +component is optional, and is searched for using the standard module-finding +rules. If only the function name is specified, then the default location +(inside the transform module itself) is used, where the standard Spyce +filters reside. The standard Spyce filters are described below.
      • +

      • List / Tuple:
        If a parameter of list or tuple type is +specified, its elements should be functions, strings, lists or +tuples. The compound filter is recursively defined as +f=fn(...f2(f1())...), for the parameter +(f1,f2,...,fn). +
      • +

      +

      +Having explained how to install filters, we now list the standard Spyce +filters and show how they are used: +

        +
      • ignore_none( o ):
        Emits any input o except for None, +which is converted into an empty string.
      • +

      • truncate( o, [maxlen] ):
        If maxlen is specified, +then only the first maxlen characters of input o are returned, +otherwise the entire original.
      • +

      • html_encode( o, [also] ):
        Converts any '&', '<' and +'>' characters of input o into HTML entities for safe inclusion in +among HTML. The optional also parameter can specify, additional +characters that should be entity encoded.
      • +

      • url_encode( o ):
        Converts input o into a URL-encoded +string.
      • +

      • nb_space( o ):
        Replaces all spaces in input o with +"&nbsp;".
      • +

      • silence( o ):
        Outputs nothing.
      • +

      +

      +The optional parameters to some of these filters can be passed to the various +write functions as named parameters. They can also be specified in an +expression tag, as in the following example. (One should simply imagine that +the entire expression tag is replaced with a call to response.writeExpr). +
      + +
      [[.import name=transform]]
      +[[ transform.expr(("truncate", "html_encode")) ]]
      +[[='This is an unsafe (< > &) string... '*100, maxlen=500]] 
      +
      +

      +In the example above, the unsafe string is repeated 100 times. It is then +passed through a truncate filter that will accept +only the first 500 characters. It is then passed through the html_encode filter that will convert the unsafe +characters into their safe, equivalent HTML entities. The resulting string is +emitted.

      +The parameters (specified by their names) are simply accepted by the +appropriate write method (writeExpr() in the case above) and passed along to +the installed filter. Note that in the case of compound filters, the +parameters are passed to ALL the functions. The html_encode filter is +written to ignore the maxlen parameter, and does not fail.

      +For those who would like to write their own filters, looking at the definition +of the truncate filter will help. The other standard filters are in modules/transform.py. +
      + +
      def truncate(o, maxlen=None, **kwargs):
      +
      +

      +When writing a filter, any function will do, but it is strongly advised to +follow the model above. The important points are: +

        +
      • The input o can be of any type, not only a string.
      • +
      • The function result does not have to be string either. It is +automatically stringified at the end.
      • +
      • The function can accept parameters that modify its behaviour, such +as maxlen, above.
      • +
      • It is recommended to provide convenient user defaults for all +parameters.
      • +
      • The last parameter should be **kwargs so that unneeded parameters +are quietly passed along.
      • +
      +

      +Lastly, one can retrieve filters. This can be useful when creating new +functions that depend on existing filters, but can not be compounded using the +tuple syntax above. For example, one might use one filter or another +conditionally. For whatever purpose, the following module function is provided +to retreive standard Spyce filters, if needed:

      +

        +
      • create( [ fn ] ):
        Returns a filter. The fn parameter +can be of type None, function, string, list or tuple and is handled as in +the installation functions discussed above.
      • +

      +The transform module is flexible, but not complicated to use. The example +below is not examplary of typical use. Rather it highlights some of the +flexibility, so that users can think about creative uses.

      + +
      + examples/transform.spy +
      + +
      [[.import name=transform]]
      +[[\
      +def tag(o, tags=[], **kwargs):
      +  import string
      +  pre = string.join(map(lambda x: '<'+x+'>',tags))
      +  tags.reverse()
      +  post = string.join(map(lambda x: '</'+x+'>',tags))
      +  return pre+str(o)+post
      +def bold(o, _tag=tag, **kwargs):
      +  kwargs['tags'] = ['b']
      +  return apply(_tag, (o,), kwargs)
      +def bolditalic(o, _tag=tag, **kwargs):
      +  kwargs['tags'] = ['b','i']
      +  return apply(_tag, (o,), kwargs)
      +myfilter = transform.create(['html_encode', bolditalic])
      +mystring = 'bold and italic unsafe string: < > &'
      +def simpletable(o, **kwargs):
      +  s = '<table border=1>'
      +  for row in o:
      +    s=s+'<tr>'
      +    for cell in row:
      +      s=s+'<td>'+str(cell)+'</td>'
      +    s=s+'</tr>'
      +  s = s+'</table>'
      +  return s
      +]]
      +<html><body>
      +  install an expression filter:<br>
      +  [[transform.expr(['html_encode', tag])]]
      +  1.[[=mystring, tags=['b','i'] ]]
      +  <br>
      +  [[transform.expr(myfilter)]]
      +  2.[[=mystring]]
      +  [[transform.expr()]]
      +  <p>
      +  or use a filter directly:<br>
      +  1.[[=transform.create(['html_encode',tag])(mystring,tags=['b','i'])]]
      +  <br>
      +  2.[[=myfilter(mystring)]]
      +  <p>
      +  Formatting data in a table...<br>
      +  [[=simpletable([ [1,2,3], [4,5,6] ])]]
      +  <p>
      +  Though the transform module is flexible, <br>
      +  most users will probably only install the <br>
      +  <b>html_encode</b> filter.
      +</body></html>
      +
      +
      +
      + + Run this code + +
      +

      +3.8.9. Compress

      + + +The compress module supports dynamic compression of Spyce output, and can save +bandwidth in addition to static compaction. The different forms +of compression supported are described below. +

        +
      • spaces( [ boolean ] ):
        Controls dynamic space compression. +Dynamic space compression will eliminate consecutive whitespaces (spaces, +newlines and tabs) in the output stream, each time it is flushed. The optional +boolean parameter defaults to true.

        +

      • gzip( [ level ] ):
        Applies gzip compression to the Spyce +output stream, but only if the browser can support gzip content encoding. Note +that this function will fail if the output stream has already been flushed, +and should generally only be used with buffered output streams. The optional +level parameter specifies the compression level, between 1 and 9 +inclusive. A value of zero disables compression. If level is omitted, the +default gzip compression level is used. This function will automatically check +the request's Accept-Encoding header, and set the response's +Content-Encoding header.

        +

      +The example below shows the compression module in use.

      + +
      + examples/compress.spy +
      + +
      [[.import name=compress args="gzip=1, spaces=1"]]
      +[[\
      +  response.write('<html><body>')
      +  response.write('  Space compression will remove these     spaces.<br>')
      +  response.write('  gzip compression will highly compress this:<br>')
      +  for i in range(1000):
      +    response.write('  hello')
      +  response.write('</body></html>')
      +]]
      +
      +
      +
      + + Run this code + +
      +

      +Note that the compression functions need not be called at the beginning of the +input, but before the output stream is flushed. Also, to really see what is +going on, you should telnet to your web server, and provide something like the +following request. + +
      + +
      GET /spyce/examples/compress.spy HTTP/1.1
      +Accept-Encoding: gzip
      +
      +
      +

      +3.8.10. Include

      + + +Many websites carry a theme across their various pages, which is often +achieved by including a common header or footer. This is best done with a +parent template from the spy:parent tag, +but you can also do this with the include module for backwards compatibility +with Spyce 1.x. +

      +Another option to consider for repeating a common task is +a custom +active tag. +

      +The include module can also pretty print Spyce code or include the contents of +anything in your filesystem. +

        +
      • spyce( file, [context] ):
        Dynamically includes the specified +file (corresponding to the Spyce document root, not filesystem), +and processes it as Spyce code. The return value is that of the +included Spyce file. One can optionally provide a context value to +the included file. If omitted, the value defaults to None. All currently +imported modules are passed along into the included file without +re-initialization. However, for each explicit [[.import ]] tag in the included file, a new +module is initialized and also finalized up at the end of processing. The +include module provides three fields for use inside included files:

        +

          +
        • include.context: This field stores the value passed in at the +point of inclusion. Note that if the value is one that is passed by +reference (as is the case with object, list, and dictionary types), then +the context may be used to pass information back to the including file, in +addition to the return value.
        • +

        • include.vars: If the include context is of type dictionary, +then the vars field is initialized, otherwise it is None. The vars field +provides attribute-based access to the context dictionary, merely for +convenience. In other words, include.vars.x is +equivalent to include.context['x'].
        • +

        +Note that either the locals() or globals() dictionaries may be passed in as +include contexts. However, be advised that due to Python optimizations of +local variable access, any updates to the locals() dictionary may not be +reflected in the local namespace under all circumstances and all versions of +Python. In fact, this is the reason why the context has been made explicit, +and does not simply grab the locals() dictionary. It may, however, safely be +used for read access. With respect to the globals() dictionary, it is not +advised to pollute this +namespace.
      • +

      • spyceStr( file, [context] ):
        Same as spyce(), but +performs no output and instead returns the processed included Spyce file as +a string.
      • +

      • dump( file, [binary] ):
        Contents of the file +(from the filesystem -- use spyceUtil.url2file(url, request.filename) +if you need to turn a url into a filesystem path) +are returned. If the binary parameter is true, the file is opened in +binary mode. By default, text mode is used. +

        Be careful not to blindly trust the user to specify which file +to dump, since anything your Spyce process has access to in the filesystem +is fair game. +

      • +

      • spycecode( file ):
        Contents of the file +(relative to the Spyce document root) +are returned +as HTML formatted Spyce code.
      • +

      +The example below (taken from this documentation file), uses a common header +template only requiring two context variables to change the title and the +highlighted link:
      + +
      + +
        [[.import name=include]]
      +  [[include.spyce('inc/head.spi', 
      +      {'pagename': 'Documentation', 
      +       'page': 'manual.html'}) ]]
      +
      +

      + +In head.spi, we use this information to set the title:

      + +
      + +
      +  [[.import name=include]]
      +  <title>[[=include.context['pagename'] ]]</title>
      +
      +
      +

      + +By convention, included files are given the extension .spi.

      +Below we contrast the difference between static and dynamic includes. A +dynamic include is included on each request; a static include is inserted at +compile time. A static include runs in the same context, while a dynamic +include has a separate context.

      + +
      + examples/include.spy +
      + +
      [[.import name=include]]
      +<html><body>
      +  main file<br>
      +  <hr>
      +  [[
      +    context = {'foo': 'old value'}
      +    result=include.spyce('include.spi', context)
      +  ]]
      +  <hr>
      +  main file again<br>
      +  context: [[=context]]<br>
      +  return value: [[=result]]<br>
      +</body></html>
      +
      +
      +
      + + Run this code + +
      +

      + +
      + examples/include.spi +
      + +
      begin include<br>
      +context: [[=include.context ]]<br>
      +from: [[=request.stack()[-2] ]]<br>
      +foo was [[=include.vars.foo]]<br>
      +setting foo to 'new value' [[include.vars.foo = 'new value']]<br>
      +returing 'retval'<br>
      +end include<br>
      +[[ return 'retval' ]]
      +
      +
      +
      +

      + +
      + examples/includestatic.spy +
      + +
      <html><body>
      +  [[x=1]]
      +  main file<br>
      +  x=[[=x]]<br>
      +  <hr>
      +  [[.include file="includestatic.spi"]]
      +  <hr>
      +  main file again<br>
      +  x=[[=x]]
      +</body></html>
      +
      +
      +
      + + Run this code + +
      +

      + +
      + examples/includestatic.spi +
      + +
      begin included file<br>
      +changing value of x<br>
      +[[x=2]]
      +end included file<br>
      +
      +
      +
      +

      +3.8.11. Internal modules

      + + +These modules are used internally by Spce. Documentation is included for +those curious about Spyce internals; ordinarily you will never use these +modules directly. + +The error module is implicitly loaded and provides error-handling +functionality. An error is any unhandled runtime exception that +occurs during Spyce processing. This mechanism does not include +exceptions that are not related to Spyce processing (i.e. server-related +exceptions), that can be caused before or after Spyce processing by invalid +syntax, missing files and file access restrictions. To install a server-level +error handler use a configuration +file. The default page-level error handler can also be modified in the configuration file. This module +allows the user to install page-level error handling code, overriding the +default page-level handler, by using one of the following functions:

      +

        +
      • setStringHandler( string ):
        Installs a function that will +processes the given string, as Spyce code, for error handling. +
      • +

      • setFileHandler( uri ):
        Installs a function that will +processes the given uri for error handling.
      • +

      • setHandler( fn ):
        Installs the fn function for error +handling. The function is passed one parameter, a reference to the error +module. From this, all the error information as well as references to other +modules and Spyce objects can be accessed.
      • +

      +The error module provides the following information about an error:

      +

        +
      • isError():
        Returns whether an error is being handled. +
      • +

      • getMessage():
        Return the error message; the string of the +object that was raised, or None if there is no current error.
      • +

      • getType():
        Return the error type; the type of the object +that was raised, or None if there is no current error.
      • +

      • getFile():
        Return the file where the error was raised, or +None if there is no current error.
      • +

      • getTraceback():
        Return the stack trace as an array of +tuples, or None if there is no current error. Each tuple entry is of the +form: (file, line numbers, function name, code context).
      • +

      • getString():
        Return the string of the entire error (the +string representation of the message, type, location and stack trace), or +None if there is no current error.
      • +

      +The default error handling function uses the following string handler: +
      +
      
      +[[.module name=transform]]
      +[[transform.expr('html_encode')]]
      +<html>
      +<title>Spyce exception: [[=error.getMessage()]]</title>
      +<body>
      +<table cellspacing=10 border=0>
      +  <tr><td colspan=2><h1>Spyce exception</h1></td></tr>
      +  <tr><td valign=top align=right><b>File:</b></td><td>[[=error.getFile()]]</tr>
      +  <tr><td valign=top align=right><b>Message:</b></td>
      +    <td><pre>[[=error.getMessage()]]</pre></tr>
      +  <tr><td valign=top align=right><b>Stack:</b></td><td>
      +    [[\
      +      L = list(error.getTraceback())
      +      L.reverse()
      +    ]]
      +    [[ for frame in L: { ]]
      +      [[=frame[0] ]]:[[=frame[1] ]], in [[=frame[2] ]]:<br>
      +      <table border=0><tr><td width=10></td><td>
      +        <pre>[[=frame[3] ]]</pre>
      +      </td></tr></table>
      +    [[ } ]]
      +    </td></tr>
      +</table>
      +</body></html>
      +
      +
      +

      +The example below shows the error module in use. Error handling can often be +used to send emails notifying webmasters of problems, as this example shows. +

      + +
      + examples/error.spy +
      + +
      [[error.setFileHandler('error.spi') ]]
      +This is a page with an error...
      +[[ raise 'an error' ]]
      +
      +
      +
      + + Run this code + +
      +

      + +
      + examples/error.spi +
      + +
      <h1>Oops</h1>
      +An error occurred while processing your request. 
      +We have logged this for our webmasters, and they 
      +will fix it shortly. We apologize for the inconvenience.
      +In the meantime, please use the parts of our site that 
      +actually do work... <a href="somewhere">somewhere</a>.
      +[[\
      +  # could redirect the user immediately
      +  #response.getModule('redirect').external('somewhere.spy')
      +
      +  # could send an email
      +  import time
      +  msg = '''
      +time: %s
      +error: %s
      +env: %s
      +other info...
      +''' % (
      +    time.asctime(time.localtime(time.time())), 
      +    error.getString(),
      +    request.env()
      +  )
      +  #function_to_send_email('webmaster@foo.com', msg)
      +
      +  #or perform other generic error handling...
      +]]
      +
      +
      +
      +

      +This mechanism is not a subsititute for proper exception handling within the +code itself, and should not be abused. It does, however, serve as a useful +catch-all for bugs that slip through the cracks.

      + +The stdout module is loaded implicitly and redirects Python's sys.stdout (in a thread-safe manner) to the appropriate +response object for the duration of Spyce processing. This allows one to use +print, without having to write print >> response, .... The stdout +module provides a variable stdout.stdout, which +refers to the original stream, but is unlikely to be needed. It may also be +useful to know that sys.stderr is, under many +configurations, connected to the webserver error log.

      +In addition, the stdout module provides the following functions for capturing +or redirecting output: +

        +
      • push( [filename] ):
        Begin capturing output. Namely, the current +output stream is pushed onto the stack and replaced with a memory buffer. An +optional filename may be associated with this operation (see pop() +method below).
      • +

      • pop():
        Close current output buffer, and return the captured +output as a string. If a filename was associated with the push(), then the +string will also be written to that file.
      • +

      • capture(f, [*args], [**kwargs] ):
        Push the current stream, +call the given function f with any supplied arguments *args +and keyword arguments **kwargs, and then pop it back. Capture returns +a tuple (r,s), where r is the result returned by f and s is a string of its +output.
      • +

      +The example below show how the module is used: + +
      + examples/stdout.spy +
      + +
      <html><body>
      +  [[ print '''Using the stdout module redirects 
      +    stdout to the response object, so you can use
      +    <b>print</b>!''']]<br>
      +  redirecting stdout can be used to...
      +  [[stdout.push()]]
      +  [[print 'capture']] out[[='put']]
      +  [[cached = stdout.pop()]]
      +  ... for later: <br>
      +  [[=cached]]
      +</body></html>
      +
      +
      +
      + + Run this code + +
      + + +This module is used internally by Spyce, not usually by users. Documentation is +included for those curious about Spyce internals. The spylambda module is +loaded implicitly and allows the definition of functions based on Spyce +scripts; see Spyce Lambdas. The +spylambda module provides the following methods: +

        +
      • define( args, code, [memoize] ):
        Returns a function that +accepts the given args and executes the Spyce script defined by the +code parameter. Note that the code is compiled immediately and that +spyce.spyceSyntaxError or spyce.spycePythonError exceptions can be thrown for +invalid code arguments. The optional memoize parameter sets whether +the spyce can or can not be memoized, with the default being false. +Memoizing a function means capturing the result and output and caching them, +keyed on the function parameters. Later, if a function is called again with +the same parameters, the cached information is returned, if it exists, and +the function may not actually be called. Thus, you should only memoize +functions that are truly functional, i.e. they do not have side-effects: +they only return a value and output data to the response object, and their +behaviour depends exclusively on their parameters. If you memoize code that +does have side-effects, those side-effects may not occur on every +invocation.
      • +

      • __call__( args, code, _spyceCache ):
        This is an alias to the +define function. Because of the special method name, the spylambda module +object can be called as +if it were a function.
      • +

      + +The taglib module is loaded implicitly and supports +Active Tags +functionality. The taglib module provides the following +methods: +
        +
      • load( libname, [libfrom], [libas] ):
        Loads a tag library +class named libname from a file called libfrom in the search +path, and installed it under the tag prefix libas. The default for +libfrom is libname.py. The default for +libas is libname. Once installed, a library +name is its unique tag prefix.
      • +

      • unload( libname ):
        Unload a tag library that is installed +under the libname prefix. This is usually performed only at the end +of a request.
      • +

      • tagPush( libname, tagname, pair ):
        Push a new tag object for +a libname:tagname tag onto the tag stack. The pair +parameter is a flag indicating whether this is a singleton or a paired tag. +
      • +

      • tagPop():
        Pop the current tag from the tag stack.
      • +

      • getTag():
        Return the current tag object.
      • +

      • outPush():
        Begin capturing the current output stream. This +is usually called by the tagBegin method.
      • +

      • outPopCond():
        End capturing the current output stream, and +return the captured contents. It will only "pop" once, even if called +multiple times for the same tag. This method is usually called by either the +tagEnd(), tagCatch, or tagPop() methods.
      • +

      • tagBegin( attrs ):
        This method sets the tag output and +variable environment, and then calls the tag's begin() method with +the given attrs tag attribute dictionary. This method returns a flag, +and the tag body must be processed if and only if this flag is true.
      • +

        +

      • tagBody():
        This method sets the tag output and variable +environment, and then calls the tag's body() method with the captured +output of the body processing. If this method returns true, then the +processing of the body must be repeated.
      • +

      • tagEnd():
        This method sets the tag output and variable +environment, and then calls the tag's end() method. This method must +be called if the tagBegin() method completes successfully in order to +preserve tag semantics.
      • +

      • tagCatch():
        This method should be called if any of the +tagBegin, tagBody or tagEnd methods raise an exception. It calls the tag's +catch() method with the current exception.
      • +

      + +3.8.12. Writing Modules

      + + +Writing your own Spyce modules is simple. +

      +A Spyce modules is simply a Python class that exposes specific methods +to the Spyce server. The most important are start, finish, +and init. With these, +a Spyce module may access the +internal request and response structures or alter the behaviour of the +runtime engine in some way. +

      Let us begin with a basic example +called myModule. It is a module that implements one function named foo(). + +
      + examples/myModule.py +
      + + + +
      +from spyceModule import spyceModule
      +
      +class myModule(spyceModule):
      +  def foo(self):
      +    print 'foo called'
      +
      +
      +
      + +
      +
      +

      +Saving this code in myModule.py in the same +directory as the Spyce script, or somewhere on the module path, we could use +it as expected:

      +
      + +
      [[.import name=myModule]]
      +[[ myModule.foo() ]]
      +
      +
      +

      +A Spyce module can be any Python class that derives from +spyceModule.spyceModule. When it is loaded, Spyce assigns it a +__file__ attribute indicating its source location. +Do not override the __init__(...) +method because it is inherited from spyceModule and has an fixed signature +that is expected by the Spyce engine's module loader. The inherited method +accepts a Spyce API object, a Bastion +of spyce.spyceWrapper, an internal engine object, and stores it in +self._api. This is the building block for all the functionality that +any module provides. The available API methods of the wrapper are (listed in +spyceModule.spyceModuleAPI): +

        + +
      • getFilename: Return filename of current Spyce
      • + +
      • getCode: Return processed Spyce (i.e. Python) code
      • + +
      • getCodeRefs: Return python-to-Spyce code line references
      • + +
      • getModRefs: Return list of import references in Spyce code
      • + +
      • getServerObject: Return unique (per engine instance) server object
      • + +
      • getServerGlobals: Return server configuration globals
      • + +
      • getServerID: Return unique server identifier
      • + +
      • getModules: Return references to currently loaded modules
      • + +
      • getModule: Get module reference. The module is dynamically loaded and initialised + if it does not exist (ie. if it was not explicitly imported, but requested + by another module during processing)
      • + +
      • setModule: Add existing module (by reference) to Spyce namespace (used for includes)
      • + +
      • getGlobals: Return the Spyce global namespace dictionary
      • + +
      • registerModuleCallback: Register a callback for modules change
      • + +
      • unregisterModuleCallback: Unregister a callback for modules change
      • + +
      • getRequest: Return internal request object
      • + +
      • getResponse: Return internal response object
      • + +
      • setResponse: Set internal response object
      • + +
      • registerResponseCallback: Register a callback for when internal response changes
      • + +
      • unregisterResponseCallback: Unregister a callback for when internal response changes
      • + +
      • spyceString: Return a spyceCode object of a string
      • + +
      • spyceFile: Return a spyceCode object of a file
      • + +
      • spyceModule: Return Spyce module class
      • + +
      • spyceTaglib: Return Spyce taglib class
      • + +
      • setStdout: Set the stdout stream (thread-safe)
      • + +
      • getStdout: Get the stdout stream (thread-safe)
      • + +

      +For convenience, one can sub-class the spyceModulePlus class instead of +the regular spyceModule. The spyceModulePlus defines a +self.modules field, which can be used to acquire references to other +modules loaded into the Spyce environment. The response module, for +instance, would be referenced as self.modules.response. Modules are +loaded on demand, if necessary. The spyceModulePlus also contains a +self.globals field, which is a reference to the Spyce global namespace +dictionary, though this should rarely be needed.

      +Note: It is not expected that many module writers will need the entire +API functionality. In fact, the vast majority of modules will use a small +portion of the API, if at all. Many of these functions are included for just +one of the standard Spyce modules that needs to perform some esoteric +function.

      +Three Spyce module methods, start(), init([args]) and +finish(error) are special in that they are automatically called by the +runtime during Spyce request startup, processing and cleanup, respectively. +The modules are started in the order in which module directives appear in the +file, before processing begins. The implicitly loaded modules are always +loaded first. The init method is called during Spyce processing at the +location of the module directive in the file, with the optional args attribute +is passed as the arguments of this call. Finally, after Spyce processing is +complete, the modules are finalized in reverse order. If there is an unhandled +exception, it will be wrapped in a spyce.spyceException object and passed as +the first parameter to finish(). During successful completion of Spyce +processing (i.e. without exception), the error parameter is None. The default +inherited start, init and finish methods from spyceModule are noops.

      +Note 2: When writing a Spyce module, consider carefully why you are +selecting a Spyce module over a regular Python module. If it is just code, +that does not interact with the Spyce engine, then a regular Python import instead of an Spyce [[.import]] can just as easily bring in the necessary +code, and is preferred. In other words, choose a Spyce module only when there +is a need for per-request initialization or for one of the engine APIs.

      +Module writers are encouraged to look at the existing standard modules as +examples and the definitions of the core Spyce objects in spyce.py as well. If you write or use a novel Spyce +module that you think is of general use, please email your +contribution, +or a link to it. Also, please keep in mind that the standard modules are designed +with the goal of being minimalist. Much functionality is readily available +using the Python language libraries. If you think that they should be +expanded, also please send a note.

      + +3.9. Tags

      + + +The previous chapter discussed the Spyce module facility, the standard Spyce +modules and how users can create their own modules to extend Spyce. Spyce +functionality can also be extended via active tags, which are defined in tag +libraries. This chapter describes what Spyce active tags are, and how they are +used. We then describe each of the standard active tag libraries and, finally, +how to define new tags libraries.

      +It is important, from the outset, to define what an active tag actually does. +A few illustrative examples may help. The examples below all use tags that are +defined in the core tag library, +that has been installed under the spy prefix, as is the default.

      +

        +
      • <spy:parent src="parent.spi"/>
        +Wraps the current page in the parent template found at parent.spi in the +same directory. +
      • <spy:for items="=range(5)">
        +  <spy:print value="=foo"/>
        </spy:for>
        +
        As expected, these tags will print the value of foo, set to +bar above, 5 times.
      • +

      +Common mistake: Don't use [[= ]] to send values to active tag attributes: +[[= ]] sends its result directly to the output stream. And since those tokens +are parsed with higher precedence than the tag markup, Spyce won't recognize +your tag at all and will print it verbatim to the client. +Instead, prefix an expression with =, as in "=range(5)" above, and Spyce will +eval it before sending it to the tag. +

      +Note that the same output could have been achieved in many different ways, and +entirely without active tags. The manner in which you choose to organize your +script or application, and when you choose active tags over other +alternatives, is a matter of personal preference. Notice also that active tags +entirely control their output and what they do with their attributes and the +result of processing their bodies (in fact, whether the body of the tag is +even processed). Tags can even supply additional syntax constraints on their +attributes that will be enforced at compile-time. Most commonly a tag could +require that certain attributes exist, and possibly that it be used only as a +single or only as a paired (open and close) tag. Unlike early versions of +HTML, active tags must be strictly balanced, and this will be enforced by the +Spyce compiler.

      +Below, each individual standard Spyce tag library is documented, followed by a +description of how one would write a new +active tag library. The following general information will be useful for +reading that material. +

        +
      • Active tags are installed using the [[.taglib]] +directive, under some prefix. +Tag libraries may also be be loaded globally in the config module; by default +the core and form libraries are preloaded. +Active tags are of the format <pre:name ... >, where pre is the +prefix under which the tag library was installed, and name is defined +by the tag library. In the following tag library documentation, the prefix +is omitted from the syntax.
      • +
      • The following notation is used in the documentation of the tag libraries +below: +
          +
        • <name .../> : The tag should be used as a singleton.
        • +
        • <name ... > ... </name> : The tag should be used as an +open-close pair.
        • +
        • [ x (default)] : The attribute is optional. Attributes not enclosed in +brackets are required.
        • +
        • foo|bar : indicates that an attribute may be one of two +constant strings. The underlined value is the default.
        • +
        • string : an arbitrary string constant, never evaluated as Python
        • +
        • exprstring : may be a string constant, and may be of the form +'=expr', where expr is +a Python expression that will be evaluated in the tag context.
        • +
        • expr: a Python expression. (Currently only the "data" parameters +of some form and core tags use this rather than exprstring.) +
        • +
        • exports foo, *bar Exports to the parent context +the variable foo and the variable +with the name given by the expression bar. Normally, implementation +details of tags will not affect the parent context, so you do not have +to worry about your variables being clobbered. Tags may, however, +export specific parts of their own context to the parent. +See, for example, +the let and for tags in the core taglib. +Note: exporting of variables whose name +cannot be determined at compile time is deprecated, and will be removed in Spyce 2.2. +
        • +
        +

      +3.9.1. Core

      + + +The core tag library is aliased as "spy" by default. +

      +This library contains various frequently-used tags: the parent tag, login tags, +and some tags for generating lists and tables from Python iterators. + +

      +Parent Tag +

        +
      • <parent [src=url] [other parameters] /> +
        + Specifies a parent template to apply to the current page, which + is passed to the parent as child._body. Any extra parameters are also + passed in the child dictionary. If src is not given, 'parent.spi' used if it exists + in the current directory; otherwise, the default parent + is used as specified in the config module. +

        + +
        + examples/hello-templated.spy +
        + +
        <spy:parent title="Hello, world!" />
        +
        +[[-- Spyce can embed chunks of Python code, like this. --]]
        +[[\
        +  import time
        +  t = time.ctime()
        +]]
        +
        +[[-- 
        +  pull the count from the GET variable.
        +  the int cast is necessary since request stores everything as a string 
        +--]]
        +[[ for i in range(int(request.get1('count', 2))):{ ]]
        +<div>Hello, world!</div>
        +[[ } ]]
        +
        +The time is now [[= t ]].
        +
        +
        +
        + + Run this code + +
        + +

      + +Login Tags +
        +
      • <login [validator=function name] /> +
        + Generates a login form according to the template specified in your config file. + If validator is not specified, the default validator from your config file is used. + (validator may be the name of a function in a different Python module; + just prefix it with the module name and Spyce will automaticall import it when + necessary.) +

        + +
        + examples/login-optional.spy +
        + +
        <html><body>
        +
        +<f:form>
        +  [[ if request.login_id():{ ]]
        +    You are logged in with user id [[= request.login_id() ]]
        +    <spy:logout />
        +  [[ } else: { ]]
        +    <spy:login />
        +    You are not logged in.  (You may login with username/password: spyce/spyce.)
        +  [[ } ]]
        +  
        +  <p>
        +  (Here is some content that is the same before and after login.)
        +</f:form>
        +
        +</body></html>
        +
        +
        + + Run this code + +
        + + +

      • <logout /> +
        + Generates a logout button that will clear the cookie generated by login + and login_required. +

        + +

      • <login_required [validator=function name] /> +
        + If a valid login cookie is not present, generates a login form according to the + template specified in your config file, then halts execution of the current page. +

        + (You may log in to this example as user spyce, password spyce.) + +
        + examples/login-required.spy +
        + +
        <spy:parent title="Login example" />
        +
        +<f:form>
        +  <spy:login_required />
        +
        +  You are logged in.
        +  <spy:logout />
        +</f:form>
        +
        +
        +
        + + Run this code + +
        + +

      + +Convenience Tags +

      +These tags are shortcuts for creatings lists and tables. As with +the form tag library, any python +iterator may be given as the data parameter. Also as with the form tags, +any unrecognized parameters will be passed through to the generated HTML. + +

        +
      • <ul data=expr] /> +
        + Convenience tag for the common use of ul; equivalent to +
        +
        +    <ul>
        +    [[ for item in data:{ ]]
        +      <li>[[= item ]]</li>
        +    [[ } ]]
        +    </ul>
        +  
        +
        + +
      • <ol data=expr] /> +
        + Like ul, but for ordered lists.

        + +
      • <dl data=expr] /> +
        + Convenience tag for the common use of dl; equivalent to +
        +
        +  <dl>
        +  [[ for term, desc in data:{ ]]
        +    <dt>[[= term ]]</dt>
        +    <dd>[[= desc ]]</dd>
        +  [[ } ]]
        +  </dl>
        +  
        +
        + +
      • <table data=expr] /> +
        + Convenience tag for the common use of table; equivalent to +
        +
        +  <table>
        +  [[ for row in data:{ ]]
        +    <tr>
        +    [[ for cell in row:{ ]]
        +      <td>[[= cell ]]</td>
        +    [[ } ]]
        +    </tr>
        +  [[ } ]]
        +  </table>
        +  
        +
        + +
      + +3.9.2. Form

      + + +The form tag library is aliased as "spy" by default. +

      +This library simplifies the generation and handling of forms by +automating away repetitive tasks. Let's take a look at a simple example: + +
      + examples/formintro.spy +
      + +
      <spy:parent title="Form tag intro" />
      +
      +<f:form>
      +
      +<div class="simpleform">
      +  <f:text name="text1" label="Text input" default="change me" size=10 maxlength=30 />
      +  <f:text name="text2" label="Text input 2" value="change me" size=10 maxlength=30 />
      +</div>
      +
      +<fieldset style="clear: both">
      +  <legend>One or two?  Or both?</legend>
      +  <f:checkboxlist class="radio" name="checkboxlist" data="[(1, 'one'), (2, 'two')]" />
      +</fieldset>
      +
      +<div style="clear: both"><f:submit /></div>
      +
      +</f:form>
      +
      +
      + + Run this code + +
      + +

      This demonstrates several properties of Spyce form tags: +

        +
      • Most tags take an optional label parameter; this is turned into an HTML label tag +associated with the form element itself. +
      • If you View Source in your browser while running this sample, you can +see that Spyce generates an id with the same value as the name parameter. +(You can override this by explicitly specifying a different id parameter, +if you need.) +
      • You can pass arbitrary parameters (such as the class parameter for <f:form>) +to a Spyce form tag; parameters that do not have special meaning to Spyce +will be passed through to the HTML output. +
      • Try changing the form values and submitting. By default, Spyce automatically +remembers the user input for you, unless you give a tag a value +parameter (or selected for collection elements), which has highest precedence. +Note the different behavior of text1 and text2 in this example. +
      • Spyce provides some higher-level tags such as checkboxlist that result in multiple +elements at the HTML level. For these tags, a "data" parameter is expected, +which is always interpreted as a Python expression. +Any iterable may be used as data, including generators and generator expressions +for those with recent Python versions. Typically data would come from the +database; here we're just using a literal list. +
      +Handlers +

      +Active Handlers allow you to "attach" python functions to form submissions. +They are described in the +Active Handlers manual page. +

      +Reference +

      +First, some general rules: +

      The text displayed by a text-centric tag can come from one of three +places. In order of decreasing priority, these are +

        +
      • the value parameter +
      • the value submitted by the user is used +
      • the default parameter +
      +If none of these are found, the input will be empty. +

      For determining whether option, radio, and checkbox tags are checked or selected, +a similar process is followed, with +selected and checked parameters as the highest-priority source. +The same parameters are used for select, radiolist, and checkboxlist tags; +the only difference is for the collection tags, you can also specify +multiple values in a Python list (or other iterable) in either the +selected/checked or default parameters. +

      All tags except form and submit can be given names that tell Spyce how to +treat their submitted values when passing to an Active Handler function. +Adding ":int", ":float", ":bool", or ":list" is allowed. The first three +tell Spyce what kind of Python value to convert the submission to; ":list" +may be combined with these, or used separately, and tells Spyce to pass a +list of all values submitted instead of a single one. +(An example is given in the Active Handlers page.) +Finally, here is the list of tags: +

        +
      • <form +[method=exprstring] [action=exprstring] ...> </form> +
        Begin a new form. The method parameter defaults +to 'POST'. The action parameter defaults to the current page. +
      • +

      • +<submit +[handler=exprstring] [value=exprstring] ... /> + +
        Create a submit button. The value parameter is +emitted as the button text. If handler is given, Spyce will call the function(s) +it represents at the beginning of the page load after this button is clicked. +(Multiple function names may be separated with commas.) +

        +If the handler is in a different [python] module, Spyce will automatically import it +before use. +

        +A handler may take zero or more arguments. +For the first non-self argument (if present), Spyce always passes a +moduleFinder corresponding to the current +spyceWrapper object; it is customary to call this argument "api." +moduleFinder provides __getitem__ access to loaded modules; thus, +"api.request" would be the current request module. If a requested module +is not found, it is loaded. +

        +(You can also directly access the wrapper with api._wrapper, providing +access to anything module authors have, +but you will rarely if ever need to do this.) +

        +For other handler function parameters, Spyce will pass the values for the +corresponding form input, or None if nothing was found in the GET or POST +variables. +

        +See also the Active Handlers +language section for a higher-level overview. +

        +Limitation: currently, Active Handlers require resubmitting to the same spyce page; +of course, the handler method may perform an internal or external +redirect. +

      • +

      • <hidden +name=exprstring [value=exprstring] [default=exprstring] .../> +
        Create a hidden form field. The name parameter is evaluated and +emitted. +
      • +

      • <text +name=exprstring [value=exprstring] [default=exprstring]  .../> +
        Create a form text field. The name parameter is evaluated and +emitted. +
      • +

      • <date +name=exprstring [value=exprstring] [default=exprstring] [size=exprstring] [format=exprstring] .../> +
        Create a form text field with a javascript date picker. Format defaults to MM/DD/YYYY. Maxlength is always len(format); +this is also the default size, but size may be overridden for aesthetics. +
      • <password +name=exprstring [value=exprstring] [default=exprstring] [size=exprstring] [maxlength=exprstring] .../> +
        Create a form password field. Parameters are the same as for text +fields, explained above.
      • +

      • <textarea +name=exprstring [value=exprstring] [rows=exprstring] [cols=exprstring] ...>default</textarea> +
        Create a form textarea field. The name parameter is evaluated and +emitted. The value optional parameter is evaluated. A default +may be provided in the body of the tag. The value emitted is, in order of +decreasing priority: local tag value, value in submitted +request dictionary, local tag default. We search this list +for the first non-None value. The rows and cols optional +parameters are evaluated and emitted.
      • +

      • <radio +name=exprstring value=exprstring [checked] [default] .../> +
        Create a form radio-box. The name and value parameters are +evaluated and emitted. A checked and default flags affect +whether this box is checked. The box is checked based on the following +values, in decreasing order of priority: tag value, +value in submitted request dictionary, tag default. +We search this list for the first non-None value.
      • +

      • <checkbox +name=exprstring value=exprstring [checked] [default] .../> +
        Create a form check-box. Parameters are the same as for radio +fields, explained above.
      • +

      • <select +name=exprstring [selected=exprstring] [default=exprstring] [data=expr] ...>...</select> +
        Create a form select block. The name parameter is +evaluated and emitted. The optional data should be an iterable of +(description, value) pairs. +
      • +

      • <option +[text=exprstring] [value=exprstring] [selected] [default] .../> +
        <option +[value=exprstring] [selected] [default] ...>text</option> +
        Create a form selection option. This tag must be nested within a +select tag. The text optional parameter is evaluated and +emitted in the body of the tag. It can also be provided in the body of the +tag, as you might be used to seeing in HTML. +
      • +

      • <radiolist +name=exprstring data=expr [checked=exprstring] [default=exprstring] ...>...</select> +
        Create multiple radio buttons from data, which should be an iterable of +(description, value) pairs. +
      • +

      • <checkboxlist +name=exprstring data=expr [checked=exprstring] [default=exprstring] ...>...</select> +
        Create multiple checkboxes from data, which should be an iterable of +(description, value) pairs. +
      • +

      +Here is an example of all of these tags in use: + +
      + examples/formtag.spy +
      + +
      <spy:parent title="Form tag example" />
      +
      +<f:form>
      +
      +<h2>Primitive controls</h2>
      +
      +<div class="simpleform">
      +  <f:text name="mytext" label="Text" default="some text" size=10 maxlength=30 />
      +
      +  <f:password name="mypass" label="Password" default="secret" />
      +
      +  <f:textarea name="mytextarea" label="Textarea" default="rimon" rows=2 cols=50></f:textarea>
      +
      +  <label for="mycheck">Checkbox</label><f:checkbox name=mycheck value=check1 />
      +
      +  <label for="myradio1">Radio option 1</label><f:radio name=myradio value=option1 />
      +  <label for="myradio2">Radio option 2</label><f:radio name=myradio value=option2 />
      +</div>
      +
      +<div style="clear: both">
      +  <h2 style="padding-top: 1em;">Compound controls</h2>
      +  [[-- a simple data source for the compound controls -- in practice
      +	     this would probably come from the database --]]
      +  [[ L = [('option %d' %i, str(i)) for i in range(5)] ]]
      +
      +  <fieldset>
      +    <legend>Radiolist</legend>
      +    <f:radiolist class=radio name=radiolist data="L" default="3" />
      +  </fieldset>
      +
      +  <fieldset>
      +    <legend>Checkboxlist</legend>
      +    <f:checkboxlist class=radio name=checkboxlist data="L" default="=['0', '1']" />
      +  </fieldset>
      +
      +  <fieldset>
      +    <legend>Select</legend>
      +    <f:select name=myselect multiple size=5 data="L" default="2" />
      +  </fieldset>
      +
      +  <fieldset>
      +    <legend>Date</legend>
      +    <f:date name=mydate />
      +  </fieldset>
      +
      +  <h2 style="clear:both; padding-top: 1em;">Test it!</h2>
      +  <input type="submit" name="foo" value="Submit!">
      +
      +</f:form>
      +
      +
      +
      +
      + + Run this code + +
      + +3.9.3. Active Handlers

      + + +Active Handlers allow you to "attach" python functions to Spyce form submissions. +Instead of old-fashioned inspection of the request environment, Spyce will +pull the parameters required by your function's signature out for you. +Let's have a look at an example. Here, we define a calculate +function and assign it to be the handler for our submit input: + +
      + examples/handlerintro.spy +
      + +
      [[!
      +def calculate(self, api, x, y):
      +    self.result = x * y
      +]]
      +
      +<spy:parent title="Active Handler example" />
      +<f:form>
      +    <f:text name="x:float" default="2" label="first value" />
      +    <f:text name="y:float" default="3" label="second value" />
      +
      +    <f:submit handler="self.calculate" value="Multiply" />
      +</f:form>
      +
      +<p>
      +Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]]
      +</p>
      +
      +
      +
      + + Run this code + +
      +

      +

        +
      • +Handlers may be inline, as the calculate handler is here, or in a +separate Python module. When using a handler from a Python module, Spyce +will automatically import the module when needed. (Handler parameters are +strings, not Python references.) The todo demo +demonstrates using handlers this way. +
      • You can give your form inputs a data type; Spyce will perform the +conversion automatically before passing parameters to your handler function. +
      +

      +Active Handlers also make it easy to incorporate user-friendly error messages +into your forms simply by raising a HandlerError: + +
      + examples/handlervalidate.spy +
      + +
      [[!
      +def calculate(self, api, x):
      +    from spyceException import HandlerError
      +    try:
      +        x = float(x)
      +    except ValueError:
      +        raise HandlerError('Value', 'Please input a number')
      +    if x < 0:
      +        raise HandlerError('Value', 'Cannot take the square root of negative numbers!')
      +    self.result = x ** 0.5
      +]]
      +
      +<spy:parent title="Active Handler Validation" />
      +<f:form>
      +    <f:text name="x" default="-1" label="Value:" />
      +    <f:submit handler="self.calculate" value="Square root" />
      +</f:form>
      +
      +<p>
      +Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]]
      +</p>
      +
      +
      +
      + + Run this code + +
      + +

      +You can show multiple errors at once with a CompoundHandlerError: + +
      + examples/handlervalidate2.spy +
      + +
      [[!
      +def errors(self, api):
      +    from spyceException import HandlerError, CompoundHandlerError
      +    cve = CompoundHandlerError()
      +    cve.add(HandlerError('One', 'First error'))
      +    cve.add(HandlerError('Two', 'Second error'))
      +    if cve:
      +        raise cve
      +]]
      +
      +<spy:parent title="Active Handler Validation 2" />
      +<f:form>
      +    <f:submit handler="self.errors" value="Show me some errors" />
      +</f:form>
      +
      +
      +
      + + Run this code + +
      + +

      +All Spyce modules are available via the api handler parameter (which +should always be the first parameter (after self in a class). Here +is an example that uses the db module: + +
      + examples/db.spy +
      + +
      <spy:parent title="To-do demo" />
      +
      +[[!
      +def list_new(self, api, name):
      +    if api.db.todo_lists.selectfirst_by(name=name):
      +        raise HandlerError('New list', 'a list with that description already exists')
      +    api.db.todo_lists.insert(name=name)
      +    api.db.flush()
      +]]
      +
      +(This is an self-contained example using the same database as the
      +<a href=/demos/to-do/index.spy>to-do demo</a>.)
      +
      +<h2>To-do lists</h2>
      +
      +[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]]
      +<spy:ul data="[L.name for L in lists]" />
      +
      +<h2>New list</h2>
      +<f:form>
      +<f:submit value="New list" handler=self.list_new />:
      +<f:text name=name value="" />
      +</f:form>
      +
      +
      +
      + + Run this code + +
      + +

      +Handlers in Active Tags allow you to +create reusable components, as in the the chat demo. +

      +(Since Spyce captures stdout, you can use print to debug handlers.) +3.9.4. Writing Tag Libraries

      + +Creating your own active tags is quite easy and this section explains how. You +may want to create your own active tags for a number of reasons. More advanced +uses of tags include database querying, separation of business logic, or +component rendering. On the other hand, you might consider creating simpler +task-specific tag libraries. For example, if you do not wish to rely on +style-sheets you could easily define your own custom tags to perform the +formatting in a consistent manner at the server. Another convenient use for +tags is to automatically fill forms with session data. These are only a few of +the uses for tags. As you will see, writing a Spyce active tag is far +simpler than writing a JSP tag. +

      +(The chatbox demo gives an example of an active tag.) +

      +Tag libraries must be placed in a separate file from request-handling Spyce pages. +The following +directives +apply specifically to tag library definition: +

        +
      • +[[.tagcollection ]] : +
        Indicates that the current file will be an Active Tag library. Must +be at the start of the file. +
      • +[[.begin name=string [buffers=True|False] [singleton=True|False] [kwattrs=string] ]] : +
        +Begin defining a tag named name. Optional attributes: +
          +
        • buffers: if true, Spyce will evaluate the code between the begin and end tags +and pass it to the tag as the variable _content. For instance, the following +simplistic tag makes its contents bold: + +
          + examples/tagbold.spi +
          + +
          [[.begin name=bold buffers=True]]
          +
          +<b>[[=_contents]]</b>
          +
          +[[.end]]
          +
          +
          +
        • singleton: if true, Spyce will not allow paired use of the tag (<tag></tag>) +and only allow singleton use (<tag />). If false, the reverse is true. +
        • kwattrs: the name of the dict in which to place attributes not specified with [[.attr]] +directives. If not given, Spyce will raise an error if unexpected attributes are seen. +
        +
      • +[[.attr name=string [default=string] ]] : +
        Specify that the current tag being defined expects an attribute named name. +If a default string is given, the attribute is optional. (Dynamic attributes +may be accepted with the kwattrs option in the begin directive.) (If the default +string is prefixed with '=', it will be evaluated as python code at runtime.) +
      • +[[.export var=string [as=string] ]] : +
        Specifies that the variable from the tag context named var will be +exported back to the calling page. The optional as attribute may be used +to give the variable a different name in the calling context. +
      • +[[.end ]] : +
        Ends definition of the current tag. +
      +Active tags may specify handlers +as in normal Spyce code; this may be done inline +with class chunks, or as a reference +to a separate .py module. This allows building reusable components easily! +Again, the chatbox demo demonstrates this. +

      +(Be careful if you take the class chunk approach with handlers, since all class chunks that get used +in a given page are pulled into the same namespace. By convention, tag handlers +defined in reusable tags are prefixed with the tag name, e.g., chatbox_addline.) +

      +Active tags should not contain f:form active tags; this needs to be done +by the .spy page for the Spyce compiler to link up Active Handlers correctly. +

      +One limitation of using the Active Tag directives described here is that tags +within a single collection may not call each other. Usually, you can work +around this by defining common code inside a .py module and importing that. +If this is not an option, you can create Active Tags manually. This is +described in the next section. +3.9.5. Writing Tag Libraries the hard way

      + + +You can still write tag libraries manually if the tag declaration language doesn't +give you the tools you need. (About the only functionality it doesn't currently +expose is the looping ability used in spy:for.) You may wish to approximate +what you want with a new-style tag library +and examine the compiler's output (spyceCmd.py -c). +

      +We begin with a basic example:

      + +
      + examples/myTaglib.py +
      + + + +
      +from spyceTag import spyceTagLibrary, spyceTagPlus
      +
      +class tag_foo(spyceTagPlus):
      +  name = 'foo'
      +  mustend = 1
      +  def syntax(self):
      +    self.syntaxPairOnly()
      +    self.syntaxNonEmpty('val')
      +  def begin(self, val):
      +    self.getOut().write('<font size="%s"><b>' % str(val))
      +  def end(self):
      +    self.getOut().write('</b></font><br>')
      +
      +class myTaglib(spyceTagLibrary):
      +  tags = [
      +    tag_foo, 
      +  ]
      +
      +
      +
      + +
      +
      +

      +Saving this code in myTaglib.py, in the same +directory as your script or anywhere else in the search path, one could then +use the foo active tag (defined above), as follows:

      + +
      + examples/tag.spy +
      + +
      [[.taglib name=myTaglib as=me]]
      +<html><body>
      +[[ for x in range(2, 6):{ ]]
      +  <me:foo val="=x">size [[= x ]]</me:foo>
      +[[ } ]]
      +</body></html>
      +
      +
      +
      + + Run this code + +
      +

      +An active tag library can be any Python class that derives from +spyceTag.spyceTagLibrary. The interesting aspects of this class +definition to implementors are:

      +

        +
      • tags:
        This field is usually all that requires redefinition. +It should be a list of the classes (as opposed to instances) of the +active tags.
      • +

      • start():
        This methd is invoked by the engine upon loading +the library. The inherited method is a noop.
      • +

      • finish():
        This method is invoked by the engine upon +unloading the library after a request. The inherited method is a noop.
      • +

        +

      +Each active tag can be any Python class that derives from +spyceTag.spyceTag. The interesting aspects of the class definition for +tag implementors are:

      +

        +
      • name:
        This field MUST be overidden to indicate the name of +the tag that this class defines.
      • +

      • buffer:
        This flag indicates whether the processing of the +body of the tag should be performed with the current output stream +(unbuffered) or with a new, buffered output stream. Buffering is useful for +tags that may want to transform, or otherwise use, the output of processing +their own bodies, before it is sent to the client. The inherited default is +false.
      • +

      • conditional:
        This flag indicates whether this tag may +conditionally control the execution of its body. If true, then the begin() +method of the tag must return true to process the tag body, or false to skip +it. If the flag is set to false, then return value of the begin() method is +ignored, and the body executed (unless an exception is triggered). Some +tags, such as the core:if tag, require this +functionality, and will set the flag true. Many other kinds of tags do not, +thus saving a level of indentation (which is unfortunately limited in Python +-- hence the need for this switch). The inherited default is false.
      • +

        +

      • loops:
        This flag indicates whether this tag may want to loop +over the execution of its body. If true, then the body() method of the tag +must return true to repeat the body processing, or false to move on to the +end() of the tag. If the flag is set to false, then the return value of the +body() method is ignored, and the body is not looped. Some tags, such as the +core:for tag, require this functionality, and +will set the flag true. Many other kinds of tags do not, thus saving a level +of indentation. The inherited default is false.
      • +

      • catches:
        This flag indicates whether this tag may want to +catch any exceptions that occur during the execution of its body. If true, +then the catch() method of the tag will be invoked on exception. If the flag +is false, the exception will continue to propagate beyond this point. Some +tags, such as the core:catch, require this +functionality, and will set the flag true. Many other kinds of tags do not, +thus saving a level of indentation. The inherited default is false.
      • +

        +

      • mustend:
        This flag indicates whether this tag wants the +end() method to get called, if the begin() completes successfully, no +matter what. In other words, the call to end() is placed in the finally +clause of +the try-finally block which begins just after the begin(). This is useful for +tag cleanup. However, many tags do not perform anything in the +end() of their tag, or perhaps perform operations that are not important in +the case of an exception. Such tags do not require this functionaliy, thus +saving a level of indentation. The inherited default is false.
      • +

      • syntax():
        This method is invoked at compile time to perform +any additional tag-specific syntax checks. The inherited method returns +None, which means that there are no syntax errors. If a syntax error is +detected, this function should return a string with a helpful message about +the problem. Alternatively, one could raise an +spyceTagSyntaxException.
      • +

      • begin( ... ):
        This method is invoked when the corresponding +start tag is encountered in the document. All the attributes of the tag are +passed in by name. This method may return a boolean flag. If +conditional is set to true (see above), then this flag indicates +whether the body of the tag should be processed (true), or skipped (false). +The inherited method performs no operation, except to return true.
      • +

      • body( contents ):
        This method is invoked when the body of +the tag has completed processing. It will be called also for a +singleton, which we assume simply has an empty body. However, it will not be +called if the begin() method has chosen to skip body processing entirely. If +the tag sets buffer to true for capturing the body processing output +(see above), then the string output of the body processing has been captured +and stored in contents. It is the responsibility of this method to +emit something, if necessary. If the tag does not buffer then +contents will be None, and any output has already been written to the +enclosing scope. If the loops flag is set to true, then this method +is expected to return a boolean flag. If the flag is true, then the body +will be processed again, followed by another invocation of this method. And +again, and again, until false is received. The inherited tag method performs +nothing and returns false.
      • +

      • end():
        This method is invoked when the corresponding end tag +is encountered. For a singleton tag, we assume that the end tag immediately +follows the begin, and still invoke this method. If the mustend flag +has been set to true, then the runtime engine semantics ensure that if the +begin method terminates successfully, this method will get called, +even in the case of an exception during body processing. The inherited +method is a noop.
      • +

      • catch( ex ):
        If the catches flag has been set to +true, then if any exception occurs in the begin(), body() or end() methods +or from within the body processing, this method will be invoked. The +parameter ex holds the value that was thrown. The inherited method +simply re-raises the exception.
      • +

      • getPrefix():
        Return the prefix under which this tag library +was installed.
      • +

      • getAttributes():
        Return a dictionary of tag attributes. +
      • +

      • getPaired():
        Return true if this is a paired (open and +close) tag, or false if it is a singleton.
      • +

      • getParent( [name] ):
        Return the object of the direct parent +tag, or None if this is the root active tag. Plain (inactive) tags do not +have objects associated with them in this hierarchy. If the optional +name parameter is provided, then we search up the tree for an active +tag of the same library and with the given name. If such a tag can not be +found, then return None.
      • +

      • getOut():
        Return the (possibly buffered) output stream that +this tag should write to.
      • +

      • getBuffered():
        Returns true if the tag output stream is a +local buffer, or false if the output is connected to the enlosing scope. +
      • +

      +Note that Spyce goes to a lot of effort to avoid unnecessary indentation in +the code it generates for Active Tags. +A limitation of the Python runtime is that the level of indentation +within any method is limited by a +compile-time constant. You can change it, of course, but in most +Python distributions as of this writing (Feb 2005), this is currently set to 100. +See for instance +this thread +from python-bugs. +

      +For convenience, tag implementors may wish to derive their implementations +from spyceTagPlus, which provides some useful additional methods: +

        +
      • getModule( name ):
        Return a reference to a module +from the page context. The module is loaded, if necessary.
      • +

      • syntaxExist( [must]* ):
        Removed in 2.0; +Spyce now checks the signature of the begin method; arguments +with no default are enforced as required automatically.

        +

      • syntaxNonEmpty( [names]* ):
        Ensure that if the attributes +listed in names exist, then each of them does not contain an empty +string value. Otherwise, a spyceTagSyntaxException is thrown. Note that the +actual existence of a tag is checked by syntaxExists(), and that this method +only checks that a tag is non-empty. Specifically, there is no exception +raised from this method, if the attribute does not exist.
      • +

      • syntaxValidSet( name, validSet ):
        Ensure that the value of +the attribute name, if it exists, is one of the values in the set +validSet. Otherwise, a spyceTagSyntaxException is raised.
      • +

      • syntaxPairOnly():
        Ensure that this tag is a paired tag. +Otherwise, a spyceTagSyntaxException is thrown.
      • +

      • syntaxSingleOnly():
        Ensure that this tag is a singleton tag. +Otherwise, a spyceTagSyntaxException is thrown.
      • +

      +Despite the length of this description, most tags are trivial to write, as +shown in the initial example. The easiest way to start is by having at a look +at various implemented tag libraries, such as tags/core.py. The more curious reader is welcome to look +at the tag library internals in spyceTag.py and +modules/taglib.py. The tag semantics are ensured by +the Spyce compiler (see spyceCompile.py), though it +is likely easier simply to look at the generated Python code using the "spyce -c" command-line facility.

      + +3.10. Installation

      + + +Spyce can be installed and used in many configurations. Hopefully, one of the +ones below will suit your needs. If not, feel free to email the lists asking +for assistance in using a different setup. If you have successfully set up +Spyce by some other method, please also email us the details. +Finally, if you had troubles following these instructions, +please send suggestions on how to improve them.

      +3.10.1. Overview

      + + +Spyce (the core engine and all the standard modules) currently requires +Python version 2.3 or greater. Spyce uses no version-specific Apache features.

      +Spyce supports operation through +its own webserver, FastCGI, mod_python, Apache proxy, CGI, and the command-line. +Some of these require configuration-specific tweaks. These are kept to an +absolute minimum, however; where possible, the configuration of the +Spyce engine is performed through the +Spyce configuration module. +The supported adapters are: +

        +
      • Web server: The preferred alternative is to serve Spyce files via +its built-in webserver. For production use, it is recommended to do this +as a proxy behind another server such as Apache that handles static content, +url rewriting, ssl, etc. +This is the best option if it is available to you, since you only +have a single process (with concurrency provided by multithreading) which +makes resource pooling (data, database handles, etc.) an easy way to help +your site scale. It also avoids the waste of loading your code into +multiple Python interpreters. +
      • +

      • FastCGI: +This is a CGI-like interface that +is relatively fast, because it does not incur the large process startup +overhead on each request.
      • +

      • mod_python: This is another relatively performant +way to serve up Spyce. +If this is that is your chosen Apache integration route, +make sure you can first get mod_python running on your system, before +adding Spyce to the mix. +
      • +

      • CGI: Failing these alternatives, you can always process requests +via regular CGI, but this alternative is the slowest option and is intended +primarily for those who do not have much control over their web environments. +
      • +

      • Command line: Spyce is useful as a command-line tool +for pre-processing Spyce pages and creating static HTML files. +This documentation, for instance, is produced this way. +
      • +

      • Others: Spyce abstracts its operating environment using a thin +abstraction layer. Spyce users have written small Spyce adapters for the +Xitami webserver and also to integrate with the Coil framework. Writing your +own adapter, should the need arise, is therefore a realistic possibility. +
      • +

      +The following sections assume that you have already downloaded and uncompressed Spyce. +3.10.2. Web Server

      + + +The preferred method of running Spyce it to run it as a web-server. +

      +The Spyce web server configuration is defined in the "webserver options" section +of the Spyce configuration module. +

        +
      • Start the Spyce web server. The command-line syntax for starting the +server is:
        spyceCmd.py -l +
        +Spyce will now be running on port 8000. You can change the port in your +spyceconf module. +
      • +
      • If you are going to proxy Spyce behind Apache (this is done automatically if you installed via RPM): +
          +
        1. Copy the proxy section ("Spyce via proxy") from spyceApache.conf into httpd.conf. +
        2. Make sure mod_proxy is installed and enabled. Check httpd.conf for a mod_proxy +section; look for instructions like "Uncomment the following lines to enable the proxy server." +
        3. Restart Apache
        4. +
        +
      • +
      +3.10.3. CGI/FastCGI installation

      + + +

        +
      • Ensure that you have Apache and FastCGI installed and functioning.
      • +
      • Create a symlink to the command-line executable:
        ln -sf /usr/share/spyce/run_spyceCmd.py /usr/bin/spyce +
        or wherever you have chosen to install it.
      • +
      • Copy the "Spyce via cgi or fcgi" lines from spyceApache.conf to your +/etc/httpd/conf/httpd.conf +file, and replace the XXX with the appropriate +path to your spyce installation. +
      • Restart Apache +
      +Alternative CGI configuration: +The alternative CGI configuration directs the webserver to execute the .spy +file itself, not the Spyce engine. This is appropriate if you do not control +Apache on the server you are installing to, e.g., low-end hosting +plans. Each .spy file should have execute +permissions for the web server, and the first line should be: +

      + + +

      +  #!/usr/bin/python /home/username/spyce/run_spyceCGI.py
      +
      + + +(Adjust to the correct Spyce installation path as necessary.) +Then add the following line to the httpd.conf, or to +the .htaccess file in the same directory. + + +
      +  AddHandler cgi-script spy
      +
      +
      + +Finally, ensure that the directory itself has the ExecCGI option enabled, +and set cgi_allow_only_redirect to +False in your Spyce configuration module. +For more details, please refer to the Apache documentation, specifically +ExecCGI option, +Directory, +Location, +AllowOverride and +Apache CGI documentation, +for more information on how to get a standard CGI setup working.

      +3.10.4. Mod_Python

      + + +mod_python is primarily recommended if you cannot get +FastCGI to work. (Some users have reported problems with FastCGI on Windows.) +

      +Before you try to install Spyce, first get mod_python to work! +You may have to compile from sources, and possibly do the same for Python. (See +the documentation at: mod_python.) +

      +To use Spyce via mod_python: +

        +
      • Copy the "Spyce via mod_python" lines from spyceApache.conf to your +/etc/httpd/conf/httpd.conf +file, and replace the XXX with the appropriate +path to your spyce installation. +
      • Restart Apache +
      +3.10.5. Notes for Windows

      + + +Besides your preferred installation option above, remember to +add the following line to Apache's httpd.conf:

      + + +

      +  ScriptInterpreterSource registry
      +
      + +

      +This assumes that Python has registered itself with the Windows registry to +run .py files. Otherwise, you can also omit this line, but make sure that the +first line of the run_spyceCGI.py file points to a +valid Python executable, as in: + + +

      +  #! c:/python24/python.exe
      +
      + +

      +If you are running using IIS on Windows, you can take a look at +how to +configure IIS or PWS for Python/CGI scripts.

      +The basics for getting IIS to work with Spyce are: +

        +
      • Start the IIS administration console. You get to it from the Control +Panel. Click on Administrative Tools, and then Internet +Services Manager.
      • +
      • Drill down to your Default Web Site. Right click and select +Properties.
      • +
      • Select the Home Directory tab, and click on the +Configuration... button near the bottom right.
      • +
      • Add an application mapping. On the executable line you should +type the equivalent of:
        "c:\program files\python22\python.exe" "c:\program files\spyce\spyceCGI.py". +
        Set the extension to .spy, or +whatever you like.
        Limit the Verbs to GET,POST.
        Select the Script engine and +Check that file exists check-boxes.
      • +
      • Click OK twice. Make sure to propagate these properties to all +sub-nodes. That is, click Select All and then OK once more. +
      • +
      • You should now be able to browse .spy files within your website. Note, +that this is a very slow mechanism, since it utilizes CGI and restarts the +Spyce engine on each request.
      • +
      • Using the Spyce proxy web server or installing FastCGI are much +advised for the vast majority of environments.
      • +
      +3.10.6. Notes on RPM installation

      + + +

        +
      • The RPM installation installs Spyce to /usr/share/spyce and +creates a /usr/bin/spyce as a symlink to spyceCmd.py. It also +configures Apache to forward .spy requests to the Spyce server on port 8000. +You're still responsible for starting the spyce server as described in the +Web Server configuration section. +
      • If you upgrading Spyce, it is recommended to uninstall the previous +version using rpm -e spyce, and then install a fresh copy, as opposed +to using the rpm -U option. +
      • Historical note to Redhat users: RH releases prior to 8.0 still used Python version +1.5, as many standard scripts depended on it. If you want to run Spyce with +some of the newer Python2 rpms, you will need to change top line of the run_spyceCmd.py, run_spyceCGI.py, run_spyceModpy.py and verchk.py +scripts, or reconfigure your path so that the default python is the version +that you want.

        +3.10.7. Notes on Apache integration

        + + +

          +
        • If you installed via RPM, Apache is configured to proxy .spy requests to the Spyce webserver. If you are running without the spyce standalone server, comment out those lines in httpd.conf (starting with "Spyce via proxy"). +
        • +
        • To avoid confusion, you may wish to change the "root" option in your +spyceconf module +to be the same as Apache's DocumentRoot. +
        • You may wish to copy the "Documentation alias" section from spyceApache.conf. +This will allow you to access the spyce documentation and examples +from http://localhost/spyce/, so you can test your install by browsing to +http://localhost/spyce/examples/hello.spy. +
        +3.10.8. Starting your first project

        + + +Spyce provides a simple method to create a new project. +This creates directories for your Spyce project, creates an initial +Spyce config file, +and sets up other resources. +

        +For example, running +

        python spyceProject.py /var/firstproject
        +results in the following directory structure + +
        +$ tree /var/firstproject/
        +    /var/firstproject/
        +    |-- config.py
        +    |-- lib
        +    |-- login_tokens
        +    `-- www
        +        |-- _util
        +        |   |-- form_calendar.gif
        +        |   `-- form_calendar.js
        +        |-- index.spy
        +        |-- parent.spi
        +        `-- style.css
        +
        + +Now you can simply run spyceCmd.py -l --conf /var/firstproject/config.py +and browse to http://localhost:8000/index.spy to verify that it worked. +

        +Notes: +

          +
        • The login_tokens directory is used by the spy:login tags; do not remove it. +
        • The lib/ directory (also initially empty) +is placed on the Python sys.path, meaning you can import any Python module in this +directory from any .spy file. It is good practice to put re-usable code +into .py files in this directory. +
        + +3.11. Programmatic Interface

        + + +It is also possible to embed Spyce into another program. All you need is to +run or embed + a Python interpreter. Although other entry points into the engine code +are possible, the most convenient entry points are in spyce.py: +

          +
        • spyceFileHandler( request, response, filename, [sig], [args], [kwargs], [config] )
          +
        • +

        • spyceStringHandler( request, response, code, [sig], [args], [kwargs], [config] )
          +
        • +

        +Either of these functions will execute some Spyce code within the context of some request object and send the output to the corresponding response object. spyceFileHandler gets the Spyce code to be executed from a file, while spyceStringHandler is passed Spyce code in a string. +3.11.1. Basic usage

        + + +For basic usage, the following arguments need to be understood: +

          +
        • request
          +is an object derived from spyce.spyceRequest, denoting the current HTTP request. +
        • +

        • response
          +is an object derived from spyce.spyceResponse, denoting the HTTP response resulting from the invocation. +
        • +

        • filename
          +is the name of a file which contains spyce source code (it will be read, compiled, cached and executed when spyceFileHandler is called). +
        • +

        • code
          +is a string which contains spyce source code (it will be executed when spyceStringHandler is called).
          +TODO: how is caching handled in this case? +
        • +

        • config
          +is an object which contains the configuration to be used. The normal usage is to have a configuration file which can be imported as a normal python module. The imported module then passed as conf. +
        • +

        + +3.11.1. Passing parameters

        + + +One may wish to also pass a parameter from the calling code into the name space used inside the Spyce code so that it may be accessed from Python Statements, Chunks, Expressions, etcetera. Something analogous to Python's own execfile built-in function where you can pass a locals and globals dictionary.

        +Spyce code to be invoked is dealt with similar to a function body, and hence one can send parameters to that "function" upon invocation. The arguments sig, args and kwargs are used to control such parameter passing. With sig, a signature for this external code is specified, with args and kwargs values for the actual arguments are passed. Thus, sig controls what can go into args and kwargs: +

          +
        • sig
          +is a string containing a function signature in usual python syntax, without surrounding brackets.
          +For example: 'x, y, a=None, b={}'. +
        • +

        • args
          +is a list of values, each value corresponding to a positional argument specified in sig, in order. +
        • +

        • kwargs
          +is a dictionary of name to value mappings, its keys should be in the list of keyword argument names specified in sig, its values provide actual values to be passed. +
        • +

        + +3.11.1. Customized Request/Response classes

        + + +explanation forthcoming; read the code for now, or send an email + +3.11.1. Example

        + + +Here's an example that demonstrates such programmatic usage:

        + +
        + examples/programmaticUsage.py +
        + + + +
        +import os
        +import spyce
        +
        +#--------------------------------------------------[ a hardcoded fake request ]
        +class TestRequest(spyce.spyceRequest):
        +    environment = {'QUERY_STRING': '',
        +            'REQUEST_METHOD': 'get'}
        +    headers = {}
        +    
        +    def env(self, name=None):
        +        if not name:
        +            return self.environment
        +        return self.environment[name]
        +    
        +    def getHeader(self, type=None):
        +        return self.headers[type]
        +    
        +    def getServerID(self):
        +        os.getpid()
        +
        +
        +#--------------------------------------------------[ a hardcoded fake response ]
        +class TestResponse(spyce.spyceResponse):
        +    returncode = spyce.spyceResponse.RETURN_OK
        +    out = ''
        +    err = ''
        +    headers = {}
        +    def write(self, s):
        +        self.out = self.out+s
        +    def writeErr(self, s):
        +        self.err = self.err+s
        +    def close(self):
        +        pass
        +    def clear(self):
        +        self.out = ''
        +    def sendHeaders(self):
        +        pass
        +    def clearHeaders(self):
        +        self.headers = {}
        +    def setContentType(self, content_type):
        +        self.headers['content-type'] = content_type
        +    def setReturnCode(self, code):
        +        self.returncode = code
        +    def addHeader(self, type, data, replace=0):
        +        self.headers[type] = data
        +    def flush(self, *args):
        +        pass
        +    def unbuffer(self):
        +        pass
        +
        +
        +#--------------------------------------------------[ invoking Spyce code ]
        +import spyceConfig
        +    
        +req = TestRequest()
        +resp = TestResponse()
        +
        +spyceCode  = 'Hello, the following names are defined: "[[print dir(),]]", and '
        +spyceCode += 'these were the parameters passed: [[print (a, b, c, d)]]\n',
        +
        +spyce.spyceStringHandler(
        +             req,
        +             resp,
        +             spyceCode,
        +             sig='a, b, c="asd", d=None',
        +             args=(1, 'two'),
        +             kwargs={'c': 'aa'},
        +             config=spyceConfig)
        +print resp.err
        +print resp.out
        +
        +
        + +
        +
        + + + +

        4. ADDENDA

        + + +List of appendices: +

          +
        • Performance - Some throughput +micro-benchmarks
        • +

        • History - The brief history +of Spyce
        • +

        +4.1. Performance

        + + +Although flexibility usually outweighs raw performance in the choice of +technology, it is nice to know that the technology that you have chosen is not +a resource hog, and can scale to large production sites. The current Spyce +implementation is comparable to its cousin technologies: JSP, PHP and ASP. We +ran a micro-benchmark using hello.spy and equivalents. All benchmark +files are available in the misc/benchmark +directory.

        + +
        + examples/hello.spy +
        + +
        <spy:parent title="Hello" />
        +[[ import spyce ]]
        +
        +Hello from Spyce version 
        +[[= spyce.__version__ ]]!
        +
        +
        +
        + + Run this code + +
        +

        +Spyce was measured under CGI, FCGI, mod_python and proxy configurations. For +calibration the static HTML, CGI-C, CGI-Python and FCGI-Python tests were +performed. In the case of CGI-C, the request is handled by a compiled C +program with the appropriate printf statements. In the case of CGI-Python, we +have an executable Python script with the appropriate print statements. +FCGI-Python is a similar script that is FCGI enabled. ASP was measured on a +different machine, only to satisfy curiosity; those results are omitted.

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        ConfigurationHello world!
        Spyce-modpython250
        modpython publisher300
        Spyce-proxy200
        JSP100
        PHP450
        Spyce-FCGI100
        Python-FCGI140
        Spyce-CGI8
        Python-CGI25
        C-CGI180
        Static HTML1500
        +

        +The throughput results (shown above in requests per second) were measured on a +Intel PIII 700MHz, with 128 MB of RAM and a 512 KB cache +running RedHat Linux 7.2 (2.4.7-10 kernel), Apache 1.3.22 and +Python 2.2 using loopback (http://localhost/...) requests. Since each of +the script languages requires an initial compilation phase (of which JSP seems +the longest), the server was warmed up with 100 requests before executing +1000 measured requests with a concurrency level of 3, using the +ab (Apache benchmark) tool. Figures are rounded to the nearest +25 requests/second.

        +Conclusion:All spyce configuration options except CGI +can handle large +websites, as the Spyce engine and cache persist between requests. The CGI +version takes a hit in recompiling Spyce files on every request. (This may be +alleviated using a disk-based Spyce cache (as opposed to the current +memory-based implementation).) +

        +4.2. History

        + + +The initial idea for a Python-based HTML scripting language came in May 1999, +a few months after I (Rimon) had first learned of Python, while working with JSP on +some website. The idea was pretty basic and I felt that someone was bound to +implement it sooner or later, so I waited. But, nobody stepped up to the task, +and the idea remained little more than a design in my head for two and a half +years. In early 2002, after having successfully used Python extensively for +various other tasks and gaining experience with the language, I began to +revisit my thoughts on a Python-based HTML scripting language, and by late +May 2002 the beta of version 1.0 was released.

        +Version 1.0 had support for standard features like get and post, +cookies, session management, etc. Development was still on-going, but Spyce +was mature and being used on live systems. Support of various features was +enhanced for about a week or two, and then a new design idea popped into my +head. Version 1.1 was the first modular release of Spyce. Lots of +prior functionality was shipped out of the core engine and into standard +modules. Many, many new modules and features were added. Spyce popularity rose +to the top percentile of SourceForge projects and the user base grew. +Version 1.2 represented a greatly matured release of Spyce. Spyce +got a totally revamped website and documentation, and development continued. +Version 1.3 introduced active tags. More performance work, more +modules, etc. +

        +In February 2005 Jonathan Ellis was hired by SilentWhistle to work on their +web application, written in Spyce. He began by overhauling the Active Tag code +and was soon deep in the guts of Spyce. Jonathan added Active Handlers, +parent templates, and the tag compiler on the way to Version 2.0. +

        +In July 2006 Version 2.1 introduced the Spyce login tags and +database integration via SQLAlchemy, as well as improvments to parameter +marshalling for Active Handlers. +

        +For more detail, please refer to the +change log. +As always, user feedback is welcome and appreciated.

        + + +

        +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-single.spy b/spyce-2.1/www/demo-site/docs/doc-single.spy new file mode 100755 index 0000000000000000000000000000000000000000..24bd0c5eee413d8789c8d682418f9227bfc528ae --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-single.spy @@ -0,0 +1,3 @@ +[[.import names=include]] +[[ singlepage = 1 ]] +[[.include file="doc.spy"]] diff --git a/spyce-2.1/www/demo-site/docs/doc-tag.html b/spyce-2.1/www/demo-site/docs/doc-tag.html new file mode 100755 index 0000000000000000000000000000000000000000..63ce4463e576dd81f9793de349f3b0f2f311b661 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-tag.html @@ -0,0 +1,305 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Tags
    +
    + + + + + + + +
    Prev: 3.8.12 - Writing ModulesUp: 3 - RuntimeNext: 3.9.1 - Core
    +
    +3.9. Tags

    + + +The previous chapter discussed the Spyce module facility, the standard Spyce +modules and how users can create their own modules to extend Spyce. Spyce +functionality can also be extended via active tags, which are defined in tag +libraries. This chapter describes what Spyce active tags are, and how they are +used. We then describe each of the standard active tag libraries and, finally, +how to define new tags libraries.

    +It is important, from the outset, to define what an active tag actually does. +A few illustrative examples may help. The examples below all use tags that are +defined in the core tag library, +that has been installed under the spy prefix, as is the default.

    +

      +
    • <spy:parent src="parent.spi"/>
      +Wraps the current page in the parent template found at parent.spi in the +same directory. +
    • <spy:for items="=range(5)">
      +  <spy:print value="=foo"/>
      </spy:for>
      +
      As expected, these tags will print the value of foo, set to +bar above, 5 times.
    • +

    +Common mistake: Don't use [[= ]] to send values to active tag attributes: +[[= ]] sends its result directly to the output stream. And since those tokens +are parsed with higher precedence than the tag markup, Spyce won't recognize +your tag at all and will print it verbatim to the client. +Instead, prefix an expression with =, as in "=range(5)" above, and Spyce will +eval it before sending it to the tag. +

    +Note that the same output could have been achieved in many different ways, and +entirely without active tags. The manner in which you choose to organize your +script or application, and when you choose active tags over other +alternatives, is a matter of personal preference. Notice also that active tags +entirely control their output and what they do with their attributes and the +result of processing their bodies (in fact, whether the body of the tag is +even processed). Tags can even supply additional syntax constraints on their +attributes that will be enforced at compile-time. Most commonly a tag could +require that certain attributes exist, and possibly that it be used only as a +single or only as a paired (open and close) tag. Unlike early versions of +HTML, active tags must be strictly balanced, and this will be enforced by the +Spyce compiler.

    +Below, each individual standard Spyce tag library is documented, followed by a +description of how one would write a new +active tag library. The following general information will be useful for +reading that material. +

      +
    • Active tags are installed using the [[.taglib]] +directive, under some prefix. +Tag libraries may also be be loaded globally in the config module; by default +the core and form libraries are preloaded. +Active tags are of the format <pre:name ... >, where pre is the +prefix under which the tag library was installed, and name is defined +by the tag library. In the following tag library documentation, the prefix +is omitted from the syntax.
    • +
    • The following notation is used in the documentation of the tag libraries +below: +
        +
      • <name .../> : The tag should be used as a singleton.
      • +
      • <name ... > ... </name> : The tag should be used as an +open-close pair.
      • +
      • [ x (default)] : The attribute is optional. Attributes not enclosed in +brackets are required.
      • +
      • foo|bar : indicates that an attribute may be one of two +constant strings. The underlined value is the default.
      • +
      • string : an arbitrary string constant, never evaluated as Python
      • +
      • exprstring : may be a string constant, and may be of the form +'=expr', where expr is +a Python expression that will be evaluated in the tag context.
      • +
      • expr: a Python expression. (Currently only the "data" parameters +of some form and core tags use this rather than exprstring.) +
      • +
      • exports foo, *bar Exports to the parent context +the variable foo and the variable +with the name given by the expression bar. Normally, implementation +details of tags will not affect the parent context, so you do not have +to worry about your variables being clobbered. Tags may, however, +export specific parts of their own context to the parent. +See, for example, +the let and for tags in the core taglib. +Note: exporting of variables whose name +cannot be determined at compile time is deprecated, and will be removed in Spyce 2.2. +
      • +
      +

    + +


    + + + + + +
    Prev: 3.8.12 - Writing ModulesUp: 3 - RuntimeNext: 3.9.1 - Core
    +

    +Sub-sections: +

    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-tag_core.html b/spyce-2.1/www/demo-site/docs/doc-tag_core.html new file mode 100755 index 0000000000000000000000000000000000000000..3c40cb8e9ff1b4f017b91c565f00275802c4ad13 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-tag_core.html @@ -0,0 +1,392 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Tags
    +
    + + + + + + + +
    Prev: 3.9 - TagsUp: 3.9 - TagsNext: 3.9.2 - Form
    +
    +3.9.1. Core

    + + +The core tag library is aliased as "spy" by default. +

    +This library contains various frequently-used tags: the parent tag, login tags, +and some tags for generating lists and tables from Python iterators. + +

    +Parent Tag +

      +
    • <parent [src=url] [other parameters] /> +
      + Specifies a parent template to apply to the current page, which + is passed to the parent as child._body. Any extra parameters are also + passed in the child dictionary. If src is not given, 'parent.spi' used if it exists + in the current directory; otherwise, the default parent + is used as specified in the config module. +

      + +
      + examples/hello-templated.spy +
      + +
      <spy:parent title="Hello, world!" />
      +
      +[[-- Spyce can embed chunks of Python code, like this. --]]
      +[[\
      +  import time
      +  t = time.ctime()
      +]]
      +
      +[[-- 
      +  pull the count from the GET variable.
      +  the int cast is necessary since request stores everything as a string 
      +--]]
      +[[ for i in range(int(request.get1('count', 2))):{ ]]
      +<div>Hello, world!</div>
      +[[ } ]]
      +
      +The time is now [[= t ]].
      +
      +
      +
      + + Run this code + +
      + +

    + +Login Tags +
      +
    • <login [validator=function name] /> +
      + Generates a login form according to the template specified in your config file. + If validator is not specified, the default validator from your config file is used. + (validator may be the name of a function in a different Python module; + just prefix it with the module name and Spyce will automaticall import it when + necessary.) +

      + +
      + examples/login-optional.spy +
      + +
      <html><body>
      +
      +<f:form>
      +  [[ if request.login_id():{ ]]
      +    You are logged in with user id [[= request.login_id() ]]
      +    <spy:logout />
      +  [[ } else: { ]]
      +    <spy:login />
      +    You are not logged in.  (You may login with username/password: spyce/spyce.)
      +  [[ } ]]
      +  
      +  <p>
      +  (Here is some content that is the same before and after login.)
      +</f:form>
      +
      +</body></html>
      +
      +
      + + Run this code + +
      + + +

    • <logout /> +
      + Generates a logout button that will clear the cookie generated by login + and login_required. +

      + +

    • <login_required [validator=function name] /> +
      + If a valid login cookie is not present, generates a login form according to the + template specified in your config file, then halts execution of the current page. +

      + (You may log in to this example as user spyce, password spyce.) + +
      + examples/login-required.spy +
      + +
      <spy:parent title="Login example" />
      +
      +<f:form>
      +  <spy:login_required />
      +
      +  You are logged in.
      +  <spy:logout />
      +</f:form>
      +
      +
      +
      + + Run this code + +
      + +

    + +Convenience Tags +

    +These tags are shortcuts for creatings lists and tables. As with +the form tag library, any python +iterator may be given as the data parameter. Also as with the form tags, +any unrecognized parameters will be passed through to the generated HTML. + +

      +
    • <ul data=expr] /> +
      + Convenience tag for the common use of ul; equivalent to +
      +
      +    <ul>
      +    [[ for item in data:{ ]]
      +      <li>[[= item ]]</li>
      +    [[ } ]]
      +    </ul>
      +  
      +
      + +
    • <ol data=expr] /> +
      + Like ul, but for ordered lists.

      + +
    • <dl data=expr] /> +
      + Convenience tag for the common use of dl; equivalent to +
      +
      +  <dl>
      +  [[ for term, desc in data:{ ]]
      +    <dt>[[= term ]]</dt>
      +    <dd>[[= desc ]]</dd>
      +  [[ } ]]
      +  </dl>
      +  
      +
      + +
    • <table data=expr] /> +
      + Convenience tag for the common use of table; equivalent to +
      +
      +  <table>
      +  [[ for row in data:{ ]]
      +    <tr>
      +    [[ for cell in row:{ ]]
      +      <td>[[= cell ]]</td>
      +    [[ } ]]
      +    </tr>
      +  [[ } ]]
      +  </table>
      +  
      +
      + +
    + +
    + + + + + +
    Prev: 3.9 - TagsUp: 3.9 - TagsNext: 3.9.2 - Form
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-tag_form.html b/spyce-2.1/www/demo-site/docs/doc-tag_form.html new file mode 100755 index 0000000000000000000000000000000000000000..0103c4afa25879b7a35830f0644bba631fbb4ace --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-tag_form.html @@ -0,0 +1,469 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Tags
    +
    + + + + + + + +
    Prev: 3.9.1 - CoreUp: 3.9 - TagsNext: 3.9.3 - Active Handlers
    +
    +3.9.2. Form

    + + +The form tag library is aliased as "spy" by default. +

    +This library simplifies the generation and handling of forms by +automating away repetitive tasks. Let's take a look at a simple example: + +
    + examples/formintro.spy +
    + +
    <spy:parent title="Form tag intro" />
    +
    +<f:form>
    +
    +<div class="simpleform">
    +  <f:text name="text1" label="Text input" default="change me" size=10 maxlength=30 />
    +  <f:text name="text2" label="Text input 2" value="change me" size=10 maxlength=30 />
    +</div>
    +
    +<fieldset style="clear: both">
    +  <legend>One or two?  Or both?</legend>
    +  <f:checkboxlist class="radio" name="checkboxlist" data="[(1, 'one'), (2, 'two')]" />
    +</fieldset>
    +
    +<div style="clear: both"><f:submit /></div>
    +
    +</f:form>
    +
    +
    + + Run this code + +
    + +

    This demonstrates several properties of Spyce form tags: +

      +
    • Most tags take an optional label parameter; this is turned into an HTML label tag +associated with the form element itself. +
    • If you View Source in your browser while running this sample, you can +see that Spyce generates an id with the same value as the name parameter. +(You can override this by explicitly specifying a different id parameter, +if you need.) +
    • You can pass arbitrary parameters (such as the class parameter for <f:form>) +to a Spyce form tag; parameters that do not have special meaning to Spyce +will be passed through to the HTML output. +
    • Try changing the form values and submitting. By default, Spyce automatically +remembers the user input for you, unless you give a tag a value +parameter (or selected for collection elements), which has highest precedence. +Note the different behavior of text1 and text2 in this example. +
    • Spyce provides some higher-level tags such as checkboxlist that result in multiple +elements at the HTML level. For these tags, a "data" parameter is expected, +which is always interpreted as a Python expression. +Any iterable may be used as data, including generators and generator expressions +for those with recent Python versions. Typically data would come from the +database; here we're just using a literal list. +
    +Handlers +

    +Active Handlers allow you to "attach" python functions to form submissions. +They are described in the +Active Handlers manual page. +

    +Reference +

    +First, some general rules: +

    The text displayed by a text-centric tag can come from one of three +places. In order of decreasing priority, these are +

      +
    • the value parameter +
    • the value submitted by the user is used +
    • the default parameter +
    +If none of these are found, the input will be empty. +

    For determining whether option, radio, and checkbox tags are checked or selected, +a similar process is followed, with +selected and checked parameters as the highest-priority source. +The same parameters are used for select, radiolist, and checkboxlist tags; +the only difference is for the collection tags, you can also specify +multiple values in a Python list (or other iterable) in either the +selected/checked or default parameters. +

    All tags except form and submit can be given names that tell Spyce how to +treat their submitted values when passing to an Active Handler function. +Adding ":int", ":float", ":bool", or ":list" is allowed. The first three +tell Spyce what kind of Python value to convert the submission to; ":list" +may be combined with these, or used separately, and tells Spyce to pass a +list of all values submitted instead of a single one. +(An example is given in the Active Handlers page.) +Finally, here is the list of tags: +

      +
    • <form +[method=exprstring] [action=exprstring] ...> </form> +
      Begin a new form. The method parameter defaults +to 'POST'. The action parameter defaults to the current page. +
    • +

    • +<submit +[handler=exprstring] [value=exprstring] ... /> + +
      Create a submit button. The value parameter is +emitted as the button text. If handler is given, Spyce will call the function(s) +it represents at the beginning of the page load after this button is clicked. +(Multiple function names may be separated with commas.) +

      +If the handler is in a different [python] module, Spyce will automatically import it +before use. +

      +A handler may take zero or more arguments. +For the first non-self argument (if present), Spyce always passes a +moduleFinder corresponding to the current +spyceWrapper object; it is customary to call this argument "api." +moduleFinder provides __getitem__ access to loaded modules; thus, +"api.request" would be the current request module. If a requested module +is not found, it is loaded. +

      +(You can also directly access the wrapper with api._wrapper, providing +access to anything module authors have, +but you will rarely if ever need to do this.) +

      +For other handler function parameters, Spyce will pass the values for the +corresponding form input, or None if nothing was found in the GET or POST +variables. +

      +See also the Active Handlers +language section for a higher-level overview. +

      +Limitation: currently, Active Handlers require resubmitting to the same spyce page; +of course, the handler method may perform an internal or external +redirect. +

    • +

    • <hidden +name=exprstring [value=exprstring] [default=exprstring] .../> +
      Create a hidden form field. The name parameter is evaluated and +emitted. +
    • +

    • <text +name=exprstring [value=exprstring] [default=exprstring]  .../> +
      Create a form text field. The name parameter is evaluated and +emitted. +
    • +

    • <date +name=exprstring [value=exprstring] [default=exprstring] [size=exprstring] [format=exprstring] .../> +
      Create a form text field with a javascript date picker. Format defaults to MM/DD/YYYY. Maxlength is always len(format); +this is also the default size, but size may be overridden for aesthetics. +
    • <password +name=exprstring [value=exprstring] [default=exprstring] [size=exprstring] [maxlength=exprstring] .../> +
      Create a form password field. Parameters are the same as for text +fields, explained above.
    • +

    • <textarea +name=exprstring [value=exprstring] [rows=exprstring] [cols=exprstring] ...>default</textarea> +
      Create a form textarea field. The name parameter is evaluated and +emitted. The value optional parameter is evaluated. A default +may be provided in the body of the tag. The value emitted is, in order of +decreasing priority: local tag value, value in submitted +request dictionary, local tag default. We search this list +for the first non-None value. The rows and cols optional +parameters are evaluated and emitted.
    • +

    • <radio +name=exprstring value=exprstring [checked] [default] .../> +
      Create a form radio-box. The name and value parameters are +evaluated and emitted. A checked and default flags affect +whether this box is checked. The box is checked based on the following +values, in decreasing order of priority: tag value, +value in submitted request dictionary, tag default. +We search this list for the first non-None value.
    • +

    • <checkbox +name=exprstring value=exprstring [checked] [default] .../> +
      Create a form check-box. Parameters are the same as for radio +fields, explained above.
    • +

    • <select +name=exprstring [selected=exprstring] [default=exprstring] [data=expr] ...>...</select> +
      Create a form select block. The name parameter is +evaluated and emitted. The optional data should be an iterable of +(description, value) pairs. +
    • +

    • <option +[text=exprstring] [value=exprstring] [selected] [default] .../> +
      <option +[value=exprstring] [selected] [default] ...>text</option> +
      Create a form selection option. This tag must be nested within a +select tag. The text optional parameter is evaluated and +emitted in the body of the tag. It can also be provided in the body of the +tag, as you might be used to seeing in HTML. +
    • +

    • <radiolist +name=exprstring data=expr [checked=exprstring] [default=exprstring] ...>...</select> +
      Create multiple radio buttons from data, which should be an iterable of +(description, value) pairs. +
    • +

    • <checkboxlist +name=exprstring data=expr [checked=exprstring] [default=exprstring] ...>...</select> +
      Create multiple checkboxes from data, which should be an iterable of +(description, value) pairs. +
    • +

    +Here is an example of all of these tags in use: + +
    + examples/formtag.spy +
    + +
    <spy:parent title="Form tag example" />
    +
    +<f:form>
    +
    +<h2>Primitive controls</h2>
    +
    +<div class="simpleform">
    +  <f:text name="mytext" label="Text" default="some text" size=10 maxlength=30 />
    +
    +  <f:password name="mypass" label="Password" default="secret" />
    +
    +  <f:textarea name="mytextarea" label="Textarea" default="rimon" rows=2 cols=50></f:textarea>
    +
    +  <label for="mycheck">Checkbox</label><f:checkbox name=mycheck value=check1 />
    +
    +  <label for="myradio1">Radio option 1</label><f:radio name=myradio value=option1 />
    +  <label for="myradio2">Radio option 2</label><f:radio name=myradio value=option2 />
    +</div>
    +
    +<div style="clear: both">
    +  <h2 style="padding-top: 1em;">Compound controls</h2>
    +  [[-- a simple data source for the compound controls -- in practice
    +	     this would probably come from the database --]]
    +  [[ L = [('option %d' %i, str(i)) for i in range(5)] ]]
    +
    +  <fieldset>
    +    <legend>Radiolist</legend>
    +    <f:radiolist class=radio name=radiolist data="L" default="3" />
    +  </fieldset>
    +
    +  <fieldset>
    +    <legend>Checkboxlist</legend>
    +    <f:checkboxlist class=radio name=checkboxlist data="L" default="=['0', '1']" />
    +  </fieldset>
    +
    +  <fieldset>
    +    <legend>Select</legend>
    +    <f:select name=myselect multiple size=5 data="L" default="2" />
    +  </fieldset>
    +
    +  <fieldset>
    +    <legend>Date</legend>
    +    <f:date name=mydate />
    +  </fieldset>
    +
    +  <h2 style="clear:both; padding-top: 1em;">Test it!</h2>
    +  <input type="submit" name="foo" value="Submit!">
    +
    +</f:form>
    +
    +
    +
    +
    + + Run this code + +
    + +


    + + + + + +
    Prev: 3.9.1 - CoreUp: 3.9 - TagsNext: 3.9.3 - Active Handlers
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-tag_handlers.html b/spyce-2.1/www/demo-site/docs/doc-tag_handlers.html new file mode 100755 index 0000000000000000000000000000000000000000..cdc46f0060d1aaecd8d55fa9fe81e0ee7cdef283 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-tag_handlers.html @@ -0,0 +1,367 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Tags
    +
    + + + + + + + +
    Prev: 3.9.2 - FormUp: 3.9 - TagsNext: 3.9.4 - Writing Tag Libraries
    +
    +3.9.3. Active Handlers

    + + +Active Handlers allow you to "attach" python functions to Spyce form submissions. +Instead of old-fashioned inspection of the request environment, Spyce will +pull the parameters required by your function's signature out for you. +Let's have a look at an example. Here, we define a calculate +function and assign it to be the handler for our submit input: + +
    + examples/handlerintro.spy +
    + +
    [[!
    +def calculate(self, api, x, y):
    +    self.result = x * y
    +]]
    +
    +<spy:parent title="Active Handler example" />
    +<f:form>
    +    <f:text name="x:float" default="2" label="first value" />
    +    <f:text name="y:float" default="3" label="second value" />
    +
    +    <f:submit handler="self.calculate" value="Multiply" />
    +</f:form>
    +
    +<p>
    +Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]]
    +</p>
    +
    +
    +
    + + Run this code + +
    +

    +

      +
    • +Handlers may be inline, as the calculate handler is here, or in a +separate Python module. When using a handler from a Python module, Spyce +will automatically import the module when needed. (Handler parameters are +strings, not Python references.) The todo demo +demonstrates using handlers this way. +
    • You can give your form inputs a data type; Spyce will perform the +conversion automatically before passing parameters to your handler function. +
    +

    +Active Handlers also make it easy to incorporate user-friendly error messages +into your forms simply by raising a HandlerError: + +
    + examples/handlervalidate.spy +
    + +
    [[!
    +def calculate(self, api, x):
    +    from spyceException import HandlerError
    +    try:
    +        x = float(x)
    +    except ValueError:
    +        raise HandlerError('Value', 'Please input a number')
    +    if x < 0:
    +        raise HandlerError('Value', 'Cannot take the square root of negative numbers!')
    +    self.result = x ** 0.5
    +]]
    +
    +<spy:parent title="Active Handler Validation" />
    +<f:form>
    +    <f:text name="x" default="-1" label="Value:" />
    +    <f:submit handler="self.calculate" value="Square root" />
    +</f:form>
    +
    +<p>
    +Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]]
    +</p>
    +
    +
    +
    + + Run this code + +
    + +

    +You can show multiple errors at once with a CompoundHandlerError: + +
    + examples/handlervalidate2.spy +
    + +
    [[!
    +def errors(self, api):
    +    from spyceException import HandlerError, CompoundHandlerError
    +    cve = CompoundHandlerError()
    +    cve.add(HandlerError('One', 'First error'))
    +    cve.add(HandlerError('Two', 'Second error'))
    +    if cve:
    +        raise cve
    +]]
    +
    +<spy:parent title="Active Handler Validation 2" />
    +<f:form>
    +    <f:submit handler="self.errors" value="Show me some errors" />
    +</f:form>
    +
    +
    +
    + + Run this code + +
    + +

    +All Spyce modules are available via the api handler parameter (which +should always be the first parameter (after self in a class). Here +is an example that uses the db module: + +
    + examples/db.spy +
    + +
    <spy:parent title="To-do demo" />
    +
    +[[!
    +def list_new(self, api, name):
    +    if api.db.todo_lists.selectfirst_by(name=name):
    +        raise HandlerError('New list', 'a list with that description already exists')
    +    api.db.todo_lists.insert(name=name)
    +    api.db.flush()
    +]]
    +
    +(This is an self-contained example using the same database as the
    +<a href=/demos/to-do/index.spy>to-do demo</a>.)
    +
    +<h2>To-do lists</h2>
    +
    +[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]]
    +<spy:ul data="[L.name for L in lists]" />
    +
    +<h2>New list</h2>
    +<f:form>
    +<f:submit value="New list" handler=self.list_new />:
    +<f:text name=name value="" />
    +</f:form>
    +
    +
    +
    + + Run this code + +
    + +

    +Handlers in Active Tags allow you to +create reusable components, as in the the chat demo. +

    +(Since Spyce captures stdout, you can use print to debug handlers.) +


    + + + + + +
    Prev: 3.9.2 - FormUp: 3.9 - TagsNext: 3.9.4 - Writing Tag Libraries
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-tag_new.html b/spyce-2.1/www/demo-site/docs/doc-tag_new.html new file mode 100755 index 0000000000000000000000000000000000000000..b3e1d3e3497ebaa638d55854b1d264ac0ff17282 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-tag_new.html @@ -0,0 +1,444 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Tags
    +
    + + + + + + + +
    Prev: 3.9.4 - Writing Tag LibrariesUp: 3.9 - TagsNext: 3.10 - Installation
    +
    +3.9.5. Writing Tag Libraries the hard way

    + + +You can still write tag libraries manually if the tag declaration language doesn't +give you the tools you need. (About the only functionality it doesn't currently +expose is the looping ability used in spy:for.) You may wish to approximate +what you want with a new-style tag library +and examine the compiler's output (spyceCmd.py -c). +

    +We begin with a basic example:

    + +
    + examples/myTaglib.py +
    + + + +
    +from spyceTag import spyceTagLibrary, spyceTagPlus
    +
    +class tag_foo(spyceTagPlus):
    +  name = 'foo'
    +  mustend = 1
    +  def syntax(self):
    +    self.syntaxPairOnly()
    +    self.syntaxNonEmpty('val')
    +  def begin(self, val):
    +    self.getOut().write('<font size="%s"><b>' % str(val))
    +  def end(self):
    +    self.getOut().write('</b></font><br>')
    +
    +class myTaglib(spyceTagLibrary):
    +  tags = [
    +    tag_foo, 
    +  ]
    +
    +
    +
    + +
    +
    +

    +Saving this code in myTaglib.py, in the same +directory as your script or anywhere else in the search path, one could then +use the foo active tag (defined above), as follows:

    + +
    + examples/tag.spy +
    + +
    [[.taglib name=myTaglib as=me]]
    +<html><body>
    +[[ for x in range(2, 6):{ ]]
    +  <me:foo val="=x">size [[= x ]]</me:foo>
    +[[ } ]]
    +</body></html>
    +
    +
    +
    + + Run this code + +
    +

    +An active tag library can be any Python class that derives from +spyceTag.spyceTagLibrary. The interesting aspects of this class +definition to implementors are:

    +

      +
    • tags:
      This field is usually all that requires redefinition. +It should be a list of the classes (as opposed to instances) of the +active tags.
    • +

    • start():
      This methd is invoked by the engine upon loading +the library. The inherited method is a noop.
    • +

    • finish():
      This method is invoked by the engine upon +unloading the library after a request. The inherited method is a noop.
    • +

      +

    +Each active tag can be any Python class that derives from +spyceTag.spyceTag. The interesting aspects of the class definition for +tag implementors are:

    +

      +
    • name:
      This field MUST be overidden to indicate the name of +the tag that this class defines.
    • +

    • buffer:
      This flag indicates whether the processing of the +body of the tag should be performed with the current output stream +(unbuffered) or with a new, buffered output stream. Buffering is useful for +tags that may want to transform, or otherwise use, the output of processing +their own bodies, before it is sent to the client. The inherited default is +false.
    • +

    • conditional:
      This flag indicates whether this tag may +conditionally control the execution of its body. If true, then the begin() +method of the tag must return true to process the tag body, or false to skip +it. If the flag is set to false, then return value of the begin() method is +ignored, and the body executed (unless an exception is triggered). Some +tags, such as the core:if tag, require this +functionality, and will set the flag true. Many other kinds of tags do not, +thus saving a level of indentation (which is unfortunately limited in Python +-- hence the need for this switch). The inherited default is false.
    • +

      +

    • loops:
      This flag indicates whether this tag may want to loop +over the execution of its body. If true, then the body() method of the tag +must return true to repeat the body processing, or false to move on to the +end() of the tag. If the flag is set to false, then the return value of the +body() method is ignored, and the body is not looped. Some tags, such as the +core:for tag, require this functionality, and +will set the flag true. Many other kinds of tags do not, thus saving a level +of indentation. The inherited default is false.
    • +

    • catches:
      This flag indicates whether this tag may want to +catch any exceptions that occur during the execution of its body. If true, +then the catch() method of the tag will be invoked on exception. If the flag +is false, the exception will continue to propagate beyond this point. Some +tags, such as the core:catch, require this +functionality, and will set the flag true. Many other kinds of tags do not, +thus saving a level of indentation. The inherited default is false.
    • +

      +

    • mustend:
      This flag indicates whether this tag wants the +end() method to get called, if the begin() completes successfully, no +matter what. In other words, the call to end() is placed in the finally +clause of +the try-finally block which begins just after the begin(). This is useful for +tag cleanup. However, many tags do not perform anything in the +end() of their tag, or perhaps perform operations that are not important in +the case of an exception. Such tags do not require this functionaliy, thus +saving a level of indentation. The inherited default is false.
    • +

    • syntax():
      This method is invoked at compile time to perform +any additional tag-specific syntax checks. The inherited method returns +None, which means that there are no syntax errors. If a syntax error is +detected, this function should return a string with a helpful message about +the problem. Alternatively, one could raise an +spyceTagSyntaxException.
    • +

    • begin( ... ):
      This method is invoked when the corresponding +start tag is encountered in the document. All the attributes of the tag are +passed in by name. This method may return a boolean flag. If +conditional is set to true (see above), then this flag indicates +whether the body of the tag should be processed (true), or skipped (false). +The inherited method performs no operation, except to return true.
    • +

    • body( contents ):
      This method is invoked when the body of +the tag has completed processing. It will be called also for a +singleton, which we assume simply has an empty body. However, it will not be +called if the begin() method has chosen to skip body processing entirely. If +the tag sets buffer to true for capturing the body processing output +(see above), then the string output of the body processing has been captured +and stored in contents. It is the responsibility of this method to +emit something, if necessary. If the tag does not buffer then +contents will be None, and any output has already been written to the +enclosing scope. If the loops flag is set to true, then this method +is expected to return a boolean flag. If the flag is true, then the body +will be processed again, followed by another invocation of this method. And +again, and again, until false is received. The inherited tag method performs +nothing and returns false.
    • +

    • end():
      This method is invoked when the corresponding end tag +is encountered. For a singleton tag, we assume that the end tag immediately +follows the begin, and still invoke this method. If the mustend flag +has been set to true, then the runtime engine semantics ensure that if the +begin method terminates successfully, this method will get called, +even in the case of an exception during body processing. The inherited +method is a noop.
    • +

    • catch( ex ):
      If the catches flag has been set to +true, then if any exception occurs in the begin(), body() or end() methods +or from within the body processing, this method will be invoked. The +parameter ex holds the value that was thrown. The inherited method +simply re-raises the exception.
    • +

    • getPrefix():
      Return the prefix under which this tag library +was installed.
    • +

    • getAttributes():
      Return a dictionary of tag attributes. +
    • +

    • getPaired():
      Return true if this is a paired (open and +close) tag, or false if it is a singleton.
    • +

    • getParent( [name] ):
      Return the object of the direct parent +tag, or None if this is the root active tag. Plain (inactive) tags do not +have objects associated with them in this hierarchy. If the optional +name parameter is provided, then we search up the tree for an active +tag of the same library and with the given name. If such a tag can not be +found, then return None.
    • +

    • getOut():
      Return the (possibly buffered) output stream that +this tag should write to.
    • +

    • getBuffered():
      Returns true if the tag output stream is a +local buffer, or false if the output is connected to the enlosing scope. +
    • +

    +Note that Spyce goes to a lot of effort to avoid unnecessary indentation in +the code it generates for Active Tags. +A limitation of the Python runtime is that the level of indentation +within any method is limited by a +compile-time constant. You can change it, of course, but in most +Python distributions as of this writing (Feb 2005), this is currently set to 100. +See for instance +this thread +from python-bugs. +

    +For convenience, tag implementors may wish to derive their implementations +from spyceTagPlus, which provides some useful additional methods: +

      +
    • getModule( name ):
      Return a reference to a module +from the page context. The module is loaded, if necessary.
    • +

    • syntaxExist( [must]* ):
      Removed in 2.0; +Spyce now checks the signature of the begin method; arguments +with no default are enforced as required automatically.

      +

    • syntaxNonEmpty( [names]* ):
      Ensure that if the attributes +listed in names exist, then each of them does not contain an empty +string value. Otherwise, a spyceTagSyntaxException is thrown. Note that the +actual existence of a tag is checked by syntaxExists(), and that this method +only checks that a tag is non-empty. Specifically, there is no exception +raised from this method, if the attribute does not exist.
    • +

    • syntaxValidSet( name, validSet ):
      Ensure that the value of +the attribute name, if it exists, is one of the values in the set +validSet. Otherwise, a spyceTagSyntaxException is raised.
    • +

    • syntaxPairOnly():
      Ensure that this tag is a paired tag. +Otherwise, a spyceTagSyntaxException is thrown.
    • +

    • syntaxSingleOnly():
      Ensure that this tag is a singleton tag. +Otherwise, a spyceTagSyntaxException is thrown.
    • +

    +Despite the length of this description, most tags are trivial to write, as +shown in the initial example. The easiest way to start is by having at a look +at various implemented tag libraries, such as tags/core.py. The more curious reader is welcome to look +at the tag library internals in spyceTag.py and +modules/taglib.py. The tag semantics are ensured by +the Spyce compiler (see spyceCompile.py), though it +is likely easier simply to look at the generated Python code using the "spyce -c" command-line facility.

    +


    + + + + + +
    Prev: 3.9.4 - Writing Tag LibrariesUp: 3.9 - TagsNext: 3.10 - Installation
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc-tag_new2.html b/spyce-2.1/www/demo-site/docs/doc-tag_new2.html new file mode 100755 index 0000000000000000000000000000000000000000..eafdf4daba053a5ca9f11a80c80e715658998be0 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc-tag_new2.html @@ -0,0 +1,291 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation - Tags
    +
    + + + + + + + +
    Prev: 3.9.3 - Active HandlersUp: 3.9 - TagsNext: 3.9.5 - Writing Tag Libraries the hard way
    +
    +3.9.4. Writing Tag Libraries

    + +Creating your own active tags is quite easy and this section explains how. You +may want to create your own active tags for a number of reasons. More advanced +uses of tags include database querying, separation of business logic, or +component rendering. On the other hand, you might consider creating simpler +task-specific tag libraries. For example, if you do not wish to rely on +style-sheets you could easily define your own custom tags to perform the +formatting in a consistent manner at the server. Another convenient use for +tags is to automatically fill forms with session data. These are only a few of +the uses for tags. As you will see, writing a Spyce active tag is far +simpler than writing a JSP tag. +

    +(The chatbox demo gives an example of an active tag.) +

    +Tag libraries must be placed in a separate file from request-handling Spyce pages. +The following +directives +apply specifically to tag library definition: +

      +
    • +[[.tagcollection ]] : +
      Indicates that the current file will be an Active Tag library. Must +be at the start of the file. +
    • +[[.begin name=string [buffers=True|False] [singleton=True|False] [kwattrs=string] ]] : +
      +Begin defining a tag named name. Optional attributes: +
        +
      • buffers: if true, Spyce will evaluate the code between the begin and end tags +and pass it to the tag as the variable _content. For instance, the following +simplistic tag makes its contents bold: + +
        + examples/tagbold.spi +
        + +
        [[.begin name=bold buffers=True]]
        +
        +<b>[[=_contents]]</b>
        +
        +[[.end]]
        +
        +
        +
      • singleton: if true, Spyce will not allow paired use of the tag (<tag></tag>) +and only allow singleton use (<tag />). If false, the reverse is true. +
      • kwattrs: the name of the dict in which to place attributes not specified with [[.attr]] +directives. If not given, Spyce will raise an error if unexpected attributes are seen. +
      +
    • +[[.attr name=string [default=string] ]] : +
      Specify that the current tag being defined expects an attribute named name. +If a default string is given, the attribute is optional. (Dynamic attributes +may be accepted with the kwattrs option in the begin directive.) (If the default +string is prefixed with '=', it will be evaluated as python code at runtime.) +
    • +[[.export var=string [as=string] ]] : +
      Specifies that the variable from the tag context named var will be +exported back to the calling page. The optional as attribute may be used +to give the variable a different name in the calling context. +
    • +[[.end ]] : +
      Ends definition of the current tag. +
    +Active tags may specify handlers +as in normal Spyce code; this may be done inline +with class chunks, or as a reference +to a separate .py module. This allows building reusable components easily! +Again, the chatbox demo demonstrates this. +

    +(Be careful if you take the class chunk approach with handlers, since all class chunks that get used +in a given page are pulled into the same namespace. By convention, tag handlers +defined in reusable tags are prefixed with the tag name, e.g., chatbox_addline.) +

    +Active tags should not contain f:form active tags; this needs to be done +by the .spy page for the Spyce compiler to link up Active Handlers correctly. +

    +One limitation of using the Active Tag directives described here is that tags +within a single collection may not call each other. Usually, you can work +around this by defining common code inside a .py module and importing that. +If this is not an option, you can create Active Tags manually. This is +described in the next section. +


    + + + + + +
    Prev: 3.9.3 - Active HandlersUp: 3.9 - TagsNext: 3.9.5 - Writing Tag Libraries the hard way
    + +

    +


    + + + +
    + Spyce logo +
    + Python Server Pages
    version 2.1.3
    +
    + Spyce Powered + SourceForge Logo + +
    + +
    + + diff --git a/spyce-2.1/www/demo-site/docs/doc.html b/spyce-2.1/www/demo-site/docs/doc.html new file mode 100755 index 0000000000000000000000000000000000000000..284b7520d45a7ce14ed8175d03da248e0bf815de --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc.html @@ -0,0 +1,528 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation
    +
    + + + + + + + + +
    +Spyce - Python Server Pages (PSP)
    +User Documentation

    +Release 2.1

    +

    +
    + [ Single-Page Format ] +
    +

    +TABLE OF CONTENTS
    + + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + +
    + + +
    +spyce +
    + + + + + +
              + + + + + +
    + home +     + documentation +     + download +     + + Spyce logo +
    + +

    +Documentation
    +
    + + + + + + + + +
    +Spyce - Python Server Pages (PSP)
    +User Documentation

    +Release 2.1

    +

    +
    + [ Single-Page Format ] +
    +

    +TABLE OF CONTENTS
    +

    + + diff --git a/spyce-2.1/www/demo-site/docs/doc.spy b/spyce-2.1/www/demo-site/docs/doc.spy new file mode 100755 index 0000000000000000000000000000000000000000..6af4179e9738778717191dd96e6d2bead7997b40 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/doc.spy @@ -0,0 +1,3727 @@ +[[.import names="include,stdout,transform,toc"]] +[[ include.spyce('/inc/head.spi', {'pagename': 'Documentation', 'page': 'doc.html'}) ]] +[[.include file="/inc/static.spi"]] +[[.compact]] + +[[\ +import os +try: + os.remove('_eginfo.txt') # for eg.spy +except OSError: + pass + +import verchk, re, string, sys +import spyceModule, spyce + +def escapeHTMLandCode(html): + import re + html = re.sub(r'\<', '<', html) + html = re.sub(r'\>', '>', html) + html = re.sub(r'\[\+', '['*2, html) + html = re.sub(r'\+\]', ']'*2, html) + return html + +# single and multipage +sectionbegin = 1 +try: + singlepage # does it exist? + multipage = 0 +except: multipage = 1 + +# table of contents functions +def genTOClink(tag, multipage=multipage): + if multipage: + filename = os.path.splitext(os.path.basename(request.filename()))[0] + if tag=='root': return "%s.html" % filename + else: return "%s-%s.html" % (filename, tag) + else: return '#%s' % tag + +def getNumData(tag): + import string + data = None + num = '' + if tag: + data = str(toc.getData(tag)) + num = toc.getNumbering(tag) + if num: num = string.join(map(str, num),'.') + return num, data + +def getLink(tag, genTOClink=genTOClink, getNumData=getNumData): + num, data = getNumData(tag) + if tag: + if num: return '%s - %s' % (genTOClink(tag), num, data) + else: return '%s' % (genTOClink(tag), data) + else: return 'None' + +def emitLocation(tag, getLink=getLink): + next = getLink(toc.getNextTag(tag)) + prev = getLink(toc.getPrevTag(tag)) + up = getLink(toc.getParentTag(tag)) + print ''' + + + + +
    Prev: %sUp: %sNext: %s
    ''' % ('%', '%', prev, '%', up, '%', next) + +def emitChildren(tag, getLink=getLink): + nodes = toc.getChildrenTags(tag) + if not nodes: return + links = map(getLink, nodes) + print '

    ' + print 'Sub-sections:' + print '

      ' + for l in links: + print '
    • %s
    • ' % l + print '
    ' + +def emitHeader(tag, emitLocation=emitLocation, genTOClink=genTOClink): + tag2 = toc.getTag() + pagename, page = 'Documentation', 'doc.html' + while tag2: + titles = { + 'intro': ('Introduction', 'intro'), + 'lang': ('Language', 'lang'), + 'runtime': ('Runtime', 'runtime'), + 'mod': ('Modules', 'mod'), + 'tag': ('Tags', 'tag'), + 'conf': ('Install', 'conf'), + 'add_perf': ('Performance', None), + 'add_history': ('History', None), + 'add_related': ('Related', None), + } + if titles.has_key(tag2): + pagename = pagename+' - '+titles[tag2][0] + if titles[tag2][1]: + page = 'doc-'+titles[tag2][1]+'.html' + break + tag2 = toc.getParentTag(tag2) + context = { + 'pagename': pagename, + 'page': page, + 'nexturl': genTOClink(toc.getNextTag(tag)), + 'prevurl': genTOClink(toc.getPrevTag(tag)), + 'contentsurl': 'doc.html', + } + include.spyce('/inc/head.spi', context) + emitLocation(tag) + print '
    ' + +def emitFooter(tag, emitLocation=emitLocation, emitChildren=emitChildren): + print '
    ' + emitLocation(tag) + emitChildren(tag) + include.spyce('/inc/tail.spi') + + +def toc_tocPush(depth, tag, numbering, data): + print '
      \n' + +def toc_tocPop(depth, tag, numbering, data): + print '
    \n' + +def toc_tocStart(depth, tag, numbering, data, genTOClink=genTOClink): + if depth==1: + print '
  • %s %s
  • \n' % ( + reduce(lambda s, i: '%s%d.' % (s, i), numbering, ''), + genTOClink(tag), + data) + if depth==2: + print '
  • %s %s
  • \n' % ( + reduce(lambda s, i: '%s%d.' % (s, i), numbering, ''), + genTOClink(tag), + data) + if depth==3: + print '
  • %s %s
  • \n' % ( + reduce(lambda s, i: '%s%d.' % (s, i), numbering, ''), + genTOClink(tag), + data) + +def toc_docStart(depth, tag, numbering, data, multipage=multipage, genTOClink=genTOClink, emitHeader=emitHeader): + import string + if multipage: + stdout.push(genTOClink(tag)) + emitHeader(tag) + if depth==1: + print '

    %s %s

    \n' % ( + tag, + reduce(lambda s, i: '%s%d.' % (s, i), numbering, ''), + string.upper(data)) + if depth==2: + print '%s %s

    \n' % ( + tag, + reduce(lambda s, i: '%s%d.' % (s, i), numbering, ''), + data) + if depth==3: + print '%s %s

    \n' % ( + tag, + reduce(lambda s, i: '%s%d.' % (s, i), numbering, ''), + data) + +def toc_docEnd(depth, tag, numbering, data, multipage=multipage, emitFooter=emitFooter): + if multipage: + emitFooter(tag) + stdout.pop() + +toc.setTOC_PUSH(toc_tocPush) +toc.setTOC_POP(toc_tocPop) +toc.setTOC_ENTRY(toc_tocStart) +toc.setDOC_START(toc_docStart) +toc.setDOC_END(toc_docEnd) +]] + + +[[toc.anchor("Table of Contents")]] + +

    + Spyce - Python Server Pages (PSP)
    + User Documentation

    + Release [[=spyce.__version__]]

    +

    + +
    +[[ if multipage: { ]] + [ Single-Page Format ] +[[ } else: { ]] + [ Multi-Page Format ] +[[ } ]] +
    +

    + +TABLE OF CONTENTS
    +[[toc.showTOC()]] +[[\ +if not multipage: + print '


    ' +]] + +[[toc.b("Introduction", "intro")]] + +This document aims to be the authoritative source of +information about Spyce, usable as a comprehensive refence, a user guide and a +tutorial. It should be at least skimmed from beginning to end, +so you have at least an idea of the functionality available and can refer +back for more details as needed.

    + +[[ import spyce ]] +[[=spyce.__doc__]] +

    +Spyce's modular design makes it very flexible +and extensible. It can also be used as a command-line utility for static text +pre-processing or as a web-server proxy. +

    +Spyce's performance is comparable to the +other solutions in its class.

    + +Note: This manual assumes a knowledge of Python and focusses +exclusively on Spyce. If you do not already know Python, it is easy to +learn via this short tutorial, and has +extensive documentation.

    + +[[toc.b("Rationale / competitive analysis", "intro_rationale")]] + +This section is somewhat dated. We plan to update it soon. + +

    +A natural question to ask is why one would choose Spyce over JSP, ASP, PHP, +or any of the other HTML scripting languages that +perform a similar function. We compare Spyce with an array of exising tools: + +

      + +
    • Java Server Pages, JSP, is a widely popular, effective and + well-supported solution based on Java Servlet technology. Spyce differs from + JSP in that it embeds Python code among the HTML, thus providing a number of + advantages over Java. +
        +
      • Python is a high-level scripting language, + where rapid prototyping is syntactically easier to perform. +
      • There is no need for a separate "expression langauge" in Spyce; + Python is well-suited for both larger modules and active tag scripting. +
      • Python + is interpreted and latently typed, which can be advantageous for + prototyping, especially in avoiding unnecessary binary incompatibility of + classes for minor changes. +
      • Spyce code is of first-order in the Spyce + language, unlike JSP, which allows you to create useful Spyce lambda + functions. +
      • Creating new active tags and modules is simpler in + Spyce than in JSP. +
      • Spyce is better-integrated than JSP; to get similar functionality + in JSP, you have to add JSF (Java Server Faces) and Tiles, or + equivalents. +
      +

      + +

    • + +
    • PHP is another popular webserver module for dynamic + content generation. The PHP interpreter engine and the language itself were + explicitly designed for the task of dynamic HTML generation, while Python is + a general-purpose scripting language. +
        +
      • Spyce leverages from the extensive + development effort in Python: since any Python library can be imported and + reused, Spyce does not need to rebuild many of the core function libraries + that have been implemented by the PHP project. +
      • Use of Python + often simplifies integration of Spyce with existing system environments. +
      • Spyce code is also first-order in the Spyce language and Spyce supports + active tags. +
      • Spyce is modular in its design, allowing + users to easily extend its base functinality with add-on modules. +
      • The Spyce engine can be run from the command-line, which allows + Spyce to be used as an HTML preprocessor. +
      + Spyce, + like PHP, can run entirely within the process space of a webserver or via + CGI (as well as other web server adapters), and has been benchmarked to be + competitive in performance.
    • + +

    • ASP.NET is a Microsoft technology + popular with Microsoft Internet Information Server (IIS) users. Visual + Basic .NET and C# are both popular implementation languages. +
        +
      • Spyce provides the power of the ASP.NET "component" development style + without trying to pretend that web applications live in a stateful, + event-driven environment. This is a leaky abstraction that causes + ASP.NET to have a steep learning curve while the user learns + where the rough edges are. +
      • ASP.NET is not well-supported outside the IIS environment. Spyce can + currently run as a standalone or proxy server, under mod_python (Apache), + or under CGI and FastCGI, which are + supported in the majority of web server environments. Adapters have also + been written for Xitami, Coil, Cheetah -- other web servers and frameworks. +
      • Spyce is open-source, and free. +
      +
    • +

      + +

    • WebWare with Python Server Pages, PSP, is another + Python-based open-source development. PSP is similar in design to the Spyce + language, and shares many of the same benefits. Some important differences + include +
        +
      • Spyce supports both + Python chunks (indented Python) as well as PSP-style statements (braced + Python). +
      • Spyce supports active tags and component-based development +
      • Spyce code is first-order in the Spyce language +
      + PSP is also an integral part of + WebWare, an application-server framework similar to Tomcat Java-based + application server of the Apache Jakarta project. Spyce is to WebWare as JSP + is to Tomcat. Spyce is far simpler to install and run than WebWare (in the + author's humble opinion), and does not involve notions such as application + contexts. It aims to do only one thing well: provide a preprocessor and + runtime engine for the dynamic generation of HTML using embedded Python. +
    • + +

    • Zope is an object-oriented open-source application server, + specializing in "content management, portals, and custom applications." Zope + is the most mature Python web application development environment, but + to a large degree suffers from + second-system syndrome. + In the author's opinion, Zope is to a large degree responsible for the + large number of python + web environments: a few years ago, it was de rigeur for talented programmers + to try Zope, realize it was a mess, and go off to write their own framework. +

      + Zope provides a scripting language called DHTML and can call + extensions written in Perl or Python. Spyce embeds Python directly in the + HTML, and only Python. It is an HTML-embedded language, not an application + server.

    • + +

    + +Spyce strikes a unique balance between power and simplicity. +Many users have said that this +is "exactly what they have been waiting for". Hopefully, this is the correct +point in the design space for your project as well. +

    + +[[toc.n('Design Goals', 'intro_design')]] + +As a Spyce user, it helps to understand the broad design goals of this tool. +Spyce is designed to be: + +

      + +
    • Minimalist: The philosophy behind the design of Spyce is only + to include features that particularly enhance its functionality over the + wealth that is already available from within Python. One can readily import + and use Python modules for many functions, and there is no need to recode + large bodies of functionality.
    • + +

    • Powerful: Spyce aims to free the programmer from as much + "plumbing"-style drudgery as possible through features such as + Active Handlers + and reusable Active Tags.
    • + +

    • Modular: Spyce is built to be extended with + Spyce modules and + Active Tags + that provide additional functionality over the core engine + capabilities and standard Python modules. New features in the core engine + and language are rationalised against the option of creating a new module or + a new tag library. Standard Spyce modules and tag libraries are those that + are considered useful in a general setting and are included in the default + Spyce distribution. Users and third-parties are encouraged to develop their + own Spyce modules.
    • + +

    • Intuitive: Obey user expectations. Part of this is avoiding + special cases.
    • + +

    • Convenient: Using Spyce should be made as efficient as possible. + This, for example, is the reason behind the choice of \[[ as delimeters over alternatives such as <? (php) and <% (jsp). + (However, ASP/JSP-style delimeters are also supported, so if you're + used to that style and like it, feel free to continue using it with Spyce.) + Functions and + modules are also designed with as many defaults as possible. + There are no XML configuration files in Spyce. +
    • + +

    • Single-purpose: To be the best, most versatile, wildly-popular + Python-based dynamic HTML engine. Nothing more; nothing less.
    • + +

    • Fast: Performance is + important. It is expected that Spyce will perform comparably with any other + dynamic, scripting solutions available. +
    • + +

    + +Now, let's start using Spyce...

    + +[[toc.e()]] + +[[toc.n('Language', 'lang')]] + +The basic structure of a Spyce script is an HTML file with embeddings. There +are six types of possible embeddings among the plain HTML text: + +

      +
    • <taglib:name attr1=val1 ...> +
      Active tags may include presentation and action code. +
    • \[[-- Spyce comment      --\]] +
      Enclosed code is elided from the compiled Spyce class. +
    • \[[\  Python chunk         \]] +
      Embed python in your code. +
    • \[[!  Python class chunk  \]] +
      Like chunks, but at the class level rather than the spyceProcess method. +
    • \[[   Python statement(s)  \]] +
      Like chunks, but may include braces to indicate that the block should continue after the \]]. +
    • \[[=  Python expression    \]] +
      Output the result of evaluating the given expression. +
    • \[[.  Spyce directive      \]] +
      Pass options to the Spyce compiler. +
    • \[[spy  lambda             \]] +
      Allows dynamic compilation of Spyce code to a python function. +
    + +Each Spyce tag type has a +unique beginning delimeter, namely \[[, \[[\, \[[=, \[[. or \[[--. All +tags end with \]], except comment tags, which +end with --\]]. +

    +Since +\[[ and +\]] +are special Spyce delimeters, one would escape them as +\\[[ and +\\]] +for use in HTML text. They can not be escaped within Python code, but the +string expressions +("["*2) and +("]"*2), or equivalent expressions, +can be used instead, or the brackets can be conveniently separated with a +space in the case of list or slicing expressions.

    + +[[toc.b('Plain HTML and Active Tags', 'lang_string')]] + +Static plain HTML strings are printed as they are encountered. Depending on +the compacting mode of the +Spyce compiler, some whitespace may be eliminated. The Spyce transform module, for example, may +further pre-processes this string, by inserting transformations into the +output pipe. This is useful, for example, for dynamic compression of the +script result.

    + +The Spyce language supports tag libraries. +Once a tag library is imported under some name, mytags, then all static +HTML tags of the form <mytags:foo ... > become "active". +That is, code from the tag library is executed at that point in the document. +Tags can control their output, conditionally skip or loop the execution of +their bodies, and can interact with other active tags in the document. They +are similar, in spirit and functionality, to JSP tags. Tag libraries and modules (discussed later) can both +considerably reduce the amount of code on a Spyce page, and increase code +reuse and modularity.

    + +[[toc.n('Spyce Comments', 'lang_comment')]] + +Syntax: \[[-- comment --\]]

    + +Spyce comments are ignored, and do not produce any output, meaning that they +will not appear at the browser even in the HTML source. The first line of a +Spyce file, if it begins with the characters #!, is also considered a +comment, by Unix scripting convention. Spyce comments do not nest.

    + +[[toc.n('Spyce Directives', 'lang_directive')]] + +Syntax: \[[. directive \]] +

    + +Spyce directives directly affect the operation of the Spyce compiler. There +is a limited set of directives, listed and explained below: + +

      + +
    • + \[[.compact mode=mode\]] :
      + + Spyce can output the static HTML strings in various modes of compaction, + which can both save bandwidth and improve download times without visibly + affecting the output. Compaction of static HTML strings is performed once + when the input Spyce file is compiled, and there is no additional run-time + overhead beyond that. Dynamically generated content from Python code tags + and expressions is not compacted nor altered in any way. Spyce can operate + in one of the compaction modes listed below. One can use the compact + tag to change the compaction mode from that point in the file forwards.

      + +

        + +
      • off: No compaction is performed. Every space and newline in the + static HTML strings is preserved.
      • + +

      • space: Space compaction involves reducing any consecutive runs + of spaces or tabs down to a single space. Any spaces or tabs at the + beginning of a line are eliminated. These transformations will not affect + HTML output, barring the <pre> tag, but can considerably reduce the + size of the emitted text.
      • + +

      • line: Line compaction eliminates any (invisible) trailing + whitespace at the end of lines. More significantly it improves the indented + presentation of HTML, by ignoring any lines that do not contain any static + text or expression tags. Namely, it removes all the whitespace, including + the line break, surrounding the code or directives on that line. This + compaction method usually "does the right thing", and produces nice HTML + without requiring tricky indentation tricks by the developer. It is, + therefore, the initial compaction mode.
      • + +

      • full: Full compaction applies both space and line compaction. If + the optional mode attribute is omitted, full compaction mode is the + default value assumed.
      • + +

      +
    • + +

    • + \[[.import name=name from=file as=name args=arguments\]] :
      + + The import directive loads and defines a Spyce module into the global + context. (The \[[.module ... \]]directive + is synonymous.) A Spyce module is a + Python file, written specifically to interact with Spyce. The name + parameter is required, specifying the name of the Python class to load. The + file parameter is optional, specifying the file where the named class + is to be found. If omitted, file will equal name.py. The file path can be absolute or + relative. Relative paths are scanned in the Spyce home, user-configurable server path directories and current + script directory, in that order. Users are encouraged to name or prefix their + modules uniquely so as not to be masked by system modules or tag libraries. + The as parameter is optional, and specifies the name under which the + module will be installed in the global context. If omitted, this parameter + defaults to the name parameter. Lastly, the optional args parameter + provides arguments to be passed to the module initialization function. All + Spyce modules are start()ed before Spyce processing begins, + init()ed at the point where the directive is placed in the code, and + finish()ed after Spyce processing terminates. It is convention to + place modules at, or near, the very top of the file unless the location of + initialization is relevant for the functioning of the specific module.

      + + + \[[.import names="name1,name2,..."\]] :
      + + An alternative syntax allows convenient loading of multiple Spyce modules. + One can not specify non-standard module file locations, nor rename the + modules using this syntax.

    • + +

    • + \[[.taglib name=name from=file as=name\]] :
      + + The taglib directive loads a Spyce tag library. A Spyce tag library is a Python file, written + specifically to interact with Spyce. The name parameter specifies + the name of the Python class to load if using a 1.x-style taglib; + otherwise it is ignored. The file parameter is + optional, specifying the file where the named class is to be found. If + omitted, file will equal name.py. The file + path can be absolute or relative. Relative paths are scanned in the Spyce + home, user-configurable server + path directories and current script directory, in that order. Users are + encouraged to name or prefix their tag libraries uniquely so as not to be + masked by system tag libraries and modules. The as parameter is + optional, and specifies the unique tag prefix that will be used to identify + the tags from this library. If omitted, this parameter defaults to the name + parameter. It is convention to place tag library directives at, or near, the + very top of the file. The tags only become active after the point of the tag + library directive.

      + + Also note that the configuration parameter globaltags allows you + to set up tag libraries globally, freeing you from having to specify the + taglib directive on each page that uses a tag. By default, globaltags + installs core under the spy: prefix, and form under the f: prefix. + (Tag libraries specified in globaltags are only loaded if the Spyce compiler + determines they are actually used on the page, so there is no performance + difference between globaltags and manually setting up taglib for each page.) + +

      There are some additional directives that are only legal when + defining an active tag library. + +

    + +It is important to note that Spyce directives are processed at compile +time, not during the execution of the script, much like directives in C, and +other languages. In other words, they are processed as the Python code for the +Spyce script is being produced, not as it is being executed. Consequently, it +is not possible to include runtime values as parameters to the various +directives.

    + +[[toc.n('Python Statements', 'lang_stmt')]] + +Syntax: \[[ statement(s) \]]

    + +The contents of a code tag is one or more Python statements. The statements +are executed when the page is emitted. There will be no output unless the +statements themselves generate output.

    + +The statements are separated with semi-colons or new lines, as in regular +Python scripts. However, unlike regular Python code, Python statements do +not nest based on their level of indentation. This is because +indenting code properly in the middle of HTML is difficult on the developer. +To alleviate this problem, Spyce supports a slightly modifed Python syntax: +proper nesting of Spyce statements is achieved using begin- and end-braces: +{ and }, +respectively. These MUST be used, because the compiler regenerates the +correct indentation based on these markers alone. Even single-statement blocks +of code must be wrapped with begin and end braces. (If you prefer to use +Python-like indentation, read about chunks).

    + +The following Spyce code, from the Hello World! +example above:

    + +[[.compact mode=off]] +
    + +
    +  \[[ for i in range(10): { \]]
    +    \[[=i\]]
    +  \[[ } \]]
    +
    +
    +
    +[[.compact]]

    + +produces the following indented Python code:

    + +[[.compact mode=off]] +
    + +
    +  for i in range(10):
    +    response.writeStatic('  ')
    +    response.writeExpr(i)
    +    response.writeStatic('\n')
    +
    +
    +
    +[[.compact]]

    + +Without the braces, the code produced would be unindented and, in this case, +also invalid:

    + +[[.compact mode=off]] +
    + +
    +  for i in range(10):
    +  response.writeStatic('  ')
    +  response.writeExpr(i)
    +  response.writeStatic('\n')
    +
    +
    +
    +[[.compact]]

    + +Note how the indentation of the expression does not affect the indentation of +the Python code that is produced; it merely changes the number of spaces in +the writeStatic string. Also note that unbalanced +open and close braces within a single tag are allowed, as in the example +above, and they modify the indentation level outside the code tag. However, +the braces must be balanced across an entire file. Remember: inside the \[[ ... \]] delimiters, braces are always + required to change the indentation level.

    + +[[toc.n('Python Chunks', 'lang_chunk')]] + +Syntax: \[[\ Python chunk \]] +

    + +There are many Python users that experience anguish, disgust or dismay upon +reading the previous section: "Braces!? Give me real, indented Python!". These +intendation zealots will be more comfortable using Python chunks, which is why +Spyce supports them. Feel free to use Spyce statements or chunks +inter-changeably, as the need arises.

    + +A Python chunk is straight Python code, and the internal indentation is +preserved. The entire block is merely outdented (or indented) as a whole, such +that the first non-empty line of the block matches the indentation level of +the context into which the chunk was placed. Thus, a Python chunk can not +affect the indentation level outside its scope, but internal indentation is +fully respected, relative to the first line of code, and braces ({, }) are not required, nor +expected for anything but Python dictionaries. Since the first line of code is +used as an indentation reference, it is recommended that the start delimeter +of the tag (i.e. the \[[\) be placed on its own +line, above the code chunk, as shown in the following example:

    + +[[.compact mode=off]] +
    + +
    \[[\
    +    def printHello(num):
    +      for i in range(num):
    +        response.write('hello<br>')
    +
    +    printHello(5)
    +\]]
    +
    +
    +[[.compact]] +

    + +Naturally, one should not use braces here for purposes of indentation, +only for Python dictionaries. Additional braces will merely generate Python +syntax errors in the context of chunks. To recap: a Python statement tag +should contain braced Python; A Python chunk tag should contain regular +indented Python.

    + +[[toc.n('Python Class Chunks', 'lang_chunkc')]] + +Syntax: \[[! Python class chunk \]] +

    + +Behind the scenes, your Spyce files are compiled into a class called spyceImpl. Your Spyce script runs in a method of this class +called spyceProcess. Class chunks allow you to +specify code to be placed inside the class, but outside the main method, +analogously to the "<%!" token in JSP code. (If you would like to +see your Spyce file in compiled Python form, use the following command-line: +spyce.py -c myfile.spy.)

    + +

    +This is primarily useful when defining active +handlers without using a separate .py file: active handlers are the first +thing that the spyceProcess calls, even before any "python chunks." +For a handler callback to be visible at this stage, it needs to be defined +at the class level. Class chunks to the rescue: +

    +[[ includeCode('examples/handlerintro.spy') ]]

    + +[[toc.n('Python Expressions', 'lang_expr')]] + +Syntax: \[[= expression \]] +

    + +The contents of an expression tag is a Python expression. The result of that +expression evaluation is printed using the its string representation. +The Python object None is special cased to output as the empty string +just as it is in the Python interactive shell. This is almost always +more convenient when working with HTML. (If you really want a literal +string 'None' emitted instead, use response.write in a statement or chunk.) +

    +The Spyce transform module, can +pre-processes this result, to assist with mundane tasks such as ensuring that +the string is properly HTML-encoded, or formatted.

    + +[[toc.n('Spyce Lambdas', 'lang_lambda')]] + +Syntax: \[[spy [params] : spyce lambda code \]] +
    +or: \[[spy! [params] : spyce lambda code \]] +

    + +A nice feature of Spyce is that Spyce scripts are first-class members of the +language. In other words, you can create a Spyce lambda (or function) in any +of the Spyce Python elements (statements, chunks and expressions). These can +then be invoked like regular Python functions, stored in variables for later +use, or be passed around as paramaters. This feature is often very useful for +templating (example shown below), and can also be used to implement more +esoteric processing functionality, such as internationalization, multi-modal +component frameworks and other kinds of polymorphic renderers.

    + +It is instructive to understand how these functions are generated. The \[[spy ... : ... \]] syntax is first +translated during compilation into a call to the define() function of the spylambda module. At runtime, this +call compiles the Spyce code at the point of its definition, and returns a +function. While the invocation of a Spyce lambda is reasonably efficient, it +is certainly not as fast as a regular Python function invocation. The +spycelambda can be memoized (explained in the spylambda module section) by using +the \[[spy! ... : ... \]] +syntax. However, even with this optimization one should take care to use +Python lambdas and functions when the overhead of Spyce parsing and invocation +is not needed.

    + +Note that Spyce lambdas do not currently support nested variable scoping, nor +default parameters. The global execution context (specifically, Spyce modules) +of the Spyce lambda is defined at the point of its execution.

    + +[[includeCode('examples/spylambda.spy') ]]

    + +[[toc.n('ASP/JSP syntax', 'lang_asp') ]] + +Finally, due to popular demand, because of current editor support and people +who actually enjoy pains in their wrists, the Spyce engine will respect +ASP/JSP-like delimeters. In other words, it will also recognize the following +syntax: + +

    + +The two sets of delimeters may be used interchangeably within the same file, +though for the sake of consistency this is not recommended. + +[[toc.e()]] + +[[toc.n('Runtime', 'runtime')]] + +Having covered the Spyce language syntax, we now move to describing the +runtime processing. Each time a request comes in, the cache of compiled Spyce +files is checked for the compiled version of the requisite Spyce file. If one +is not found, the Spyce file is quickly read, transformed, compiled and cached +for future use.

    + +The compiled Spyce is initialized, then processed, then finalized. The +initialization consists of initializing all the Spyce modules. The Spyce file +is executed top-down, until the end is reached or an exception is thrown, +whichever comes first. The finalization step then finalizes each module in +reverse order of initialization, and any buffered output is automatically +flushed.

    + +[[toc.b('Exceptions', 'runtime_except')]] + +The Spyce file is executed top-down, until the end of the file is reached, a +valued is returned, or an exception is thrown, +whichever comes first. If the code terminates via an unhandled exception, then +it is caught by the Spyce engine. Depending on the exception type, different +actions are taken: + +

      + +
    • spyceDone can be raised at any time to stop the Spyce processing + (without error) at that point. It is often used to stop further output, as + in the example below that emits a binary image file. The spyceDone + exception, however, is more useful for modules writers. In regular Spyce + code one could simply issue a return statement, + with the same effect.
    • + +

    • spyceRedirect is used by the + redirect module. It causes the + Spyce engine to immediately redirect the request to another Spyce file + internally. Internally means that we do not send back a redirect to + the browser, but merely clear the output buffer and start processing a new + script.
    • + +

    • All other exceptions that occur at runtime will be processed via + the Spyce error module. This + module will emit a default error message, unless the user has installed some + other error handler.
    • + +

    + +Note that non-runtime exceptions, such as exceptions caused by compile errors, +missing files, access restrictions and the like, are handled by the server. +The default server error handler +can be configured via the server configuration file.

    + +[[includeCode('examples/gif.spy') ]] +

    + +[[toc.n('Code Transformation', 'runtime_transform')]] + +While the minutia of the code transformation that produces Python code from +the Spyce sources is of no interest to the casual user, it has some slight, +but important, ramifications on certain aspects of the Python language +semantics when used inside a Spyce file.

    + +The result of the Spyce compilation is some Python code, wherein the majority +of the Spyce code actually resides in a single function called +spyceProcess. If you are curious to see the result of a Spyce +compilation, execute: "spyce -c".

    + +It follows from the compilation transformation that: + +

      + +
    • Any functions defined within the Spyce file are actually nested + functions within the spyceProcess function.
    • + +

    • The use of global variables within Spyce code is not supported, + but also not needed. If nested scoping is available (Python versions + >2.1) then these variables will simply be available. If not, then you + will need to pass variables into functions as default parameters, and will + not be able to update them by value (standard Python limitations). It is + good practice to store constants and other globals in a single class, or to + to place them in a single, included file, or both.
    • + +

    • The global Spyce namespace is reserved for special variables, such as + Spyce and Python modules. While the use of the keyword global is not explicitly checked, it will pollute this + space and may result in unexpected behaviour or runtime errors.

    • + +

    • The lifetime of variables is the duration of a request. Variables with + lifetimes longer than a single request can be stored using the pool module.
    • + +

    + +[[toc.n('Dynamic Content', 'runtime_web')]] + +The most common use of Spyce is to serve dynamic HTML content, but it should +be noted that Spyce can be used as a general purpose text engine. It can be +used to generate XML, text and other output, as easily as HTML. In fact, the +engine can also be used to generate dynamic binary data, such as images, PDF +files, etc., if needed.

    + +The Spyce engine can be installed +in a number of different configurations that can produce dynamic output. +Proxy server, mod_python, and FastCGI exhibit high performance; the CGI approach is +slower, since a new engine must be created for each request. See the +configuration section for details. + +[[toc.n('Static Content', 'runtime_static')]] + +A nice feature of Spyce is that it can be invoked both from within a web +server to process a web request dynamically and also from the command-line. +The processing engine itself is the same in both cases. The command-line +option is actually just a modified CGI client, and is often used to +pre-process static content, such as this manual.

    + +Some remarks regarding command-line execution specifics are in order. The +request and response objects for a command-line request are connected to +standard input and output, as expected. A minimal CGI-like environment is +created among the other shell environment variables. Header and cookie lookups +will return None and the engine will accept input on stdin for POST +information, if requested. There is also no compiler cache, since the process +memory is lost at the end of every execution.

    + +Most commonly, Spyce is invoked from the command-line to generate static .html +ouput. Spyce then becomes a rather handy and powerful .html preprocessing +tool. It was used on this documentation to produce the consistent headers and +footers, to include and highlight the example code snippets, etc...

    + +The following makefile rule comes in handy:

    + +[[.compact mode=off]] +
    +
    +  %.html: %.spy
    +    spyce -o $@ $<
    +
    +[[.compact]] +

    + +[[toc.n('Command line', 'runtime_cmdline')]] + +The full command-line syntax is:

    + +[[.compact mode=off]] +
    + +
    [[ 
    +  import spyceCmd, cStringIO
    +  buf = cStringIO.StringIO()
    +  spyceCmd.showUsage(buf)
    +  response.write(escapeHTMLandCode(buf.getvalue()))
    +]]
    +
    +
    +[[.compact]]

    + +[[toc.n('Configuration', 'runtime_common')]] + + +Since there are a variety of very different +installation +alternatives for the Spyce +engine, effort has been invested in consolidating all the various runtime +configuration options. By default, the Spyce engine will search for a file +called spyceconf.py in its installation directory. An alternative file +location may be specified via the --conf command-line option. +

    +The spyce configuration file is a valid python module; any python code +may be used. To avoid duplication, we recommend starting with +"from spyceconf import *" and +override only select settings. One thing you cannot do from the config module +is access the Spyce server object spyce.getServer(), +since it has not been initialized yet.

    + +You may access the loaded configuration module from Spyce scripts and from +Python modules using the config attribute of the +server object. Or, simply as the spyceConfig module, +regardless of it actual file name. For example:

    + +[[includeCode('examples/config.spy')]]

    + +Below is the configuration file that this server is running. The length of the +file is primarily due to the thoroughness of the comments:

    + +
    + [[.compact mode=off]] + [[ import pp ]] + [[= pp.prettyhtml(open(os.path.join(os.path.dirname(request.filename()), '..', '..', 'spyceconf.py'))) ]] + [[.compact]] +
    +

    + +[[toc.n('Server utilities', 'runtime_util')]] + +Like any application server, the Spyce server provides several facilities +that can aid development. + +[[toc.b('The Spyce scheduler', 'runtime_util_scheduler')]] + +Spyce provides a scheduler that allows you to easily define tasks to +run at specified times or intervals inside the Spyce server. This +allows your tasks to leverage the tools Spyce gives you, as well as +any global data your application maintains within Spyce, such as +cached data or database connection pools. This also has the advantage +(over, say, crontab entries) of keeping your application self-contained, +making it easier to deploy changes from a development machine to production. +

    +The Spyce scheduler is currently only useful if you are running +in webserver mode. If you run under mod_python, CGI, or FastCGI, +you could approximate scheduler behavior by storing tasks and checking +to see if any are overdue with every request received; this would +be an excellent project for someone wishing to get started in Spyce +development. +

    +[[.compact mode=off]] +

    +[[ import scheduler, spyceUtil ]] +[[= spyceUtil.doc(scheduler) ]] +
    +[[.compact]] + +[[includeCode('examples/scheduling.py', run=0)]] + +[[toc.n('spyceUtil', 'runtime_util_su')]] + +Most of the spyceUtil module is interesting only to internal operations, +but several functions are more generally applicable: + +
      + +
    • url2file( url, relativeto=None ) +
      Returns the filesystem path of the file represented by url, relative + to a given path. For example, url2file('/index.spy') or + url2file('img/header.png', request.filename()). +
    • + +

    • exceptionString( ) +
      Every python programmer writes this eventually: returns a string containing + the description and stacktrace for the most recent exception. + +
    • + +

    + +[[toc.e()]] + +[[toc.n('Modules', 'mod')]] + +The Spyce language, as described above, is simple and small. Most +functionality is provided at runtime through Spyce modules and Python modules. +

    +The standard Spyce modules are documented here; some other modules are +also distributed in the contrib/ directory. + +

    +Non-implicit modules are +imported using the Spyce \[[.import\]] directive. Python modules are +imported using the Python import keyword. Remember that +modules need to have the same read permissions as regular files that you +expect the web server to read. + +

    +Modules may be imported with a non-default name using the as attribute +to the .import directive. This is discouraged for standard Spyce modules; the +session module, for example, expects to find or +otherwise load a module named cookie in the Spyce environment. + +

    +Once included, a Spyce module may be accessed anywhere in the Spyce code. + +[[toc.b('DB (implicit)', 'mod_db')]] + +[[.compact mode=off]] + +Spyce integrates an advanced database module to make reading and modifying +your data faster and less repetitive. +

    +Just initialize the db reference in your Spyce config file following +the examples given there, and you're all set. + +

    The general idea is, db.tablename represents the tablename table +in your database, and provides hooks for reading and modifying data in that +table in pure Python, no SQL required. We find this gives 90% of the benefits +of an object-relational mapping layer, without making developers learn another +complex tool. And since the Spyce db module is part of +SQLAlchemy, probably the most advanced +database toolkit in the world, the full ORM approach is available to those +who want it. + +

    +Here's a quick example of reading +and inserting data from a table called todo_lists: + +[[ includeCode('examples/db.spy') ]] + +

    Full SqlSoup documentation follows. + +

    Loading objects

    +

    +Loading objects is as easy as this: +

    +    >>> users = db.users.select()
    +    >>> users.sort()
    +    >>> users
    +    [MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0), 
    +     MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)]
    +
    + +

    +Of course, letting the database do the sort is better (".c" is short for ".columns"): +

    +    >>> db.users.select(order_by=[db.users.c.name])
    +    [MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1),
    +     MappedUsers(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0)]
    +
    + +

    +Field access is intuitive: +

    +    >>> users[0].email
    +    u'student@example.edu'
    +
    + +

    +Of course, you don't want to load all users very often. The common case is to +select by a key or other field: +

    +    >>> db.users.selectone_by(name='Bhargan Basepair')
    +    MappedUsers(name='Bhargan Basepair',email='basepair@example.edu',password='basepair',classname=None,admin=1)
    +
    + +

    Select variants

    +All the SqlAlchemy Query select variants are available. +Here's a quick summary of these methods: + +- get(PK): load a single object identified by its primary key (either a scalar, or a tuple) +- select(Clause, **kwargs): perform a select restricted by the Clause argument; returns a list of objects. The most common clause argument takes the form "db.tablename.c.columname == value." The most common optional argument is order_by. +- select_by(**params): the *_by selects allow using bare column names. (columname=value)This feels more natural to most Python programmers; the downside is you can't specify order_by or other select options. +- selectfirst, selectfirst_by: returns only the first object found; equivalent to select(...)[0] or select_by(...)[0], except None is returned if no rows are selected. +- selectone, selectone_by: like selectfirst or selectfirst_by, but raises if less or more than one object is selected. +- count, count_by: returns an integer count of the rows selected. + +See the SqlAlchemy documentation for details: +- general info and examples +- details on constructing WHERE clauses + +

    Modifying objects

    + +

    +Modifying objects is intuitive: +

    +    >>> user = _
    +    >>> user.email = 'basepair+nospam@example.edu'
    +    >>> db.flush()
    +
    + +

    (SqlSoup leverages the sophisticated SqlAlchemy unit-of-work code, so +multiple updates to a single object will be turned into a single UPDATE +statement when you flush.) + +

    +To finish covering the basics, let's insert a new loan, then delete it: +

    +    >>> db.loans.insert(book_id=db.books.selectfirst(db.books.c.title=='Regional Variation in Moss').id, user_name=user.name)
    +    MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
    +    >>> db.flush()
    +
    +    >>> loan = db.loans.selectone_by(book_id=2, user_name='Bhargan Basepair')
    +    >>> db.delete(loan)
    +    >>> db.flush()
    +
    + +

    +You can also delete rows that have not been loaded as objects. Let's do our insert/delete cycle once more, +this time using the loans table's delete method. (For SQLAlchemy experts: +note that no flush() call is required since this +delete acts at the SQL level, not at the Mapper level.) The same where-clause construction rules +apply here as to the select methods: +

    +    >>> db.loans.insert(book_id=book_id, user_name=user.name)
    +    MappedLoans(book_id=2,user_name='Bhargan Basepair',loan_date=None)
    +    >>> db.flush()
    +    >>> db.loans.delete(db.loans.c.book_id==2)
    +
    + +

    +You can similarly update multiple rows at once. This will change the book_id to 1 in all loans whose book_id is 2: +

    +    >>> db.loans.update(db.loans.c.book_id==2, book_id=1)
    +    >>> db.loans.select_by(db.loans.c.book_id==1)
    +    [MappedLoans(book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
    +
    + + +

    Joins

    + +

    Occasionally, you will want to pull out a lot of data from related tables all at +once. In this situation, it is far +more efficient to have the database perform the necessary join. (Here +we do not have "a lot of data," but hopefully the concept is still clear.) +SQLAlchemy is smart enough to recognize that loans has a foreign key +to users, and uses that as the join condition automatically. +

    +    >>> join1 = db.join(db.users, db.loans, isouter=True)
    +    >>> join1.select_by(name='Joe Student')
    +    [MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
    +     book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
    +
    + +

    +You can compose arbitrarily complex joins by combining Join objects with +tables or other joins. +

    +    >>> join2 = db.join(join1, db.books)
    +    >>> join2.select()
    +    [MappedJoin(name='Joe Student',email='student@example.edu',password='student',classname=None,admin=0,
    +     book_id=1,user_name='Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0),
    +     id=1,title='Mustards I Have Known',published_year='1989',authors='Jones')]
    +
    + +

    +If you join tables that have an identical column name, wrap your join with "with_labels", +and all the columns will be prefixed with their table name: +

    +    >>> db.with_labels(join1).select()
    +    [MappedUsersLoansJoin(users_name='Joe Student',users_email='student@example.edu',
    +                          users_password='student',users_classname=None,users_admin=0,
    +                          loans_book_id=1,loans_user_name='Joe Student',
    +                          loans_loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
    +
    + +

    Advanced usage

    + +

    +You can access the SqlSoup's engine attribute to compose SQL directly. +The engine's execute method corresponds +to the one of a DBAPI cursor, and returns a ResultProxy that has fetch methods +you would also see on a cursor. + +

    +    >>> rp = db.engine.execute('select name, email from users order by name')
    +    >>> for name, email in rp.fetchall(): print name, email
    +    Bhargan Basepair basepair+nospam@example.edu
    +    Joe Student student@example.edu
    +
    + +

    +You can also pass this engine object to other SQLAlchemy constructs; see the SQLAlchemy documentation for details. + +

    +You can access SQLAlchemy Table and Mapper objects as db.tablename._table and db.tablename._mapper, respectively. + +[[.compact]] + +[[toc.n('Request (implicit)', 'mod_request')]] + +The request module is loaded implicitly into every Spyce environment. +

    +The spyce configuration file gives two lists that affect the request module: +param_filters and file_filters. param_filters is a list of functions to run +against the GET and POST variables in the request; file filters is the same, +only for files uploaded. Each function will be passed the request module +and a dictionary when it is called; each will be called once for GET and once for POST +with each new request. +

    +These hooks exist because the request dictionaries should not be modified +in an ad-hoc manner; these allow you to set an application-wide policy +in a well-defined manner. You might, for instance, disallow all file uploads +over 1 MB. +

    +Here's an example that calls either Html.clean +or Html.escape (not shown) to ensure that no potentially harmful html can +be injected in user-editable areas of a site: + +[[includeCode('examples/filter.py', run=0) ]] + +

    +The request module provides the following methods: + +

      +
    • login_id:
      + Returns the id generated by your validator function if the user has logged in + via spy:login or spy:login_required, or None if the user is unvalidated. (See the + core tag library for details on + the login tags.) + +
    • uri( [component] ):
      Returns the request URI, or some + component thereof. If the optional component parameter is specified, + it should be one of the following strings: + + 'scheme', + 'location', + 'path', + 'parameters', + 'query' or + 'fragment'. + +
    • + +

    • method():
      Returns request method type (GET, POST, + ...)
    • + +

    • query():
      Returns the request query string
    • + +

    • get( [name], [default], [ignoreCase] ):
      Returns request GET + information. If name is specified then a single list of values is + returned if the parameter exists, or default, which defaults to an + empty list, + if the parameter does not exist. Parameters without values are skipped, + though empty string values are allowed. If name is omitted, then a + dictionary of lists is returned. If ignoreCase is true, then the + above behaviour is performed in a case insensitive manner (all parameters + are treated as lowercase).
    • + +

    • get1( [name], [default], [ignoreCase] ):
      Returns request GET + information, similarly to (though slightly differently from) the function + above. If name is specified then a single string is returned if the + parameter exists, or default, which default to None, if the parameter + does not exist. If there is more than one value for a parameter, then only + one is returned. Parameters without values are skipped, though empty string + values are allowed. If name is omitted, then a dictionary of strings is + returned. If the optional ignoreCase flag is true, then the above + behaviour is performed in a case insensitive manner (all parameters are + treated as lowercase).
    • + +

    • post( [name], [default], [ignoreCase] ):
      Returns request + POST information. If name is specified then a single list of values + is returned if the parameter exists, or default, which defaults to + an empty list, if the parameter does not exist. Parameters without values are + skipped, though empty string values are allowed. If name is omitted, then a + dictionary of lists is returned. If ignoreCase is true, then the + above behaviour is performed in a case insensitive manner (all parameters + are treated as lowercase). This function understands form information + encoded either as 'application/x-www-form-urlencoded' or + 'multipart/form-data'. Uploaded file parameters are not included in this + dictionary; they can be accessed via the file method.
    • + +

    • post1( [name], [default], [ignoreCase] ):
      Returns request + POST information, similarly to (though slightly differently from) the + function above. If name is specified then a single string is returned + if the parameter exists, or default, which defaults to None, if the + parameter does not exist. If there is more than one value for a parameter, + then only one is returned. Parameters without values are skipped, though + empty string values are allowed. If name is omitted, then a dictionary of + strings is returned. If the optional ignoreCase flag is true, then + the above behaviour is performed in a case insensitive manner (all + parameters are treated as lowercase). This function understands form + information encoded either as 'application/x-www-form-urlencoded' or + 'multipart/form-data'. Uploaded file parameters are not included in this + dictionary; they can be accessed via the file method.
    • + +

    • file( [name], [ignoreCase] ):
      Returns files POSTed in the + request. If name is specified then a single cgi.FieldStorage class is + returned if such a file parameter exists, otherwise None. If name is + omitted, then a dictionary of file entries is returned. If the optional + ignoreCase flag is true, then the above behaviour is performed in a + case insensitive manner (all parameters are treated as lowercase). The + interesting fields of the FieldStorage class are:

      + +

        + +
      • name: the field name, if specified; otherwise None
      • + +
      • filename: the filename, if specified; otherwise None; this is + the client-side filename, not the filename in which the content is stored + - a temporary file you don't deal with + +
      • value: the value as a string; for file uploads, this + transparently reads the file every time you request the value + +
      • file: the file(-like) object from which you can read the data; + None if the data is stored a simple string + +
      • type: the content-type, or None if not specified + +
      • type_options: dictionary of options specified on the + content-type line + +
      • disposition: content-disposition, or None if not specified + +
      • disposition_options: dictionary of corresponding options + +
      • headers: a dictionary(-like) object (sometimes rfc822.Message + or a subclass thereof) containing *all* headers + +

      + +

    • __getitem__( key ):
      The request module can be used as a + dictionary: i.e. request['foo']. This method first calls the get1() method, + then the post1() method and lastly the file() method trying to find the + first non-None value to return. If no value is found, then this method + returns None. Note: Throwing an exception seemed too strong a semantics, and + so this is a break from Python. One can also iterate over the request + object, as if over a dictionary of field names in the get1 and post1 + dictionaries. In the case of overlap, the get1() dictionary takes + precedence.
    • + +

    • getpost( [name], [default], [ignoreCase] ):
      Using given + parameters, return get() result if not None, otherwise return post() result + if not None, otherwise default.
    • + +

    • getpost1( [name], [default], [ignoreCase] ):
      Using given + parameters, return get1() result if not None, otherwise return post1() + result if not None, otherwise default.
    • + +

    • postget( [name], [default], [ignoreCase] ):
      Using given + parameters, return post() result if not None, otherwise return get() result + if not None, otherwise default.
    • + +

    • postget1( [name], [default], [ignoreCase] ):
      Using given + parameters, return post1() result if not None, otherwise return get1() + result if not None, otherwise default.
    • + +

    • env( [name], [default] ):
      Returns a dictionary with CGI-like + environment information of this request. If name is specified then a + single entry is returned if the parameter exists, otherwise default, + which defaults to None, if omitted.
    • + +

    • getHeader( [type] ):
      Return a specific header sent by the + browser. If optional type is omitted, a dictionary of all headers is + returned.
    • + +

    • filename( [path] ):
      Return the Spyce filename of the request + currently being processed. If an optional path parameter is provided, + then that path is made relative to the Spyce filename of the request + currently being processed.
    • + +

    • stack( [i] ):
      Returns a stack of files processed by the + Spyce runtime. If i is provided, then a given frame is returned, + with negative numbers wrapping from the back as per Python convention. + The first (index zero) item on the stack is the filename + corresponding to the URL originally requested. The last (index -1) item + on the stack is the current filename being processed. Items are added + to the stack by includes, + Spyce lambdas, and + internal redirects.

      + +

    • default( value, value2 ):
      (convenience method) Return + value if it is not None, otherwise return value2.
    • + +

    + +The example below presents the results of all the method calls list above. Run +it to understand the information available.

    + +[[includeCode('examples/request.spy') ]] +

    + +Lastly, the following example shows how to deal with uploaded files.

    + +[[includeCode('examples/fileupload.spy') ]]

    + +[[toc.n('Response (implicit)', 'mod_response')]] + +Like the request module, the response module is also loaded implicitly into every +Spyce environment. It provides the following methods: + +

      + +
    • write( string ):
      Sends a string to the client. All + writes are buffered by default and sent at the end of Spyce processing to + allow appending headers, setting cookies and exception handling. Note that + using the print statement is often easier, and + stdout is implicitly redirected + to the browser.
    • + +

    • writeln( string ):
      Sends a string to the client, and + appends a newline.
    • + +

    • writeStatic( string ):
      All static HTML strings are + emitted to the client via this method, which (by default) simply calls + write(). This method is not commonly invoked by the user.
    • + +

    • writeExpr( object ):
      All expression results are emitted to + the client via this method, which (by default) calls write() with the str() + of the result object. This method is not commonly invoked by + the user.
    • + +

    • clear( ): Clears the output buffer.
    • + +

    • flush( ): Sends buffered output to the client immediately. This + is a blocking call, and can incur a performance hit.
    • + +

    • setContentType( contentType ):
      Sets the MIME content + type of the response.
    • + +

    • setReturnCode( code ):
      Set the HTTP return code for this + response. This return code may be overriden if an error occurs or by + functions in other modules (such as redirects).
    • + +

    • addHeader( type, data, [replace] ):
      Adds the header line + "type: data" to the outgoing response. The + optional replace flag determines whether any previous headers of the + same type are first removed.
    • + +

    • unbuffer():
      Turns off buffering on the output stream. In + other words, each write is followed by a flush(). An unbuffered output + stream should be used only when sending large amounts of data (ie. file + transfers) that would take up server memory unnecessarily, and involve + consistently large writes. Note that using an unbuffered response stream + will not allow the output to be cleared if an exception occurs. It will also + immediately send any headers.
    • + +

    • isCancelled():
      Returns true if it has been detected that the + client is no longer connected. This flag will turn on, and remain on, after + the first client output failure. However, the detection is best-effort, and + may never turn on in certain configurations (such as CGI) due to buffering. +
    • + +

    • timestamp( [t] ):
      Timestamps the response with an HTTP + Date: header, using the optional t + parameter, which may be either be the number of seconds since the epoch + (see Python time + module), or a properly formatted HTTP date string. If t is omitted, + the current time is used.
    • + +

    • expires( [t] ):
      Sets the expiration time of the + response with an HTTP Expires: header, using the + optional t parameter, which may be either the number of seconds + since the epoch (see Python time + module), or a properly formatted HTTP date string. If t is omitted, + the current time is used.
    • + +

    • expiresRel( [secs] ):
      Sets the expiration time of the + response relative to the current time with an HTTP Expires: header. The optional secs (which may + also be negative) indicates the number of seconds to add to the current time + to compute the expiration time. If secs is omitted, it defaults to zero. +
    • + +

    • lastModified( [t] ):
      Sets the last modification time of + the response with an HTTP Last-Modified: header, + using the optional t parameter, which can be either the number + of seconds since the epoch (see Python time + module), or a properly formatted HTTP date string, or None indicating the + current time. If t is omitted, this function will default to the last + modification time of the Spyce file for this request, and raise an exception + if this time can not be determined. Note that, as per the HTTP + specification, you should not set a last modification time that is beyond + the response timestamp.
    • + +

    • uncacheable():
      Sets the HTTP/1.1 Cache-Control: and HTTP/1.0 Pragma: headers to inform clients and proxies that this + content should not be cached.
    • + +

    + +The methods are self-explanatory. One of the more interesting things that one could do is +to emit non-HTML content types. The example below emits the Spyce logo as a GIF.

    + +[[includeCode('examples/gif.spy') ]]

    + +[[toc.n('Redirect', 'mod_redirect')]] + +The redirect module allows requests to be redirected to different pages, by +providing the following methods: + +

      + +
    • internal( uri ):
      Performs an internal redirect. All + processing on the current page ends, the output buffer is cleared and + processing continues at the named uri. + The browser URI remains + unchanged, and does not realise that a redirect has even occurred during + processing.
    • + +

    • external( uri, [permanent] ):
      Performs an external redirect + using the HTTP Location header to a new uri. Processing of the + current file continues unless you raise spyceDone, + but the content is ignored (ie. the buffer is + cleared at the end). The status of the document is set to 301 MOVED + PERMANENTLY or 302 MOVED TEMPORARILY, depending on the permanent + boolean parameter, which defaults to false or temporary. The redirect + document is sent to the browser, which requests the new relative uri. +
    • + +

    • externalRefresh( uri, [seconds] ):
      Performs an external + redirect using the HTTP Refresh header a new uri. Processing of the + current file continues, and will be displayed on the browser as a regular + document. Unless interrupted by the user, the browser will request the new + URL after the specified number of seconds, which defaults to zero if + omitted. Many websites use this functionality to show some page, while a + file is being downloaded. To do this, one would show the page using Spyce, + and redirect with an externalRefresh to the download URI. Remember to set + the Content-Type on the target download file page + to be something that the browser can not display, only download.
    • + +

    + +The example below, shows the possible redirects in use:

    + +[[includeCode('examples/redirect.spy') ]]

    + +[[toc.n('Cookie', 'mod_cookie')]] + +This module provides cookie functionality. Its methods are: + +

      + +
    • get( [key] ):
      Return a specific cookie string sent by the + browser. If the optional cookie key is omitted, a dictionary of all + cookies is returned. The cookie module may also be accessed as an + associative array to achieve the same result as calling: namely, cookie['foo'] and cookie.get('foo') are equivalent.
    • + +

    • set( key, value, [expire], [domain], [path], [secure] ):
      + Sends a cookie to the browser. The cookie will be sent back on + subsequent requests and can be retreived using the get function. The + key and value parameters are required; the rest are optional. + The expire parameter determines how long this cookie information will + remain valid. It is specified in seconds from the current time. If expire is + omitted, no expiration value will be provided along with the cookie header, + meaning that the cookie will expire when the browser is closed. The + domain and path parameters specify when the cookie will get + sent; it will be restricted to certain document paths at certain domains, + based on the cookie standard. If these are omitted, then path and/or domain + information will not be sent in the cookie header. Lastly, the secure + parameter, which defaults to false if omitted, determines whether the cookie + information can be sent over an HTTP connection, or only via HTTPS.
    • + +

    • delete( key ):
      Send a cookie delete header to the browser to + delete the key cookie. The same may be achieved by: del cookie[key].
    • + +

    + +The example below shows to manage browser cookies.

    + +[[includeCode('examples/cookie.spy') ]]

    + +[[toc.n('Session', 'mod_session')]] + +Sessions allow information to be efficiently passed from one user request to +the next via some browser mechanism: get, post or cookie. Potentially large or +sensitive information is stored at the server, and only a short identifier is +sent to the client to be returned on callback. Sessions are often used to +create sequences of stateful pages that represent an application or work-flow. +

    +This module automates sessioning for a Spyce web site. It emulates +a dictionary specific to each user (really each browser) accessing +your web site. You simply use session as if it were a dictionary +variable, and its contents automatically change depending upon the +user calling the page. For example: + +[[includeCode('examples/session2.spy')]] + +

    +In the example above, the 'visited' key would now be valid for +all pages on your site, until the session expires. + +

    +Global session options +

    +These options are configured only in the Spyce config file: +

      +
    • session_store: declares the backing storage for session + information. It should be either session.DbmStore(path) or session.MemoryStore(). In-memory sessions storage is + faster, but volatile, and does not work in multi-process server + configurations. Advanced users are welcome to create their own storage + manager, by subclassing session.SessionStore.
    • +
    + +Advanced users can create their own storage manager by subclassing +session.SessionStore. + +

    +Per-session options +

    +These options are set in the Spyce config file, but may be overridden +at module-import time on a per-page basis: + +

      +
    • session_path: the default path to attach the session to. +This refers to +cookie semantics. For example, if the path is /myapp, the session +will only be valid under /myapp pages (and below). The default is +'/', which means the session is valid site-wide. + +
    • session_expire: the number of seconds the session is good for. +The default is one day. + +

      +You should clean up expired session state periodically. The easiest way +is to schedule session.clean_store every day or so in your config file: + +[[.compact mode=off]] +

      +import session, scheduler
      +scheduler.schedule_daily(0, 0, session.clean_store)
      +
      +[[.compact]] + +

      +(Note: for backwards compatibility, there is also a module called "session1." +New code should simply use the module described here.) + + +[[toc.n('Pool', 'mod_pool')]] + +The pool module provides support for server-pooled variables. That is support +for variables whose lifetime begins when declared, and ends when explicitly +deleted or when the server dies. These variables are often useful for caching +information that would be expensive to +compute from scratch for each request. Another common use of pool variables is to store +file- or memory-based lock objects for concurrency control. A pooled variable +can hold any Python value.

      + +The pool module may be accessed as a regular dictionary, supporting the usual +get, set, delete, has_key, keys, values and clear operations. +

      + +The example below shows how the module is used:

      + +[[includeCode('examples/pool.spy')]]

      + +

      +Pool performance suffers when not used with the Spyce webserver in +threaded concurrency mode; Spyce has to un/pickle the shared pool with +each request since there is no single long-lived process that can keep +the data in-memory. + +[[toc.n('Transform', 'mod_transform')]] + +The transform module contains useful text transformation functions, commonly +used during web-page generation.

      + +

        + +
      • html_encode( string, [also] ):
        Returns a HTML-encoded + string, with special characters replaced by entity references as + defined in the HTML 3.2 and 4 specifications. The optional also + parameter can be used to encode additional characters.
      • + +

      • url_encode( string, ):
        Returns an URL-encoded string, + with special characters replaced with %XX equivalents as defined by the URI + RFC document.
      • + +

      + +The transform module also be used to intercept and insert intermediate +processing steps when response.writeStatic(), +response.writeExpr() and response.write() are called to emit +static html, expressions and dynamic content, respectively. It can be useful, +for example, to automatically ensure that expressions never produce output +that is HTML-unsafe, in other words strings that contain characters such as +[[=transform.html_encode('''&, < and >''')]]. Many interesting processing +functions can be defined. By default, the transform module leaves all output +untouched. These processing functions, called filters, can be inserted via the +following module functions:

      + +

        + +
      • static( [ fn ] ):
        Defines the processing performed on all + static HTML strings from this point forwards. The fn parameter is + explained below.
      • + +

      • expr( [ fn ] ):
        Defines the processing performed on all the + results of all expression tags from this point forwards. The fn + parameter is explained below.
      • + +

      • dynamic( [ fn ] ):
        Defines the processing performed on all + dynamic content generated, i.e. content generated using response.write in the + code tags. The fn parameter is explained below.
      • + +

      +

      + +Each of the functions above take a single, optional parameter, which specifies +the processing to be performed. The parameter can be one of the following +types: + +

        + +
      • None:
        If the paramter is None, or omitted, then no processing + is performed other converting the output to a string.
      • + +

      • Function:
        If a parameter of function type is specified, then + that function is called to process the output. The function input can be any + Python type, and the function output may be any Python type. The result is + then converted into a string and emitted. The first parameter to a filter + will always be the object to be processed for output. However, the function + should be properly defined so as to possibly accept other parameters. The + details of how to define filters are explained below.
      • + +

      • String:
        If a paramter of string type is specified, then the + string should be of the following format: "file:name", where file is the location where + the function is defined and name is the name of the filter. The file + component is optional, and is searched for using the standard module-finding + rules. If only the function name is specified, then the default location + (inside the transform module itself) is used, where the standard Spyce + filters reside. The standard Spyce filters are described below.
      • + +

      • List / Tuple:
        If a parameter of list or tuple type is + specified, its elements should be functions, strings, lists or + tuples. The compound filter is recursively defined as + f=fn(...f2(f1())...), for the parameter + (f1,f2,...,fn). +
      • + +

      +

      + +Having explained how to install filters, we now list the standard Spyce +filters and show how they are used: + +

        + +
      • ignore_none( o ):
        Emits any input o except for None, + which is converted into an empty string.
      • + +

      • truncate( o, [maxlen] ):
        If maxlen is specified, + then only the first maxlen characters of input o are returned, + otherwise the entire original.
      • + +

      • html_encode( o, [also] ):
        Converts any '&', '<' and + '>' characters of input o into HTML entities for safe inclusion in + among HTML. The optional also parameter can specify, additional + characters that should be entity encoded.
      • + +

      • url_encode( o ):
        Converts input o into a URL-encoded + string.
      • + +

      • nb_space( o ):
        Replaces all spaces in input o with + "&nbsp;".
      • + +

      • silence( o ):
        Outputs nothing.
      • + +

      +

      + +The optional parameters to some of these filters can be passed to the various +write functions as named parameters. They can also be specified in an +expression tag, as in the following example. (One should simply imagine that +the entire expression tag is replaced with a call to response.writeExpr). + +
      +[[.compact mode=off]] +
      \[[.import name=transform\]]
      +\[[ transform.expr(("truncate", "html_encode")) \]]
      +\[[='This is an unsafe (< > &) string... '*100, maxlen=500\]] 
      +[[.compact]] +
      +

      + +In the example above, the unsafe string is repeated 100 times. It is then +passed through a truncate filter that will accept +only the first 500 characters. It is then passed through the html_encode filter that will convert the unsafe +characters into their safe, equivalent HTML entities. The resulting string is +emitted.

      + +The parameters (specified by their names) are simply accepted by the +appropriate write method (writeExpr() in the case above) and passed along to +the installed filter. Note that in the case of compound filters, the +parameters are passed to ALL the functions. The html_encode filter is +written to ignore the maxlen parameter, and does not fail.

      + +For those who would like to write their own filters, looking at the definition +of the truncate filter will help. The other standard filters are in modules/transform.py. + +
      +[[.compact mode=off]] +
      def truncate(o, maxlen=None, **kwargs):
      +[[.compact]] +
      +

      + +When writing a filter, any function will do, but it is strongly advised to +follow the model above. The important points are: + +

        + +
      • The input o can be of any type, not only a string.
      • + +
      • The function result does not have to be string either. It is + automatically stringified at the end.
      • + +
      • The function can accept parameters that modify its behaviour, such + as maxlen, above.
      • + +
      • It is recommended to provide convenient user defaults for all + parameters.
      • + +
      • The last parameter should be **kwargs so that unneeded parameters + are quietly passed along.
      • + +
      +

      + +Lastly, one can retrieve filters. This can be useful when creating new +functions that depend on existing filters, but can not be compounded using the +tuple syntax above. For example, one might use one filter or another +conditionally. For whatever purpose, the following module function is provided +to retreive standard Spyce filters, if needed:

      + +

        + +
      • create( [ fn ] ):
        Returns a filter. The fn parameter + can be of type None, function, string, list or tuple and is handled as in + the installation functions discussed above.
      • + +

      + +The transform module is flexible, but not complicated to use. The example +below is not examplary of typical use. Rather it highlights some of the +flexibility, so that users can think about creative uses.

      + +[[includeCode('examples/transform.spy') ]]

      + +[[toc.n('Compress', 'mod_compress')]] + +The compress module supports dynamic compression of Spyce output, and can save +bandwidth in addition to static compaction. The different forms +of compression supported are described below. + +

        + +
      • spaces( [ boolean ] ):
        Controls dynamic space compression. + Dynamic space compression will eliminate consecutive whitespaces (spaces, + newlines and tabs) in the output stream, each time it is flushed. The optional + boolean parameter defaults to true.

        + +

      • gzip( [ level ] ):
        Applies gzip compression to the Spyce + output stream, but only if the browser can support gzip content encoding. Note + that this function will fail if the output stream has already been flushed, + and should generally only be used with buffered output streams. The optional + level parameter specifies the compression level, between 1 and 9 + inclusive. A value of zero disables compression. If level is omitted, the + default gzip compression level is used. This function will automatically check + the request's Accept-Encoding header, and set the response's + Content-Encoding header.

        + +

      + +The example below shows the compression module in use.

      + +[[includeCode('examples/compress.spy')]] +

      + +Note that the compression functions need not be called at the beginning of the +input, but before the output stream is flushed. Also, to really see what is +going on, you should telnet to your web server, and provide something like the +following request. + +[[.compact mode=off]] +
      + +
      GET /spyce/examples/compress.spy HTTP/1.1
      +Accept-Encoding: gzip
      +
      +
      +[[.compact]]

      + +[[toc.n('Include', 'mod_include')]] + +Many websites carry a theme across their various pages, which is often +achieved by including a common header or footer. This is best done with a +parent template from the spy:parent tag, +but you can also do this with the include module for backwards compatibility +with Spyce 1.x. +

      +Another option to consider for repeating a common task is +a custom +active tag. +

      +The include module can also pretty print Spyce code or include the contents of +anything in your filesystem. + +

        + +
      • spyce( file, [context] ):
        Dynamically includes the specified + file (corresponding to the Spyce document root, not filesystem), + and processes it as Spyce code. The return value is that of the + included Spyce file. One can optionally provide a context value to + the included file. If omitted, the value defaults to None. All currently + imported modules are passed along into the included file without + re-initialization. However, for each explicit \[[.import \]] tag in the included file, a new + module is initialized and also finalized up at the end of processing. The + include module provides three fields for use inside included files:

        + +

          + +
        • include.context: This field stores the value passed in at the + point of inclusion. Note that if the value is one that is passed by + reference (as is the case with object, list, and dictionary types), then + the context may be used to pass information back to the including file, in + addition to the return value.
        • + +

        • include.vars: If the include context is of type dictionary, + then the vars field is initialized, otherwise it is None. The vars field + provides attribute-based access to the context dictionary, merely for + convenience. In other words, include.vars.x is + equivalent to include.context['x'].
        • + +

        + + Note that either the locals() or globals() dictionaries may be passed in as + include contexts. However, be advised that due to Python optimizations of + local variable access, any updates to the locals() dictionary may not be + reflected in the local namespace under all circumstances and all versions of + Python. In fact, this is the reason why the context has been made explicit, + and does not simply grab the locals() dictionary. It may, however, safely be + used for read access. With respect to the globals() dictionary, it is not + advised to pollute this + namespace.
      • + +

      • spyceStr( file, [context] ):
        Same as spyce(), but + performs no output and instead returns the processed included Spyce file as + a string.
      • + +

      • dump( file, [binary] ):
        Contents of the file + (from the filesystem -- use spyceUtil.url2file(url, request.filename) + if you need to turn a url into a filesystem path) + are returned. If the binary parameter is true, the file is opened in + binary mode. By default, text mode is used. +

        Be careful not to blindly trust the user to specify which file + to dump, since anything your Spyce process has access to in the filesystem + is fair game. +

      • + +

      • spycecode( file ):
        Contents of the file + (relative to the Spyce document root) + are returned + as HTML formatted Spyce code.
      • + +

      + +The example below (taken from this documentation file), uses a common header +template only requiring two context variables to change the title and the +highlighted link:
      + +[[.compact mode=off]] +
      + +
        \[[.import name=include\]]
      +  \[[include.spyce('inc/head.spi', 
      +      {'pagename': 'Documentation', 
      +       'page': 'manual.html'}) \]]
      +
      +

      +[[.compact]] + +In head.spi, we use this information to set the title:

      + +[[.compact mode=off]] +
      + +
      +  \[[.import name=include\]]
      +  [[=escapeHTMLandCode("[+=include.context['pagename'] +]") ]]
      +
      +
      +

      +[[.compact]] + +By convention, included files are given the extension .spi.

      + +Below we contrast the difference between static and dynamic includes. A +dynamic include is included on each request; a static include is inserted at +compile time. A static include runs in the same context, while a dynamic +include has a separate context.

      + +[[includeCode('examples/include.spy') ]]

      +[[includeCode('examples/include.spi', run=0) ]]

      +[[includeCode('examples/includestatic.spy') ]]

      +[[includeCode('examples/includestatic.spi', run=0) ]]

      + +[[toc.n('Internal modules', 'mod_internal')]] + +These modules are used internally by Spce. Documentation is included for +those curious about Spyce internals; ordinarily you will never use these +modules directly. + +[[toc.b('Error', 'mod_error')]] + +The error module is implicitly loaded and provides error-handling +functionality. An error is any unhandled runtime exception that +occurs during Spyce processing. This mechanism does not include +exceptions that are not related to Spyce processing (i.e. server-related +exceptions), that can be caused before or after Spyce processing by invalid +syntax, missing files and file access restrictions. To install a server-level +error handler use a configuration +file. The default page-level error handler can also be modified in the configuration file. This module +allows the user to install page-level error handling code, overriding the +default page-level handler, by using one of the following functions:

      + +

        + +
      • setStringHandler( string ):
        Installs a function that will + processes the given string, as Spyce code, for error handling. +
      • + +

      • setFileHandler( uri ):
        Installs a function that will + processes the given uri for error handling.
      • + +

      • setHandler( fn ):
        Installs the fn function for error + handling. The function is passed one parameter, a reference to the error + module. From this, all the error information as well as references to other + modules and Spyce objects can be accessed.
      • + +

      + +The error module provides the following information about an error:

      + +

        + +
      • isError():
        Returns whether an error is being handled. +
      • + +

      • getMessage():
        Return the error message; the string of the + object that was raised, or None if there is no current error.
      • + +

      • getType():
        Return the error type; the type of the object + that was raised, or None if there is no current error.
      • + +

      • getFile():
        Return the file where the error was raised, or + None if there is no current error.
      • + +

      • getTraceback():
        Return the stack trace as an array of + tuples, or None if there is no current error. Each tuple entry is of the + form: (file, line numbers, function name, code context).
      • + +

      • getString():
        Return the string of the entire error (the + string representation of the message, type, location and stack trace), or + None if there is no current error.
      • + +

      + +The default error handling function uses the following string handler: + +[[\ + template = response._api.spyceModule('defaultErrorTemplate', 'error.py') +]] +
      +
      [[=include.spycecode(string=template)]]
      +
      +

      + +The example below shows the error module in use. Error handling can often be +used to send emails notifying webmasters of problems, as this example shows. +

      + +[[includeCode('examples/error.spy')]] +

      + +[[includeCode('examples/error.spi', run=0)]] +

      + +This mechanism is not a subsititute for proper exception handling within the +code itself, and should not be abused. It does, however, serve as a useful +catch-all for bugs that slip through the cracks.

      + +[[toc.n('Stdout', 'mod_stdout')]] + +The stdout module is loaded implicitly and redirects Python's sys.stdout (in a thread-safe manner) to the appropriate +response object for the duration of Spyce processing. This allows one to use +print, without having to write print >> response, .... The stdout +module provides a variable stdout.stdout, which +refers to the original stream, but is unlikely to be needed. It may also be +useful to know that sys.stderr is, under many +configurations, connected to the webserver error log.

      + +In addition, the stdout module provides the following functions for capturing +or redirecting output: + +

        + +
      • push( [filename] ):
        Begin capturing output. Namely, the current + output stream is pushed onto the stack and replaced with a memory buffer. An + optional filename may be associated with this operation (see pop() + method below).
      • + +

      • pop():
        Close current output buffer, and return the captured + output as a string. If a filename was associated with the push(), then the + string will also be written to that file.
      • + +

      • capture(f, [*args], [**kwargs] ):
        Push the current stream, + call the given function f with any supplied arguments *args + and keyword arguments **kwargs, and then pop it back. Capture returns + a tuple (r,s), where r is the result returned by f and s is a string of its + output.
      • + +

      + +The example below show how the module is used: + +[[includeCode('examples/stdout.spy')]] + +[[toc.n('Spylambda', 'mod_lambda')]] + +This module is used internally by Spyce, not usually by users. Documentation is +included for those curious about Spyce internals. The spylambda module is +loaded implicitly and allows the definition of functions based on Spyce +scripts; see Spyce Lambdas. The +spylambda module provides the following methods: + +

        + +
      • define( args, code, [memoize] ):
        Returns a function that + accepts the given args and executes the Spyce script defined by the + code parameter. Note that the code is compiled immediately and that + spyce.spyceSyntaxError or spyce.spycePythonError exceptions can be thrown for + invalid code arguments. The optional memoize parameter sets whether + the spyce can or can not be memoized, with the default being false. + Memoizing a function means capturing the result and output and caching them, + keyed on the function parameters. Later, if a function is called again with + the same parameters, the cached information is returned, if it exists, and + the function may not actually be called. Thus, you should only memoize + functions that are truly functional, i.e. they do not have side-effects: + they only return a value and output data to the response object, and their + behaviour depends exclusively on their parameters. If you memoize code that + does have side-effects, those side-effects may not occur on every + invocation.
      • + +

      • __call__( args, code, _spyceCache ):
        This is an alias to the + define function. Because of the special method name, the spylambda module + object can be called as + if it were a function.
      • + +

      + +[[toc.n('Taglib', 'mod_taglib')]] + +The taglib module is loaded implicitly and supports +Active Tags +functionality. The taglib module provides the following +methods: + +
        + +
      • load( libname, [libfrom], [libas] ):
        Loads a tag library + class named libname from a file called libfrom in the search + path, and installed it under the tag prefix libas. The default for + libfrom is libname.py. The default for + libas is libname. Once installed, a library + name is its unique tag prefix.
      • + +

      • unload( libname ):
        Unload a tag library that is installed + under the libname prefix. This is usually performed only at the end + of a request.
      • + +

      • tagPush( libname, tagname, pair ):
        Push a new tag object for + a libname:tagname tag onto the tag stack. The pair + parameter is a flag indicating whether this is a singleton or a paired tag. +
      • + +

      • tagPop():
        Pop the current tag from the tag stack.
      • + +

      • getTag():
        Return the current tag object.
      • + +

      • outPush():
        Begin capturing the current output stream. This + is usually called by the tagBegin method.
      • + +

      • outPopCond():
        End capturing the current output stream, and + return the captured contents. It will only "pop" once, even if called + multiple times for the same tag. This method is usually called by either the + tagEnd(), tagCatch, or tagPop() methods.
      • + +

      • tagBegin( attrs ):
        This method sets the tag output and + variable environment, and then calls the tag's begin() method with + the given attrs tag attribute dictionary. This method returns a flag, + and the tag body must be processed if and only if this flag is true.
      • +

        + +

      • tagBody():
        This method sets the tag output and variable + environment, and then calls the tag's body() method with the captured + output of the body processing. If this method returns true, then the + processing of the body must be repeated.
      • + +

      • tagEnd():
        This method sets the tag output and variable + environment, and then calls the tag's end() method. This method must + be called if the tagBegin() method completes successfully in order to + preserve tag semantics.
      • + +

      • tagCatch():
        This method should be called if any of the + tagBegin, tagBody or tagEnd methods raise an exception. It calls the tag's + catch() method with the current exception.
      • + +

      + +[[toc.e()]] + +[[toc.n('Writing Modules', 'mod_new')]] + +Writing your own Spyce modules is simple. +

      +A Spyce modules is simply a Python class that exposes specific methods +to the Spyce server. The most important are start, finish, +and init. With these, +a Spyce module may access the +internal request and response structures or alter the behaviour of the +runtime engine in some way. + +

      Let us begin with a basic example +called myModule. It is a module that implements one function named foo(). + +[[includeCode('examples/myModule.py', run=0)]] +

      + +Saving this code in myModule.py in the same +directory as the Spyce script, or somewhere on the module path, we could use +it as expected:

      + +
      +[[.compact mode=off]] +
      \[[.import name=myModule\]]
      +\[[ myModule.foo() \]]
      +
      +[[.compact]] +
      +

      + +A Spyce module can be any Python class that derives from +spyceModule.spyceModule. When it is loaded, Spyce assigns it a +__file__ attribute indicating its source location. +Do not override the __init__(...) +method because it is inherited from spyceModule and has an fixed signature +that is expected by the Spyce engine's module loader. The inherited method +accepts a Spyce API object, a Bastion +of spyce.spyceWrapper, an internal engine object, and stores it in +self._api. This is the building block for all the functionality that +any module provides. The available API methods of the wrapper are (listed in +spyceModule.spyceModuleAPI): + +

        + + [[ for api in spyceModule.spyceModuleAPI: { ]] + +
      • [[=api]]: [[=eval('spyce.spyceWrapper.%s.__doc__'%api)]]
      • + + [[ } ]] + +

      + +For convenience, one can sub-class the spyceModulePlus class instead of +the regular spyceModule. The spyceModulePlus defines a +self.modules field, which can be used to acquire references to other +modules loaded into the Spyce environment. The response module, for +instance, would be referenced as self.modules.response. Modules are +loaded on demand, if necessary. The spyceModulePlus also contains a +self.globals field, which is a reference to the Spyce global namespace +dictionary, though this should rarely be needed.

      + +Note: It is not expected that many module writers will need the entire +API functionality. In fact, the vast majority of modules will use a small +portion of the API, if at all. Many of these functions are included for just +one of the standard Spyce modules that needs to perform some esoteric +function.

      + +Three Spyce module methods, start(), init([args]) and +finish(error) are special in that they are automatically called by the +runtime during Spyce request startup, processing and cleanup, respectively. +The modules are started in the order in which module directives appear in the +file, before processing begins. The implicitly loaded modules are always +loaded first. The init method is called during Spyce processing at the +location of the module directive in the file, with the optional args attribute +is passed as the arguments of this call. Finally, after Spyce processing is +complete, the modules are finalized in reverse order. If there is an unhandled +exception, it will be wrapped in a spyce.spyceException object and passed as +the first parameter to finish(). During successful completion of Spyce +processing (i.e. without exception), the error parameter is None. The default +inherited start, init and finish methods from spyceModule are noops.

      + +Note 2: When writing a Spyce module, consider carefully why you are +selecting a Spyce module over a regular Python module. If it is just code, +that does not interact with the Spyce engine, then a regular Python import instead of an Spyce \[[.import\]] can just as easily bring in the necessary +code, and is preferred. In other words, choose a Spyce module only when there +is a need for per-request initialization or for one of the engine APIs.

      + +Module writers are encouraged to look at the existing standard modules as +examples and the definitions of the core Spyce objects in spyce.py as well. If you write or use a novel Spyce +module that you think is of general use, please email your +contribution, +or a link to it. Also, please keep in mind that the standard modules are designed +with the goal of being minimalist. Much functionality is readily available +using the Python language libraries. If you think that they should be +expanded, also please send a note.

      + +[[toc.e()]] + +[[toc.n('Tags', 'tag')]] + +The previous chapter discussed the Spyce module facility, the standard Spyce +modules and how users can create their own modules to extend Spyce. Spyce +functionality can also be extended via active tags, which are defined in tag +libraries. This chapter describes what Spyce active tags are, and how they are +used. We then describe each of the standard active tag libraries and, finally, +how to define new tags libraries.

      + +It is important, from the outset, to define what an active tag actually does. +A few illustrative examples may help. The examples below all use tags that are +defined in the core tag library, +that has been installed under the spy prefix, as is the default.

      + +

        + +
      • <spy:parent src="parent.spi"/>
        + Wraps the current page in the parent template found at parent.spi in the + same directory. + +
      • <spy:for items="=range(5)">
        +   <spy:print value="=foo"/>
        </spy:for>
        +
        As expected, these tags will print the value of foo, set to + bar above, 5 times.
      • + +

      +Common mistake: Don't use \[[= \]] to send values to active tag attributes: +\[[= \]] sends its result directly to the output stream. And since those tokens +are parsed with higher precedence than the tag markup, Spyce won't recognize +your tag at all and will print it verbatim to the client. +Instead, prefix an expression with =, as in "=range(5)" above, and Spyce will +eval it before sending it to the tag. + +

      +Note that the same output could have been achieved in many different ways, and +entirely without active tags. The manner in which you choose to organize your +script or application, and when you choose active tags over other +alternatives, is a matter of personal preference. Notice also that active tags +entirely control their output and what they do with their attributes and the +result of processing their bodies (in fact, whether the body of the tag is +even processed). Tags can even supply additional syntax constraints on their +attributes that will be enforced at compile-time. Most commonly a tag could +require that certain attributes exist, and possibly that it be used only as a +single or only as a paired (open and close) tag. Unlike early versions of +HTML, active tags must be strictly balanced, and this will be enforced by the +Spyce compiler.

      + +Below, each individual standard Spyce tag library is documented, followed by a +description of how one would write a new +active tag library. The following general information will be useful for +reading that material. + +

        + +
      • Active tags are installed using the \[[.taglib\]] + directive, under some prefix. + Tag libraries may also be be loaded globally in the config module; by default + the core and form libraries are preloaded. + Active tags are of the format <pre:name ... >, where pre is the + prefix under which the tag library was installed, and name is defined + by the tag library. In the following tag library documentation, the prefix + is omitted from the syntax.
      • + +
      • The following notation is used in the documentation of the tag libraries + below: + +
          + +
        • <name .../> : The tag should be used as a singleton.
        • + +
        • <name ... > ... </name> : The tag should be used as an + open-close pair.
        • + +
        • [ x (default)] : The attribute is optional. Attributes not enclosed in + brackets are required.
        • + +
        • foo|bar : indicates that an attribute may be one of two + constant strings. The underlined value is the default.
        • + +
        • string : an arbitrary string constant, never evaluated as Python
        • + +
        • exprstring : may be a string constant, and may be of the form + '=expr', where expr is + a Python expression that will be evaluated in the tag context.
        • + +
        • expr: a Python expression. (Currently only the "data" parameters + of some form and core tags use this rather than exprstring.) +
        • + +
        • exports foo, *bar Exports to the parent context + the variable foo and the variable + with the name given by the expression bar. Normally, implementation + details of tags will not affect the parent context, so you do not have + to worry about your variables being clobbered. Tags may, however, + export specific parts of their own context to the parent. + See, for example, + the let and for tags in the core taglib. + Note: exporting of variables whose name + cannot be determined at compile time is deprecated, and will be removed in Spyce 2.2. +
        • + +
        + +

      + +[[toc.b('Core', 'tag_core')]] +[[.compact mode=off]] +The core tag library is aliased as "spy" by default. +

      +This library contains various frequently-used tags: the parent tag, login tags, +and some tags for generating lists and tables from Python iterators. + +

      +Parent Tag +

        +
      • <parent [src=url] [other parameters] /> +
        + Specifies a parent template to apply to the current page, which + is passed to the parent as child._body. Any extra parameters are also + passed in the child dictionary. If src is not given, 'parent.spi' used if it exists + in the current directory; otherwise, the default parent + is used as specified in the config module. +

        + [[includeCode('examples/hello-templated.spy')]] +

      + +Login Tags +
        +
      • <login [validator=function name] /> +
        + Generates a login form according to the template specified in your config file. + If validator is not specified, the default validator from your config file is used. + (validator may be the name of a function in a different Python module; + just prefix it with the module name and Spyce will automaticall import it when + necessary.) +

        + [[includeCode('examples/login-optional.spy')]] + +

      • <logout /> +
        + Generates a logout button that will clear the cookie generated by login + and login_required. +

        + +

      • <login_required [validator=function name] /> +
        + If a valid login cookie is not present, generates a login form according to the + template specified in your config file, then halts execution of the current page. +

        + (You may log in to this example as user spyce, password spyce.) + [[includeCode('examples/login-required.spy')]] +

      + +Convenience Tags +

      +These tags are shortcuts for creatings lists and tables. As with +the form tag library, any python +iterator may be given as the data parameter. Also as with the form tags, +any unrecognized parameters will be passed through to the generated HTML. + +

        +
      • <ul data=expr] /> +
        + Convenience tag for the common use of ul; equivalent to +
        +
        +    <ul>
        +    \[[ for item in data:{ \]]
        +      <li>\[[= item \]]</li>
        +    \[[ } \]]
        +    </ul>
        +  
        +
        + +
      • <ol data=expr] /> +
        + Like ul, but for ordered lists.

        + +
      • <dl data=expr] /> +
        + Convenience tag for the common use of dl; equivalent to +
        +
        +  <dl>
        +  \[[ for term, desc in data:{ \]]
        +    <dt>\[[= term \]]</dt>
        +    <dd>\[[= desc \]]</dd>
        +  \[[ } \]]
        +  </dl>
        +  
        +
        + +
      • <table data=expr] /> +
        + Convenience tag for the common use of table; equivalent to +
        +
        +  <table>
        +  \[[ for row in data:{ \]]
        +    <tr>
        +    \[[ for cell in row:{ \]]
        +      <td>\[[= cell \]]</td>
        +    \[[ } \]]
        +    </tr>
        +  \[[ } \]]
        +  </table>
        +  
        +
        + +
      +[[.compact]] + +[[toc.n('Form', 'tag_form')]] + +The form tag library is aliased as "spy" by default. +

      +This library simplifies the generation and handling of forms by +automating away repetitive tasks. Let's take a look at a simple example: + +[[includeCode('examples/formintro.spy')]] + +

      This demonstrates several properties of Spyce form tags: +

        +
      • Most tags take an optional label parameter; this is turned into an HTML label tag + associated with the form element itself. +
      • If you View Source in your browser while running this sample, you can + see that Spyce generates an id with the same value as the name parameter. + (You can override this by explicitly specifying a different id parameter, + if you need.) +
      • You can pass arbitrary parameters (such as the class parameter for <f:form>) + to a Spyce form tag; parameters that do not have special meaning to Spyce + will be passed through to the HTML output. +
      • Try changing the form values and submitting. By default, Spyce automatically + remembers the user input for you, unless you give a tag a value + parameter (or selected for collection elements), which has highest precedence. + Note the different behavior of text1 and text2 in this example. +
      • Spyce provides some higher-level tags such as checkboxlist that result in multiple + elements at the HTML level. For these tags, a "data" parameter is expected, + which is always interpreted as a Python expression. + Any iterable may be used as data, including generators and generator expressions + for those with recent Python versions. Typically data would come from the + database; here we're just using a literal list. +
      + +Handlers + +

      +Active Handlers allow you to "attach" python functions to form submissions. +They are described in the +Active Handlers manual page. + +

      +Reference + +

      +First, some general rules: + +

      The text displayed by a text-centric tag can come from one of three +places. In order of decreasing priority, these are +

        +
      • the value parameter +
      • the value submitted by the user is used +
      • the default parameter +
      +If none of these are found, the input will be empty. + +

      For determining whether option, radio, and checkbox tags are checked or selected, + a similar process is followed, with + selected and checked parameters as the highest-priority source. + The same parameters are used for select, radiolist, and checkboxlist tags; + the only difference is for the collection tags, you can also specify + multiple values in a Python list (or other iterable) in either the + selected/checked or default parameters. + +

      All tags except form and submit can be given names that tell Spyce how to + treat their submitted values when passing to an Active Handler function. + Adding ":int", ":float", ":bool", or ":list" is allowed. The first three + tell Spyce what kind of Python value to convert the submission to; ":list" + may be combined with these, or used separately, and tells Spyce to pass a + list of all values submitted instead of a single one. + (An example is given in the Active Handlers page.) + +Finally, here is the list of tags: + +

        + +
      • <form + [method=exprstring] [action=exprstring] ...> </form> +
        Begin a new form. The method parameter defaults + to 'POST'. The action parameter defaults to the current page. +
      • + +

      • + <submit + [handler=exprstring] [value=exprstring] ... /> + +
        Create a submit button. The value parameter is + emitted as the button text. If handler is given, Spyce will call the function(s) + it represents at the beginning of the page load after this button is clicked. + (Multiple function names may be separated with commas.) +

        + If the handler is in a different [python] module, Spyce will automatically import it + before use. +

        + A handler may take zero or more arguments. + For the first non-self argument (if present), Spyce always passes a + moduleFinder corresponding to the current + spyceWrapper object; it is customary to call this argument "api." + moduleFinder provides __getitem__ access to loaded modules; thus, + "api.request" would be the current request module. If a requested module + is not found, it is loaded. +

        + (You can also directly access the wrapper with api._wrapper, providing + access to anything module authors have, + but you will rarely if ever need to do this.) +

        + For other handler function parameters, Spyce will pass the values for the + corresponding form input, or None if nothing was found in the GET or POST + variables. +

        + See also the Active Handlers + language section for a higher-level overview. +

        + Limitation: currently, Active Handlers require resubmitting to the same spyce page; + of course, the handler method may perform an internal or external + redirect. + + +

      • + +

      • <hidden + name=exprstring [value=exprstring] [default=exprstring] .../> +
        Create a hidden form field. The name parameter is evaluated and + emitted. +
      • + +

      • <text + name=exprstring [value=exprstring] [default=exprstring]  .../> +
        Create a form text field. The name parameter is evaluated and + emitted. +
      • + +

      • <date + name=exprstring [value=exprstring] [default=exprstring] [size=exprstring] [format=exprstring] .../> +
        Create a form text field with a javascript date picker. Format defaults to MM/DD/YYYY. Maxlength is always len(format); + this is also the default size, but size may be overridden for aesthetics. + +
      • <password + name=exprstring [value=exprstring] [default=exprstring] [size=exprstring] [maxlength=exprstring] .../> +
        Create a form password field. Parameters are the same as for text + fields, explained above.
      • + +

      • <textarea + name=exprstring [value=exprstring] [rows=exprstring] [cols=exprstring] ...>default</textarea> +
        Create a form textarea field. The name parameter is evaluated and + emitted. The value optional parameter is evaluated. A default + may be provided in the body of the tag. The value emitted is, in order of + decreasing priority: local tag value, value in submitted + request dictionary, local tag default. We search this list + for the first non-None value. The rows and cols optional + parameters are evaluated and emitted.
      • + +

      • <radio + name=exprstring value=exprstring [checked] [default] .../> +
        Create a form radio-box. The name and value parameters are + evaluated and emitted. A checked and default flags affect + whether this box is checked. The box is checked based on the following + values, in decreasing order of priority: tag value, + value in submitted request dictionary, tag default. + We search this list for the first non-None value.
      • + +

      • <checkbox + name=exprstring value=exprstring [checked] [default] .../> +
        Create a form check-box. Parameters are the same as for radio + fields, explained above.
      • + +

      • <select + name=exprstring [selected=exprstring] [default=exprstring] [data=expr] ...>...</select> +
        Create a form select block. The name parameter is + evaluated and emitted. The optional data should be an iterable of + (description, value) pairs. +
      • + +

      • <option + [text=exprstring] [value=exprstring] [selected] [default] .../> +
        <option + [value=exprstring] [selected] [default] ...>text</option> +
        Create a form selection option. This tag must be nested within a + select tag. The text optional parameter is evaluated and + emitted in the body of the tag. It can also be provided in the body of the + tag, as you might be used to seeing in HTML. +
      • + +

      • <radiolist + name=exprstring data=expr [checked=exprstring] [default=exprstring] ...>...</select> +
        Create multiple radio buttons from data, which should be an iterable of + (description, value) pairs. +
      • + +

      • <checkboxlist + name=exprstring data=expr [checked=exprstring] [default=exprstring] ...>...</select> +
        Create multiple checkboxes from data, which should be an iterable of + (description, value) pairs. +
      • + + +

      + +Here is an example of all of these tags in use: + +[[includeCode('examples/formtag.spy')]] + +[[toc.n('Active Handlers', 'tag_handlers')]] + +Active Handlers allow you to "attach" python functions to Spyce form submissions. +Instead of old-fashioned inspection of the request environment, Spyce will +pull the parameters required by your function's signature out for you. +Let's have a look at an example. Here, we define a calculate +function and assign it to be the handler for our submit input: + +[[ includeCode('examples/handlerintro.spy') ]]

      + +

        +
      • +Handlers may be inline, as the calculate handler is here, or in a +separate Python module. When using a handler from a Python module, Spyce +will automatically import the module when needed. (Handler parameters are +strings, not Python references.) The todo demo +demonstrates using handlers this way. +
      • You can give your form inputs a data type; Spyce will perform the +conversion automatically before passing parameters to your handler function. +
      + +

      +Active Handlers also make it easy to incorporate user-friendly error messages +into your forms simply by raising a HandlerError: +[[ includeCode('examples/handlervalidate.spy') ]] + +

      +You can show multiple errors at once with a CompoundHandlerError: +[[ includeCode('examples/handlervalidate2.spy') ]] + +

      +All Spyce modules are available via the api handler parameter (which +should always be the first parameter (after self in a class). Here +is an example that uses the db module: +[[ includeCode('examples/db.spy') ]] + +

      +Handlers in Active Tags allow you to +create reusable components, as in the the chat demo. + +

      +(Since Spyce captures stdout, you can use print to debug handlers.) + +[[toc.n('Writing Tag Libraries', 'tag_new2')]] +Creating your own active tags is quite easy and this section explains how. You +may want to create your own active tags for a number of reasons. More advanced +uses of tags include database querying, separation of business logic, or +component rendering. On the other hand, you might consider creating simpler +task-specific tag libraries. For example, if you do not wish to rely on +style-sheets you could easily define your own custom tags to perform the +formatting in a consistent manner at the server. Another convenient use for +tags is to automatically fill forms with session data. These are only a few of +the uses for tags. As you will see, writing a Spyce active tag is far +simpler than writing a JSP tag. +

      +(The chatbox demo gives an example of an active tag.) +

      +Tag libraries must be placed in a separate file from request-handling Spyce pages. +The following +directives +apply specifically to tag library definition: + +

        +
      • + \[[.tagcollection \]] : +
        Indicates that the current file will be an Active Tag library. Must + be at the start of the file. +
      • + \[[.begin name=string [buffers=True|False] [singleton=True|False] [kwattrs=string] \]] : +
        + Begin defining a tag named name. Optional attributes: +
          +
        • buffers: if true, Spyce will evaluate the code between the begin and end tags + and pass it to the tag as the variable _content. For instance, the following + simplistic tag makes its contents bold: + [[includeCode('examples/tagbold.spi', run=0)]] +
        • singleton: if true, Spyce will not allow paired use of the tag (<tag></tag>) + and only allow singleton use (<tag />). If false, the reverse is true. +
        • kwattrs: the name of the dict in which to place attributes not specified with \[[.attr\]] + directives. If not given, Spyce will raise an error if unexpected attributes are seen. +
        +
      • + \[[.attr name=string [default=string] \]] : +
        Specify that the current tag being defined expects an attribute named name. + If a default string is given, the attribute is optional. (Dynamic attributes + may be accepted with the kwattrs option in the begin directive.) (If the default + string is prefixed with '=', it will be evaluated as python code at runtime.) +
      • + \[[.export var=string [as=string] \]] : +
        Specifies that the variable from the tag context named var will be + exported back to the calling page. The optional as attribute may be used + to give the variable a different name in the calling context. +
      • + \[[.end \]] : +
        Ends definition of the current tag. +
      + +Active tags may specify handlers +as in normal Spyce code; this may be done inline +with class chunks, or as a reference +to a separate .py module. This allows building reusable components easily! +Again, the chatbox demo demonstrates this. +

      +(Be careful if you take the class chunk approach with handlers, since all class chunks that get used +in a given page are pulled into the same namespace. By convention, tag handlers +defined in reusable tags are prefixed with the tag name, e.g., chatbox_addline.) +

      +Active tags should not contain f:form active tags; this needs to be done +by the .spy page for the Spyce compiler to link up Active Handlers correctly. +

      +One limitation of using the Active Tag directives described here is that tags +within a single collection may not call each other. Usually, you can work +around this by defining common code inside a .py module and importing that. +If this is not an option, you can create Active Tags manually. This is +described in the next section. + +[[toc.n('Writing Tag Libraries the hard way', 'tag_new')]] + +You can still write tag libraries manually if the tag declaration language doesn't +give you the tools you need. (About the only functionality it doesn't currently +expose is the looping ability used in spy:for.) You may wish to approximate +what you want with a new-style tag library +and examine the compiler's output (spyceCmd.py -c). +

      + +We begin with a basic example:

      + +[[includeCode('examples/myTaglib.py', run=0)]]

      + +Saving this code in myTaglib.py, in the same +directory as your script or anywhere else in the search path, one could then +use the foo active tag (defined above), as follows:

      + +[[includeCode('examples/tag.spy')]]

      + +An active tag library can be any Python class that derives from +spyceTag.spyceTagLibrary. The interesting aspects of this class +definition to implementors are:

      + +

        + +
      • tags:
        This field is usually all that requires redefinition. + It should be a list of the classes (as opposed to instances) of the + active tags.
      • + +

      • start():
        This methd is invoked by the engine upon loading + the library. The inherited method is a noop.
      • + +

      • finish():
        This method is invoked by the engine upon + unloading the library after a request. The inherited method is a noop.
      • +

        + +

      + +Each active tag can be any Python class that derives from +spyceTag.spyceTag. The interesting aspects of the class definition for +tag implementors are:

      + +

        + +
      • name:
        This field MUST be overidden to indicate the name of + the tag that this class defines.
      • + +

      • buffer:
        This flag indicates whether the processing of the + body of the tag should be performed with the current output stream + (unbuffered) or with a new, buffered output stream. Buffering is useful for + tags that may want to transform, or otherwise use, the output of processing + their own bodies, before it is sent to the client. The inherited default is + false.
      • + +

      • conditional:
        This flag indicates whether this tag may + conditionally control the execution of its body. If true, then the begin() + method of the tag must return true to process the tag body, or false to skip + it. If the flag is set to false, then return value of the begin() method is + ignored, and the body executed (unless an exception is triggered). Some + tags, such as the core:if tag, require this + functionality, and will set the flag true. Many other kinds of tags do not, + thus saving a level of indentation (which is unfortunately limited in Python + -- hence the need for this switch). The inherited default is false.
      • +

        + +

      • loops:
        This flag indicates whether this tag may want to loop + over the execution of its body. If true, then the body() method of the tag + must return true to repeat the body processing, or false to move on to the + end() of the tag. If the flag is set to false, then the return value of the + body() method is ignored, and the body is not looped. Some tags, such as the + core:for tag, require this functionality, and + will set the flag true. Many other kinds of tags do not, thus saving a level + of indentation. The inherited default is false.
      • + +

      • catches:
        This flag indicates whether this tag may want to + catch any exceptions that occur during the execution of its body. If true, + then the catch() method of the tag will be invoked on exception. If the flag + is false, the exception will continue to propagate beyond this point. Some + tags, such as the core:catch, require this + functionality, and will set the flag true. Many other kinds of tags do not, + thus saving a level of indentation. The inherited default is false.
      • +

        + +

      • mustend:
        This flag indicates whether this tag wants the + end() method to get called, if the begin() completes successfully, no + matter what. In other words, the call to end() is placed in the finally + clause of + the try-finally block which begins just after the begin(). This is useful for + tag cleanup. However, many tags do not perform anything in the + end() of their tag, or perhaps perform operations that are not important in + the case of an exception. Such tags do not require this functionaliy, thus + saving a level of indentation. The inherited default is false.
      • + +

      • syntax():
        This method is invoked at compile time to perform + any additional tag-specific syntax checks. The inherited method returns + None, which means that there are no syntax errors. If a syntax error is + detected, this function should return a string with a helpful message about + the problem. Alternatively, one could raise an + spyceTagSyntaxException.
      • + +

      • begin( ... ):
        This method is invoked when the corresponding + start tag is encountered in the document. All the attributes of the tag are + passed in by name. This method may return a boolean flag. If + conditional is set to true (see above), then this flag indicates + whether the body of the tag should be processed (true), or skipped (false). + The inherited method performs no operation, except to return true.
      • + +

      • body( contents ):
        This method is invoked when the body of + the tag has completed processing. It will be called also for a + singleton, which we assume simply has an empty body. However, it will not be + called if the begin() method has chosen to skip body processing entirely. If + the tag sets buffer to true for capturing the body processing output + (see above), then the string output of the body processing has been captured + and stored in contents. It is the responsibility of this method to + emit something, if necessary. If the tag does not buffer then + contents will be None, and any output has already been written to the + enclosing scope. If the loops flag is set to true, then this method + is expected to return a boolean flag. If the flag is true, then the body + will be processed again, followed by another invocation of this method. And + again, and again, until false is received. The inherited tag method performs + nothing and returns false.
      • + +

      • end():
        This method is invoked when the corresponding end tag + is encountered. For a singleton tag, we assume that the end tag immediately + follows the begin, and still invoke this method. If the mustend flag + has been set to true, then the runtime engine semantics ensure that if the + begin method terminates successfully, this method will get called, + even in the case of an exception during body processing. The inherited + method is a noop.
      • + +

      • catch( ex ):
        If the catches flag has been set to + true, then if any exception occurs in the begin(), body() or end() methods + or from within the body processing, this method will be invoked. The + parameter ex holds the value that was thrown. The inherited method + simply re-raises the exception.
      • + +

      • getPrefix():
        Return the prefix under which this tag library + was installed.
      • + +

      • getAttributes():
        Return a dictionary of tag attributes. +
      • + +

      • getPaired():
        Return true if this is a paired (open and + close) tag, or false if it is a singleton.
      • + +

      • getParent( [name] ):
        Return the object of the direct parent + tag, or None if this is the root active tag. Plain (inactive) tags do not + have objects associated with them in this hierarchy. If the optional + name parameter is provided, then we search up the tree for an active + tag of the same library and with the given name. If such a tag can not be + found, then return None.
      • + +

      • getOut():
        Return the (possibly buffered) output stream that + this tag should write to.
      • + +

      • getBuffered():
        Returns true if the tag output stream is a + local buffer, or false if the output is connected to the enlosing scope. +
      • + +

      + +Note that Spyce goes to a lot of effort to avoid unnecessary indentation in +the code it generates for Active Tags. +A limitation of the Python runtime is that the level of indentation +within any method is limited by a +compile-time constant. You can change it, of course, but in most +Python distributions as of this writing (Feb 2005), this is currently set to 100. +See for instance +this thread +from python-bugs. +

      +For convenience, tag implementors may wish to derive their implementations +from spyceTagPlus, which provides some useful additional methods: + +

        + +
      • getModule( name ):
        Return a reference to a module + from the page context. The module is loaded, if necessary.
      • + +

      • syntaxExist( [must]* ):
        Removed in 2.0; + Spyce now checks the signature of the begin method; arguments + with no default are enforced as required automatically.

        + +

      • syntaxNonEmpty( [names]* ):
        Ensure that if the attributes + listed in names exist, then each of them does not contain an empty + string value. Otherwise, a spyceTagSyntaxException is thrown. Note that the + actual existence of a tag is checked by syntaxExists(), and that this method + only checks that a tag is non-empty. Specifically, there is no exception + raised from this method, if the attribute does not exist.
      • + +

      • syntaxValidSet( name, validSet ):
        Ensure that the value of + the attribute name, if it exists, is one of the values in the set + validSet. Otherwise, a spyceTagSyntaxException is raised.
      • + +

      • syntaxPairOnly():
        Ensure that this tag is a paired tag. + Otherwise, a spyceTagSyntaxException is thrown.
      • + +

      • syntaxSingleOnly():
        Ensure that this tag is a singleton tag. + Otherwise, a spyceTagSyntaxException is thrown.
      • + +

      + +Despite the length of this description, most tags are trivial to write, as +shown in the initial example. The easiest way to start is by having at a look +at various implemented tag libraries, such as tags/core.py. The more curious reader is welcome to look +at the tag library internals in spyceTag.py and +modules/taglib.py. The tag semantics are ensured by +the Spyce compiler (see spyceCompile.py), though it +is likely easier simply to look at the generated Python code using the "spyce -c" command-line facility.

      + +[[toc.e()]] + +[[toc.n('Installation', 'conf')]] + +Spyce can be installed and used in many configurations. Hopefully, one of the +ones below will suit your needs. If not, feel free to email the lists asking +for assistance in using a different setup. If you have successfully set up +Spyce by some other method, please also email us the details. +Finally, if you had troubles following these instructions, +please send suggestions on how to improve them.

      + +[[toc.b('Overview', 'conf_overview')]] + +Spyce (the core engine and all the standard modules) currently requires +Python version [[=verchk.REQUIRED]] or greater. Spyce uses no version-specific Apache features.

      + +Spyce supports operation through +its own webserver, FastCGI, mod_python, Apache proxy, CGI, and the command-line. +Some of these require configuration-specific tweaks. These are kept to an +absolute minimum, however; where possible, the configuration of the +Spyce engine is performed through the +Spyce configuration module. + +The supported adapters are: + +

        +
      • Web server: The preferred alternative is to serve Spyce files via + its built-in webserver. For production use, it is recommended to do this + as a proxy behind another server such as Apache that handles static content, + url rewriting, ssl, etc. + This is the best option if it is available to you, since you only + have a single process (with concurrency provided by multithreading) which + makes resource pooling (data, database handles, etc.) an easy way to help + your site scale. It also avoids the waste of loading your code into + multiple Python interpreters. +
      • + +

      • FastCGI: + This is a CGI-like interface that + is relatively fast, because it does not incur the large process startup + overhead on each request.
      • + +

      • mod_python: This is another relatively performant + way to serve up Spyce. + If this is that is your chosen Apache integration route, + make sure you can first get mod_python running on your system, before + adding Spyce to the mix. +
      • + +

      • CGI: Failing these alternatives, you can always process requests + via regular CGI, but this alternative is the slowest option and is intended + primarily for those who do not have much control over their web environments. +
      • + +

      • Command line: Spyce is useful as a command-line tool + for pre-processing Spyce pages and creating static HTML files. + This documentation, for instance, is produced this way. +
      • + +

      • Others: Spyce abstracts its operating environment using a thin + abstraction layer. Spyce users have written small Spyce adapters for the + Xitami webserver and also to integrate with the Coil framework. Writing your + own adapter, should the need arise, is therefore a realistic possibility. +
      • + +

      + +The following sections assume that you have already downloaded and uncompressed Spyce. + +[[toc.n('Web Server', 'conf_proxy')]] + +The preferred method of running Spyce it to run it as a web-server. +

      +The Spyce web server configuration is defined in the "webserver options" section +of the Spyce configuration module. + +

        +
      • Start the Spyce web server. The command-line syntax for starting the + server is:
        spyceCmd.py -l +
        + Spyce will now be running on port 8000. You can change the port in your + spyceconf module. + +
      • + +
      • If you are going to proxy Spyce behind Apache (this is done automatically if you installed via RPM): +
          +
        1. Copy the proxy section ("Spyce via proxy") from spyceApache.conf into httpd.conf. +
        2. Make sure mod_proxy is installed and enabled. Check httpd.conf for a mod_proxy + section; look for instructions like "Uncomment the following lines to enable the proxy server." +
        3. Restart Apache
        4. +
        +
      • +
      + +[[toc.n('CGI/FastCGI installation', 'conf_source')]] + +
        + +
      • Ensure that you have Apache and FastCGI installed and functioning.
      • + +
      • Create a symlink to the command-line executable:
        ln -sf /usr/share/spyce/run_spyceCmd.py /usr/bin/spyce +
        or wherever you have chosen to install it.
      • + +
      • Copy the "Spyce via cgi or fcgi" lines from spyceApache.conf to your + /etc/httpd/conf/httpd.conf + file, and replace the XXX with the appropriate + path to your spyce installation. + +
      • Restart Apache + +
      + +Alternative CGI configuration: + +The alternative CGI configuration directs the webserver to execute the .spy +file itself, not the Spyce engine. This is appropriate if you do not control +Apache on the server you are installing to, e.g., low-end hosting +plans. Each .spy file should have execute +permissions for the web server, and the first line should be: +

      + +[[.compact mode=off]] + +

      +  #!/usr/bin/python /home/username/spyce/run_spyceCGI.py
      +
      + +[[.compact]] + +(Adjust to the correct Spyce installation path as necessary.) +Then add the following line to the httpd.conf, or to +the .htaccess file in the same directory. + +[[.compact mode=off]] + +
      +  AddHandler cgi-script spy
      +
      +
      +[[.compact]] + +Finally, ensure that the directory itself has the ExecCGI option enabled, +and set cgi_allow_only_redirect to +False in your Spyce configuration module. + + +For more details, please refer to the Apache documentation, specifically +ExecCGI option, +Directory, +Location, +AllowOverride and +Apache CGI documentation, +for more information on how to get a standard CGI setup working.

      + +[[toc.n('Mod_Python', 'conf_modpython')]] + +mod_python is primarily recommended if you cannot get +FastCGI to work. (Some users have reported problems with FastCGI on Windows.) +

      +Before you try to install Spyce, first get mod_python to work! +You may have to compile from sources, and possibly do the same for Python. (See +the documentation at: mod_python.) + +

      +To use Spyce via mod_python: + +

        + +
      • Copy the "Spyce via mod_python" lines from spyceApache.conf to your + /etc/httpd/conf/httpd.conf + file, and replace the XXX with the appropriate + path to your spyce installation. + +
      • Restart Apache + +
      + + +[[toc.n('Notes for Windows', 'conf_windows')]] + +Besides your preferred installation option above, remember to +add the following line to Apache's httpd.conf:

      + +[[.compact mode=off]] + +

      +  ScriptInterpreterSource registry
      +
      + +[[.compact]]

      + +This assumes that Python has registered itself with the Windows registry to +run .py files. Otherwise, you can also omit this line, but make sure that the +first line of the run_spyceCGI.py file points to a +valid Python executable, as in: + +[[.compact mode=off]] + +

      +  #! c:/python24/python.exe
      +
      + +[[.compact]]

      + +If you are running using IIS on Windows, you can take a look at +how to + configure IIS or PWS for Python/CGI scripts.

      + +The basics for getting IIS to work with Spyce are: + +

        + +
      • Start the IIS administration console. You get to it from the Control + Panel. Click on Administrative Tools, and then Internet + Services Manager.
      • + +
      • Drill down to your Default Web Site. Right click and select + Properties.
      • + +
      • Select the Home Directory tab, and click on the + Configuration... button near the bottom right.
      • + +
      • Add an application mapping. On the executable line you should + type the equivalent of:
        "c:\program files\python22\python.exe" "c:\program files\spyce\spyceCGI.py". +
        Set the extension to .spy, or + whatever you like.
        Limit the Verbs to GET,POST.
        Select the Script engine and + Check that file exists check-boxes.
      • + +
      • Click OK twice. Make sure to propagate these properties to all + sub-nodes. That is, click Select All and then OK once more. +
      • + +
      • You should now be able to browse .spy files within your website. Note, + that this is a very slow mechanism, since it utilizes CGI and restarts the + Spyce engine on each request.
      • + +
      • Using the Spyce proxy web server or installing FastCGI are much + advised for the vast majority of environments.
      • + +
      + +[[toc.n('Notes on RPM installation', 'conf_rpm')]] + +
        +
      • The RPM installation installs Spyce to /usr/share/spyce and +creates a /usr/bin/spyce as a symlink to spyceCmd.py. It also +configures Apache to forward .spy requests to the Spyce server on port 8000. +You're still responsible for starting the spyce server as described in the +Web Server configuration section. + +
      • If you upgrading Spyce, it is recommended to uninstall the previous +version using rpm -e spyce, and then install a fresh copy, as opposed +to using the rpm -U option. + +
      • Historical note to Redhat users: RH releases prior to 8.0 still used Python version +1.5, as many standard scripts depended on it. If you want to run Spyce with +some of the newer Python2 rpms, you will need to change top line of the run_spyceCmd.py, run_spyceCGI.py, run_spyceModpy.py and verchk.py +scripts, or reconfigure your path so that the default python is the version +that you want.

        + +[[toc.n('Notes on Apache integration', 'conf_apache')]] + +

          +
        • If you installed via RPM, Apache is configured to proxy .spy requests to the Spyce webserver. If you are running without the spyce standalone server, comment out those lines in httpd.conf (starting with "Spyce via proxy"). +
        • + +
        • To avoid confusion, you may wish to change the "root" option in your +spyceconf module +to be the same as Apache's DocumentRoot. + +
        • You may wish to copy the "Documentation alias" section from spyceApache.conf. + This will allow you to access the spyce documentation and examples + from http://localhost/spyce/, so you can test your install by browsing to + http://localhost/spyce/examples/hello.spy. + +
        + +[[toc.n('Starting your first project', 'conf_next')]] + +Spyce provides a simple method to create a new project. +This creates directories for your Spyce project, creates an initial +Spyce config file, +and sets up other resources. +

        +For example, running +

        python spyceProject.py /var/firstproject
        + +results in the following directory structure +[[.compact mode=off]] +
        +$ tree /var/firstproject/
        +    /var/firstproject/
        +    |-- config.py
        +    |-- lib
        +    |-- login_tokens
        +    `-- www
        +        |-- _util
        +        |   |-- form_calendar.gif
        +        |   `-- form_calendar.js
        +        |-- index.spy
        +        |-- parent.spi
        +        `-- style.css
        +
        +[[.compact]] + +Now you can simply run spyceCmd.py -l --conf /var/firstproject/config.py +and browse to http://localhost:8000/index.spy to verify that it worked. + +

        +Notes: +

          +
        • The login_tokens directory is used by the spy:login tags; do not remove it. +
        • The lib/ directory (also initially empty) +is placed on the Python sys.path, meaning you can import any Python module in this +directory from any .spy file. It is good practice to put re-usable code +into .py files in this directory. +
        + +[[toc.e()]] + +[[toc.n('Programmatic Interface', 'runtime_prog')]] + +It is also possible to embed Spyce into another program. All you need is to +run or embed + a Python interpreter. Although other entry points into the engine code +are possible, the most convenient entry points are in spyce.py: + +
          + +
        • spyceFileHandler( request, response, filename, [sig], [args], [kwargs], [config] )
          + +
        • + +

        • spyceStringHandler( request, response, code, [sig], [args], [kwargs], [config] )
          + +
        • + +

        + +Either of these functions will execute some Spyce code within the context of some request object and send the output to the corresponding response object. spyceFileHandler gets the Spyce code to be executed from a file, while spyceStringHandler is passed Spyce code in a string. + +[[toc.b('Basic usage', 'runtime_prog_basicUsage')]] + +For basic usage, the following arguments need to be understood: + +

          +
        • request
          + is an object derived from spyce.spyceRequest, denoting the current HTTP request. +
        • + +

        • response
          + is an object derived from spyce.spyceResponse, denoting the HTTP response resulting from the invocation. +
        • + +

        • filename
          + is the name of a file which contains spyce source code (it will be read, compiled, cached and executed when spyceFileHandler is called). +
        • + +

        • code
          + is a string which contains spyce source code (it will be executed when spyceStringHandler is called).
          + TODO: how is caching handled in this case? +
        • + +

        • config
          + is an object which contains the configuration to be used. The normal usage is to have a configuration file which can be imported as a normal python module. The imported module then passed as conf. +
        • + +

        + +[[toc.e()]] + +[[toc.b('Passing parameters', 'runtime_prog_passingParameters')]] + +One may wish to also pass a parameter from the calling code into the name space used inside the Spyce code so that it may be accessed from Python Statements, Chunks, Expressions, etcetera. Something analogous to Python's own execfile built-in function where you can pass a locals and globals dictionary.

        + +Spyce code to be invoked is dealt with similar to a function body, and hence one can send parameters to that "function" upon invocation. The arguments sig, args and kwargs are used to control such parameter passing. With sig, a signature for this external code is specified, with args and kwargs values for the actual arguments are passed. Thus, sig controls what can go into args and kwargs: + +

          +
        • sig
          + is a string containing a function signature in usual python syntax, without surrounding brackets.
          + For example: 'x, y, a=None, b={}'. +
        • + +

        • args
          + is a list of values, each value corresponding to a positional argument specified in sig, in order. +
        • + +

        • kwargs
          + is a dictionary of name to value mappings, its keys should be in the list of keyword argument names specified in sig, its values provide actual values to be passed. +
        • + +

        + +[[toc.e()]] + +[[toc.b('Customized Request/Response classes', 'runtime_prog_requestAndResponse')]] + + explanation forthcoming; read the code for now, or send an email + +[[toc.e()]] + +[[toc.b('Example', 'runtime_prog_example')]] + +Here's an example that demonstrates such programmatic usage:

        + +[[includeCode('examples/programmaticUsage.py', run=0)]] + +[[toc.e()]] + +[[toc.e()]] + +[[toc.n('Addenda', 'add')]] + +List of appendices: + +

          + +
        • Performance - Some throughput + micro-benchmarks
        • + +

        • History - The brief history + of Spyce
        • + +

        + +[[toc.b('Performance', 'add_perf')]] + +Although flexibility usually outweighs raw performance in the choice of +technology, it is nice to know that the technology that you have chosen is not +a resource hog, and can scale to large production sites. The current Spyce +implementation is comparable to its cousin technologies: JSP, PHP and ASP. We +ran a micro-benchmark using hello.spy and equivalents. All benchmark +files are available in the misc/benchmark +directory.

        + +[[includeCode('examples/hello.spy')]] +

        + +Spyce was measured under CGI, FCGI, mod_python and proxy configurations. For +calibration the static HTML, CGI-C, CGI-Python and FCGI-Python tests were +performed. In the case of CGI-C, the request is handled by a compiled C +program with the appropriate printf statements. In the case of CGI-Python, we +have an executable Python script with the appropriate print statements. +FCGI-Python is a similar script that is FCGI enabled. ASP was measured on a +different machine, only to satisfy curiosity; those results are omitted.

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        ConfigurationHello world!
        Spyce-modpython250
        modpython publisher300
        Spyce-proxy200
        JSP100
        PHP450
        Spyce-FCGI100
        Python-FCGI140
        Spyce-CGI8
        Python-CGI25
        C-CGI180
        Static HTML1500
        +

        + +The throughput results (shown above in requests per second) were measured on a +Intel PIII 700MHz, with 128 MB of RAM and a 512 KB cache +running RedHat Linux 7.2 (2.4.7-10 kernel), Apache 1.3.22 and +Python 2.2 using loopback (http://localhost/...) requests. Since each of +the script languages requires an initial compilation phase (of which JSP seems +the longest), the server was warmed up with 100 requests before executing +1000 measured requests with a concurrency level of 3, using the +ab (Apache benchmark) tool. Figures are rounded to the nearest +25 requests/second.

        + +Conclusion:All spyce configuration options except CGI +can handle large +websites, as the Spyce engine and cache persist between requests. The CGI +version takes a hit in recompiling Spyce files on every request. (This may be +alleviated using a disk-based Spyce cache (as opposed to the current +memory-based implementation).) +

        + +[[toc.n('History', 'add_history')]] + +The initial idea for a Python-based HTML scripting language came in May 1999, +a few months after I (Rimon) had first learned of Python, while working with JSP on +some website. The idea was pretty basic and I felt that someone was bound to +implement it sooner or later, so I waited. But, nobody stepped up to the task, +and the idea remained little more than a design in my head for two and a half +years. In early 2002, after having successfully used Python extensively for +various other tasks and gaining experience with the language, I began to +revisit my thoughts on a Python-based HTML scripting language, and by late +May 2002 the beta of version 1.0 was released.

        + +Version 1.0 had support for standard features like get and post, +cookies, session management, etc. Development was still on-going, but Spyce +was mature and being used on live systems. Support of various features was +enhanced for about a week or two, and then a new design idea popped into my +head. Version 1.1 was the first modular release of Spyce. Lots of +prior functionality was shipped out of the core engine and into standard +modules. Many, many new modules and features were added. Spyce popularity rose +to the top percentile of SourceForge projects and the user base grew. +Version 1.2 represented a greatly matured release of Spyce. Spyce +got a totally revamped website and documentation, and development continued. +Version 1.3 introduced active tags. More performance work, more +modules, etc. +

        +In February 2005 Jonathan Ellis was hired by SilentWhistle to work on their +web application, written in Spyce. He began by overhauling the Active Tag code +and was soon deep in the guts of Spyce. Jonathan added Active Handlers, +parent templates, and the tag compiler on the way to Version 2.0. +

        +In July 2006 Version 2.1 introduced the Spyce login tags and +database integration via SQLAlchemy, as well as improvments to parameter +marshalling for Active Handlers. + +

        +For more detail, please refer to the +change log. +As always, user feedback is welcome and appreciated.

        + +[[toc.e()]] + +[[include.spyce('/inc/tail.spi') ]] +[[toc.generate()]] \ No newline at end of file diff --git a/spyce-2.1/www/demo-site/docs/eg.html b/spyce-2.1/www/demo-site/docs/eg.html new file mode 100755 index 0000000000000000000000000000000000000000..8c760373c6e333488c300ff22006203ac524c192 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/eg.html @@ -0,0 +1,335 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + + + + +The following is a list of examples extracted from the Spyce documentation. +You can click on the links below to view or run the Spyce source. +For a more detailed explanation, please refer to the associated documentation section. + + + +

        +The Spyce documentation and the entire Spyce website were produced using +Spyce.

        + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/eg.spy b/spyce-2.1/www/demo-site/docs/eg.spy new file mode 100755 index 0000000000000000000000000000000000000000..8026780a7170e2558c2c613c477a6e284904cc16 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/eg.spy @@ -0,0 +1,84 @@ +[[.compact]] +[[.import names="include"]] +[[ include.spyce('/inc/head.spi', {'pagename': 'Examples', 'page': 'eg.html'}) ]] +[[.include file="/inc/static.spi"]] + +The following is a list of examples extracted from the Spyce documentation. +You can click on the links below to view or run the Spyce source. +For a more detailed explanation, please refer to the associated documentation section. + +[[\ + import os + + def getOutputName(file): + import os + return os.path.splitext(file)[0] + '.src.html' + + def makeEgSrc(file, otherfiles, getOutputName=getOutputName, includeCode=includeCode): + output = getOutputName(file) + stdout.push(output) + include.spyce('/inc/head.spi', {'pagename': 'Examples', 'page': 'eg.html'}) + includeCode('examples/'+file) + if otherfiles: + print '

        Supplemental files:
        ' + for f in otherfiles: + includeCode('examples/'+f, run=0) + print '

        Back to List of Examples' + include.spyce('/inc/tail.spi') + stdout.pop() + + def emitLink(f, run): + import os + rval = 'view' % getOutputName(f) + if run: + if egprefix: + run_href = egprefix + os.path.split(f)[-1] + else: + run_href = '/docs/examples' + '/'.join(os.path.split(f)) + rval += ', run' % run_href + rval += ' - %s' % f + makeEgSrc(f, ()) + return rval +]] + +[[\ +toc_paths = [] +examples_by_path = {} +last_toc = (None, None) +lines = open('_eginfo.txt').readlines() # created by doc.spy +for line in lines: + tocpath, toctag, filename, run = line.strip().split(':') + run = bool(int(run)) + if tocpath != last_toc[1]: + if last_toc[1]: + toc_paths.append(last_toc) + examples_by_path[last_toc] = L + L = [] + last_toc = (toctag, tocpath) + if filename == 'root': + # 2nd pass of toc code + break + # emitLink takes care of the path, which may be munged + filename = filename[len('examples/'):] + L.append((filename, run)) +]] + +

          +[[ for tocinfo in toc_paths:{ ]] + [[ toctag, tocpath = tocinfo ]] +
        • [[= tocpath ]] +
            + [[ for filename, run in examples_by_path[tocinfo]:{ ]] +
          • [[= emitLink(filename, run) ]]
          • + [[ } ]] +
          +
        • +[[ } ]] +
        + +

        + +The Spyce documentation and the entire Spyce website were produced using +Spyce.

        + +[[include.spyce('/inc/tail.spi') ]] diff --git a/spyce-2.1/www/demo-site/docs/error.src.html b/spyce-2.1/www/demo-site/docs/error.src.html new file mode 100755 index 0000000000000000000000000000000000000000..3cf8b5a6e0600e2657f5defd04915b6caa1fe64e --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/error.src.html @@ -0,0 +1,227 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/error.spi +
        + +
        <h1>Oops</h1>
        +An error occurred while processing your request. 
        +We have logged this for our webmasters, and they 
        +will fix it shortly. We apologize for the inconvenience.
        +In the meantime, please use the parts of our site that 
        +actually do work... <a href="somewhere">somewhere</a>.
        +[[\
        +  # could redirect the user immediately
        +  #response.getModule('redirect').external('somewhere.spy')
        +
        +  # could send an email
        +  import time
        +  msg = '''
        +time: %s
        +error: %s
        +env: %s
        +other info...
        +''' % (
        +    time.asctime(time.localtime(time.time())), 
        +    error.getString(),
        +    request.env()
        +  )
        +  #function_to_send_email('webmaster@foo.com', msg)
        +
        +  #or perform other generic error handling...
        +]]
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/examples/asp.spy b/spyce-2.1/www/demo-site/docs/examples/asp.spy new file mode 100755 index 0000000000000000000000000000000000000000..2ae10fc3cf6ce6f8959843adb78ade0567dac3ed --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/asp.spy @@ -0,0 +1,6 @@ + + Hello <%print 'world!',%> + <% for i in range(10): { %> + <%=i%> + <% } %> + diff --git a/spyce-2.1/www/demo-site/docs/examples/compress.spy b/spyce-2.1/www/demo-site/docs/examples/compress.spy new file mode 100755 index 0000000000000000000000000000000000000000..83772ce104a8cc955e5236cdd99663d710f12cca --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/compress.spy @@ -0,0 +1,9 @@ +[[.import name=compress args="gzip=1, spaces=1"]] +[[\ + response.write('') + response.write(' Space compression will remove these spaces.
        ') + response.write(' gzip compression will highly compress this:
        ') + for i in range(1000): + response.write(' hello') + response.write('') +]] diff --git a/spyce-2.1/www/demo-site/docs/examples/config.spy b/spyce-2.1/www/demo-site/docs/examples/config.spy new file mode 100755 index 0000000000000000000000000000000000000000..2783511844c10a494041f270d6b5121df088ae7a --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/config.spy @@ -0,0 +1,6 @@ +[[\ + import spyceConfig + home = spyceConfig.SPYCE_HOME +]] + +[[= home ]] diff --git a/spyce-2.1/www/demo-site/docs/examples/cookie.spy b/spyce-2.1/www/demo-site/docs/examples/cookie.spy new file mode 100755 index 0000000000000000000000000000000000000000..5b75645a15f577a70aeb53d298c8e53d6c6b8821 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/cookie.spy @@ -0,0 +1,57 @@ +[[.import name=cookie]] + + Managing cookies is simple. Use the following forms + to create and destroy cookies. Remember to refresh + once, because the cookie will only be transmitted on + the following request.
        + [[-- input forms --]] +


        +
        + + + + + + + + + + + + + + +
        Cookie name:(required)
        value:(required for set)
        expiration: seconds.(optional)
        + + + +
        +
        +
        + [[-- show cookies --]] + Cookies: [[=len(cookie.get().keys())]]
        + + + + + + [[for c in cookie.get().keys(): {]] + + + + + [[ } ]] +
        namevalue
        [[=c]][[=cookie.get(c)]]
        + [[-- set cookies --]] + [[\ + operation = request.post('operation') + if operation: + operation = operation[0] + name = request.post('name')[0] + value = request.post('value')[0] + if operation == 'set' and name and value: + cookie.set(name, value) + if operation == 'delete' and name: + cookie.delete(name) + ]] + diff --git a/spyce-2.1/www/demo-site/docs/examples/db.spy b/spyce-2.1/www/demo-site/docs/examples/db.spy new file mode 100755 index 0000000000000000000000000000000000000000..76be2e9802301f26b4d818ff1ac2bbb1c76bb7fb --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/db.spy @@ -0,0 +1,23 @@ + + +[[! +def list_new(self, api, name): + if api.db.todo_lists.selectfirst_by(name=name): + raise HandlerError('New list', 'a list with that description already exists') + api.db.todo_lists.insert(name=name) + api.db.flush() +]] + +(This is an self-contained example using the same database as the +to-do demo.) + +

        To-do lists

        + +[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]] + + +

        New list

        + +: + + diff --git a/spyce-2.1/www/demo-site/docs/examples/delay.spy b/spyce-2.1/www/demo-site/docs/examples/delay.spy new file mode 100755 index 0000000000000000000000000000000000000000..22760680f03971b600abb38fe2b0844d9485cc8a --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/delay.spy @@ -0,0 +1,9 @@ +[[\ +delay = 30 +import time +response.unbuffer() +for i in range(delay): + #response.write(str(i)) + print i, '

        ' + time.sleep(1) +]] diff --git a/spyce-2.1/www/demo-site/docs/examples/error.spi b/spyce-2.1/www/demo-site/docs/examples/error.spi new file mode 100755 index 0000000000000000000000000000000000000000..51b579f2713f705776c1ca74c720bf806e837da5 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/error.spi @@ -0,0 +1,26 @@ +

        Oops

        +An error occurred while processing your request. +We have logged this for our webmasters, and they +will fix it shortly. We apologize for the inconvenience. +In the meantime, please use the parts of our site that +actually do work... somewhere. +[[\ + # could redirect the user immediately + #response.getModule('redirect').external('somewhere.spy') + + # could send an email + import time + msg = ''' +time: %s +error: %s +env: %s +other info... +''' % ( + time.asctime(time.localtime(time.time())), + error.getString(), + request.env() + ) + #function_to_send_email('webmaster@foo.com', msg) + + #or perform other generic error handling... +]] diff --git a/spyce-2.1/www/demo-site/docs/examples/error.spy b/spyce-2.1/www/demo-site/docs/examples/error.spy new file mode 100755 index 0000000000000000000000000000000000000000..a3622e40c62aefee9e5629bcf92e203fa6b8db11 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/error.spy @@ -0,0 +1,3 @@ +[[error.setFileHandler('error.spi') ]] +This is a page with an error... +[[ raise 'an error' ]] diff --git a/spyce-2.1/www/demo-site/docs/examples/errorSyntax.spy b/spyce-2.1/www/demo-site/docs/examples/errorSyntax.spy new file mode 100755 index 0000000000000000000000000000000000000000..d446df3e7968f5cead1e97ead8eaec70d97db0fe --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/errorSyntax.spy @@ -0,0 +1 @@ +[[ syntax_error_here (:) ]] diff --git a/spyce-2.1/www/demo-site/docs/examples/ex2hello.spy b/spyce-2.1/www/demo-site/docs/examples/ex2hello.spy new file mode 100755 index 0000000000000000000000000000000000000000..55de3ed38a9f8f7ae292873c06c856e0eb0c40fe --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/ex2hello.spy @@ -0,0 +1,31 @@ +[[-- With Spyce lambda's, you can separate code from presentation --]] +[[ + # Here, we will define a spyce lambda to create a page template + page = [[spy! title, data: + + + [[=title]] + + +

        [[=title]]

        + + [[ for text in data: { ]] + + [[ } ]] +
        [[=text]]
        + + + ]] +]] +[[\ + # Here we're using the "Python Chunk" code style + # we're now going to excute code to fill the values + # of our template + import random + + title = "Hello World!" + data = ['Welcome','to','Spyce', \ + random.randint(1, 100000), random.randint(100000000, 999999999)] +]] +[[-- Emit the page --]] +[[ page(title, data) ]] diff --git a/spyce-2.1/www/demo-site/docs/examples/fileupload.spy b/spyce-2.1/www/demo-site/docs/examples/fileupload.spy new file mode 100755 index 0000000000000000000000000000000000000000..c3dfee43ad32a2eeef71b28c929a30480cd7bfd1 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/fileupload.spy @@ -0,0 +1,28 @@ +[[\ +if request.post('ct'): + response.setContentType(request.post1('ct')) + if request.file('upfile')!=None: + response.write(request.file('upfile').value) + else: + print 'file not properly uploaded' + raise spyceDone +]] + + Upload a file and it will be sent back to you.
        + [[-- input forms --]] +
        + + + + + + + + + + + + +
        file:
        content-type:
        + diff --git a/spyce-2.1/www/demo-site/docs/examples/filter.py b/spyce-2.1/www/demo-site/docs/examples/filter.py new file mode 100755 index 0000000000000000000000000000000000000000..0fae1d953c6b5a4763f6ecb6044a5a18fe6550d0 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/filter.py @@ -0,0 +1,13 @@ +def htmlFilter(request, d): + # note that spoofing __htmlfields doesn't help attacker get unsafe html in; + # we always call either clean() or escape(). + try: + # don't use request['__htmlfields'], or you will recurse infinitely + toClean = request._post['__htmlfields'][0].split(',') + except KeyError: + toClean = [] + for key in d: + if key in toClean: + d[key] = [Html.clean(s) for s in d[key]] + else: + d[key] = [Html.escape(s) for s in d[key]] diff --git a/spyce-2.1/www/demo-site/docs/examples/formintro.spy b/spyce-2.1/www/demo-site/docs/examples/formintro.spy new file mode 100755 index 0000000000000000000000000000000000000000..3d712f3f4d35c56f78d17c209b6623665c6b3b1a --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/formintro.spy @@ -0,0 +1,17 @@ + + + + +
        + + +
        + +
        + One or two? Or both? + +
        + +
        + +
        \ No newline at end of file diff --git a/spyce-2.1/www/demo-site/docs/examples/formtag.spy b/spyce-2.1/www/demo-site/docs/examples/formtag.spy new file mode 100755 index 0000000000000000000000000000000000000000..98a8347e934e32fadc74403a733d03b4216f6814 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/formtag.spy @@ -0,0 +1,50 @@ + + + + +

        Primitive controls

        + +
        + + + + + + + + + + +
        + +
        +

        Compound controls

        + [[-- a simple data source for the compound controls -- in practice + this would probably come from the database --]] + [[ L = [('option %d' %i, str(i)) for i in range(5)] ]] + +
        + Radiolist + +
        + +
        + Checkboxlist + +
        + +
        + Select + +
        + +
        + Date + +
        + +

        Test it!

        + + + + diff --git a/spyce-2.1/www/demo-site/docs/examples/gif.spy b/spyce-2.1/www/demo-site/docs/examples/gif.spy new file mode 100755 index 0000000000000000000000000000000000000000..a174cab5d7015af2433662fc44bbaaea5fd37da9 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/gif.spy @@ -0,0 +1,10 @@ +[[.import name=include ]] +[[\ + # Spyce can also generate other content types + # The following code displays the Spyce logo + response.setContentType('image/gif') + import os.path, spyce + path = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'spyce.gif') + response.write(include.dump(path, 1)) + raise spyceDone +]] diff --git a/spyce-2.1/www/demo-site/docs/examples/handlerintro.spy b/spyce-2.1/www/demo-site/docs/examples/handlerintro.spy new file mode 100755 index 0000000000000000000000000000000000000000..8c9154eb9f72715986834bf2cb0071ea2d2d89be --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/handlerintro.spy @@ -0,0 +1,16 @@ +[[! +def calculate(self, api, x, y): + self.result = x * y +]] + + + + + + + + + +

        +Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]] +

        diff --git a/spyce-2.1/www/demo-site/docs/examples/handlervalidate.spy b/spyce-2.1/www/demo-site/docs/examples/handlervalidate.spy new file mode 100755 index 0000000000000000000000000000000000000000..3bf56c31d5f971938c3255a76b4daf3f58c84bbd --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/handlervalidate.spy @@ -0,0 +1,21 @@ +[[! +def calculate(self, api, x): + from spyceException import HandlerError + try: + x = float(x) + except ValueError: + raise HandlerError('Value', 'Please input a number') + if x < 0: + raise HandlerError('Value', 'Cannot take the square root of negative numbers!') + self.result = x ** 0.5 +]] + + + + + + + +

        +Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]] +

        diff --git a/spyce-2.1/www/demo-site/docs/examples/handlervalidate2.spy b/spyce-2.1/www/demo-site/docs/examples/handlervalidate2.spy new file mode 100755 index 0000000000000000000000000000000000000000..023802be889ec4751957c52fcb1cc56410544c65 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/handlervalidate2.spy @@ -0,0 +1,14 @@ +[[! +def errors(self, api): + from spyceException import HandlerError, CompoundHandlerError + cve = CompoundHandlerError() + cve.add(HandlerError('One', 'First error')) + cve.add(HandlerError('Two', 'Second error')) + if cve: + raise cve +]] + + + + + diff --git a/spyce-2.1/www/demo-site/docs/examples/hello-templated.spy b/spyce-2.1/www/demo-site/docs/examples/hello-templated.spy new file mode 100755 index 0000000000000000000000000000000000000000..ace5812da529f49acd5bbdd396c20559cb7b2aed --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/hello-templated.spy @@ -0,0 +1,17 @@ + + +[[-- Spyce can embed chunks of Python code, like this. --]] +[[\ + import time + t = time.ctime() +]] + +[[-- + pull the count from the GET variable. + the int cast is necessary since request stores everything as a string +--]] +[[ for i in range(int(request.get1('count', 2))):{ ]] +
        Hello, world!
        +[[ } ]] + +The time is now [[= t ]]. diff --git a/spyce-2.1/www/demo-site/docs/examples/hello.spy b/spyce-2.1/www/demo-site/docs/examples/hello.spy new file mode 100755 index 0000000000000000000000000000000000000000..adb3adadc7db06bf925591b047c673988a3bebd4 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/hello.spy @@ -0,0 +1,5 @@ + +[[ import spyce ]] + +Hello from Spyce version +[[= spyce.__version__ ]]! diff --git a/spyce-2.1/www/demo-site/docs/examples/hello2.spy b/spyce-2.1/www/demo-site/docs/examples/hello2.spy new file mode 100755 index 0000000000000000000000000000000000000000..cd70d38db81a6fe21c83d2e218c0b1291c445727 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/hello2.spy @@ -0,0 +1,21 @@ + + Hello [[print 'world!',]] + [[ for i in range(10): { ]] + [[=i]] + [[ } ]] +
        + Color chart:
        + + [[ cols = ['22', '66', 'AA', 'EE' ] + lencols3 = 5*len(cols) + for r in cols: { ]] + + [[ for g in cols: { ]] + [[ for b in cols: { ]] + + [[ } ]] + [[ } ]] + + [[ } ]] +
        + diff --git a/spyce-2.1/www/demo-site/docs/examples/include.spi b/spyce-2.1/www/demo-site/docs/examples/include.spi new file mode 100755 index 0000000000000000000000000000000000000000..d821bda789ba0027c5e13b9f8931355d225a2d5f --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/include.spi @@ -0,0 +1,8 @@ +begin include
        +context: [[=include.context ]]
        +from: [[=request.stack()[-2] ]]
        +foo was [[=include.vars.foo]]
        +setting foo to 'new value' [[include.vars.foo = 'new value']]
        +returing 'retval'
        +end include
        +[[ return 'retval' ]] diff --git a/spyce-2.1/www/demo-site/docs/examples/include.spy b/spyce-2.1/www/demo-site/docs/examples/include.spy new file mode 100755 index 0000000000000000000000000000000000000000..4c02a9792073f873ed11223fbfc71daded72fdb1 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/include.spy @@ -0,0 +1,13 @@ +[[.import name=include]] + + main file
        +
        + [[ + context = {'foo': 'old value'} + result=include.spyce('include.spi', context) + ]] +
        + main file again
        + context: [[=context]]
        + return value: [[=result]]
        + diff --git a/spyce-2.1/www/demo-site/docs/examples/includestatic.spi b/spyce-2.1/www/demo-site/docs/examples/includestatic.spi new file mode 100755 index 0000000000000000000000000000000000000000..a41e38450ec9d4b67f74d80d275625bf153c055d --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/includestatic.spi @@ -0,0 +1,4 @@ +begin included file
        +changing value of x
        +[[x=2]] +end included file
        diff --git a/spyce-2.1/www/demo-site/docs/examples/includestatic.spy b/spyce-2.1/www/demo-site/docs/examples/includestatic.spy new file mode 100755 index 0000000000000000000000000000000000000000..ff8bd41edfb5bc74a0f39833a4cd832708199936 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/includestatic.spy @@ -0,0 +1,10 @@ + + [[x=1]] + main file
        + x=[[=x]]
        +
        + [[.include file="includestatic.spi"]] +
        + main file again
        + x=[[=x]] + diff --git a/spyce-2.1/www/demo-site/docs/examples/info.spy b/spyce-2.1/www/demo-site/docs/examples/info.spy new file mode 100755 index 0000000000000000000000000000000000000000..9ab9ea43918b3162d086940cac87dff8de804b51 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/info.spy @@ -0,0 +1,99 @@ +[[.import name=transform]] +[[ + import string, os, time, sys, spyce + showDict = [[spy dict: + + [[ + keys = dict.keys() + keys.sort() + for i in keys: { ]] + + + + + + [[ } ]] + [[ if not dict: { ]] + None + [[ } ]] +
        [[=i]]-[[=dict[i] ]]
        +]] ]] + + + + Spyce Runtime Information + + +

        Spyce Runtime Information


        +

        Request

        + [[ showDict( { + 'file': request.filename(), + 'uri': request.uri(), + 'method': string.lower(request.method()), + 'time': time.asctime(time.localtime(time.time())), + }) ]]
        +

        Request [[=request.method()]]

        [[ + if request.method()=='GET': showDict(request.get1()) + else: showDict(request.post1()) + ]]
        +

        Request Headers

        [[ showDict(request.getHeader()) ]]
        +

        Request Modules

        [[ + moddict = {} + for mod in request._api.getModules().keys(): { + moddict[mod] = transform.html_encode(str(request._api.getModule(mod))) + } + showDict(moddict) + ]]
        +

        Request Tags

        + [[\ + has_taglib = 1 + try: + taglib + except: + has_taglib = 0 + if has_taglib: + taglibs = map( + lambda lib, libs=taglib.taglibs, spyce=spyce: '%s as %s' % ( + str(libs[lib].__class__)[len(spyce.SPYCE_LOADER)+1:], + lib), + taglib.taglibs.keys()) + context = filter( + lambda x, modules=request._api.getModules(): not modules.has_key(x), + taglib.context.keys()) + context = map( + lambda var, tagcontext=taglib.context, transform=transform: '%s = %s' % ( + str(var), transform.html_encode(tagcontext[var])), + context) + showDict( { + 'taglibs': string.join(taglibs, ', '), + 'context': string.join(context, '
        \n'), + }) + else: + print 'no tag libraries' + ]]
        +

        Server

        + [[\ + try: sys.argv + except: sys.argv = [] + showDict({ + 'curdir': os.getcwd(), + #'parallelism': concurrency, + 'id': request._api.getServerID(), + 'home': spyce.getServer().config.SPYCE_HOME, + 'spyce.path': string.join(spyce.getServer().path, '; '), + 'sys.path': string.join(filter(None, sys.path), '; '), + 'sys.argv': string.join(sys.argv, ' '), + 'imports': string.join(spyce.getServer().imports, ', '), + }) + ]]
        +

        Server Globals

        [[ showDict(request._api.getServerGlobals()) ]]
        +

        Server Environment

        [[ showDict(request.env()) ]]
        +

        Version Information

        + [[ showDict({ + 'spyce': spyce.__version__, + 'python': sys.version, + #'header': spyce.getServer().spyceHeader, + 'mode': spyce.getServer().entry, + }) ]]
        + + diff --git a/spyce-2.1/www/demo-site/docs/examples/login-optional.spy b/spyce-2.1/www/demo-site/docs/examples/login-optional.spy new file mode 100755 index 0000000000000000000000000000000000000000..c4998531e15c1c744d601a24dce0f41f6f6b7673 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/login-optional.spy @@ -0,0 +1,16 @@ + + + + [[ if request.login_id():{ ]] + You are logged in with user id [[= request.login_id() ]] + + [[ } else: { ]] + + You are not logged in. (You may login with username/password: spyce/spyce.) + [[ } ]] + +

        + (Here is some content that is the same before and after login.) + + + \ No newline at end of file diff --git a/spyce-2.1/www/demo-site/docs/examples/login-required.spy b/spyce-2.1/www/demo-site/docs/examples/login-required.spy new file mode 100755 index 0000000000000000000000000000000000000000..a0ae2723addf075006f1bd358056aa4afb3dc520 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/login-required.spy @@ -0,0 +1,8 @@ + + + + + + You are logged in. + + diff --git a/spyce-2.1/www/demo-site/docs/examples/myModule.py b/spyce-2.1/www/demo-site/docs/examples/myModule.py new file mode 100755 index 0000000000000000000000000000000000000000..e02f90f83373a853755e8a40d5ab34d70803b36d --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/myModule.py @@ -0,0 +1,6 @@ +from spyceModule import spyceModule + +class myModule(spyceModule): + def foo(self): + print 'foo called' + diff --git a/spyce-2.1/www/demo-site/docs/examples/myTaglib.py b/spyce-2.1/www/demo-site/docs/examples/myTaglib.py new file mode 100755 index 0000000000000000000000000000000000000000..298f00379b452840cd0bf851badf179c853cd5e2 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/myTaglib.py @@ -0,0 +1,18 @@ +from spyceTag import spyceTagLibrary, spyceTagPlus + +class tag_foo(spyceTagPlus): + name = 'foo' + mustend = 1 + def syntax(self): + self.syntaxPairOnly() + self.syntaxNonEmpty('val') + def begin(self, val): + self.getOut().write('' % str(val)) + def end(self): + self.getOut().write('
        ') + +class myTaglib(spyceTagLibrary): + tags = [ + tag_foo, + ] + diff --git a/spyce-2.1/www/demo-site/docs/examples/pool.spy b/spyce-2.1/www/demo-site/docs/examples/pool.spy new file mode 100755 index 0000000000000000000000000000000000000000..4dd19e3a4a8a2941e846174fac701331cfb8e01e --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/pool.spy @@ -0,0 +1,15 @@ +[[.import names="pool"]] + + The pool module supports long-lived server-pooled objects,
        + useful for database connections, and other variables
        + that are expensive to compute.
        + [[\ + if 'foo' in pool: + print 'Pooled object foo EXISTS.' + else: + pool['foo'] = 1 + print 'Pooled object foo CREATED.' + ]] +
        + Value: [[=pool['foo'] ]]

        + diff --git a/spyce-2.1/www/demo-site/docs/examples/programmaticUsage.py b/spyce-2.1/www/demo-site/docs/examples/programmaticUsage.py new file mode 100755 index 0000000000000000000000000000000000000000..da949dc13dc5ab64142be9325e7263d10ad4647b --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/programmaticUsage.py @@ -0,0 +1,70 @@ +import os +import spyce + +#--------------------------------------------------[ a hardcoded fake request ] +class TestRequest(spyce.spyceRequest): + environment = {'QUERY_STRING': '', + 'REQUEST_METHOD': 'get'} + headers = {} + + def env(self, name=None): + if not name: + return self.environment + return self.environment[name] + + def getHeader(self, type=None): + return self.headers[type] + + def getServerID(self): + os.getpid() + + +#--------------------------------------------------[ a hardcoded fake response ] +class TestResponse(spyce.spyceResponse): + returncode = spyce.spyceResponse.RETURN_OK + out = '' + err = '' + headers = {} + def write(self, s): + self.out = self.out+s + def writeErr(self, s): + self.err = self.err+s + def close(self): + pass + def clear(self): + self.out = '' + def sendHeaders(self): + pass + def clearHeaders(self): + self.headers = {} + def setContentType(self, content_type): + self.headers['content-type'] = content_type + def setReturnCode(self, code): + self.returncode = code + def addHeader(self, type, data, replace=0): + self.headers[type] = data + def flush(self, *args): + pass + def unbuffer(self): + pass + + +#--------------------------------------------------[ invoking Spyce code ] +import spyceConfig + +req = TestRequest() +resp = TestResponse() + +spyceCode = 'Hello, the following names are defined: "[[print dir(),]]", and ' +spyceCode += 'these were the parameters passed: [[print (a, b, c, d)]]\n', + +spyce.spyceStringHandler( + req, + resp, + spyceCode, + sig='a, b, c="asd", d=None', + args=(1, 'two'), + kwargs={'c': 'aa'}, + config=spyceConfig) +print resp.err +print resp.out diff --git a/spyce-2.1/www/demo-site/docs/examples/recurse.spy b/spyce-2.1/www/demo-site/docs/examples/recurse.spy new file mode 100755 index 0000000000000000000000000000000000000000..2e26e907a2794d719f2481ad64ff9397ecdfb12c --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/recurse.spy @@ -0,0 +1,10 @@ +[[.import name=include]] +[[\ + try: i = include.context['i'] + except: i = 0 + i = i + 1 + print i + if i < 5: include.spyce(request.filename(), {'i':i}) + else: print '---' + print i +]] diff --git a/spyce-2.1/www/demo-site/docs/examples/redirect.spy b/spyce-2.1/www/demo-site/docs/examples/redirect.spy new file mode 100755 index 0000000000000000000000000000000000000000..31c8a6f26e51c6b4c1f3a86e1788738182f6105d --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/redirect.spy @@ -0,0 +1,39 @@ +[[.import name=redirect]] + + [[ type = request['type'] + url = request['url'] + if url and not type: { + ]] + + please select a redirect type +
        + [[ + } + if type and url: { + if type=='internal': redirect.internal(url) + if type=='external': redirect.external(url) + if type=='externalRefresh': redirect.externalRefresh(url, 3) + ]] Received POST info: [[=request.post1()]] [[ + } + ]] +

        + Redirection url: +
        + Redirection type: + + + + +
        + + internal +
        + + external +
        + + externalRefresh (3 seconds) +
        + +
        + diff --git a/spyce-2.1/www/demo-site/docs/examples/request.spy b/spyce-2.1/www/demo-site/docs/examples/request.spy new file mode 100755 index 0000000000000000000000000000000000000000..5e6dd22aa7a79bb804e3445c08350c7081635220 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/request.spy @@ -0,0 +1,35 @@ + + Using the Spyce request object, we can obtain + information sent along with the request. The + table below shows some request methods and their + return values. Use the form below to post form + data via GET or POST.
        +
        + [[-- input forms --]] +
        + get: + +
        +
        + post: + +
        +
        + [[-- tabulate response information --]] + + + + + + [[ for method in ['uri()', 'uri("path")', + 'uri("query")', 'method()','query()', + 'get()','get1()', 'post()','post1()', + 'getHeader()','env()', 'filename()']: { + ]] + + + + + [[ } ]] +
        MethodReturn value
        request.[[=method]][[=eval('request.%s' % method)]]
        + diff --git a/spyce-2.1/www/demo-site/docs/examples/scheduling.py b/spyce-2.1/www/demo-site/docs/examples/scheduling.py new file mode 100755 index 0000000000000000000000000000000000000000..4dc10ca6cc1ebd7d472ff15012291d4dcb965411 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/scheduling.py @@ -0,0 +1,9 @@ +import spyce, scheduler + +def delete_unsubmitted(): + db = spyce.SPYCE_GLOBALS['dbpool'].connection() + sql = "DELETE FROM alerts WHERE status = 'unsubmitted' AND created < now() - '1 week'::interval" + db.execute(sql) + +# delete alerts that were created over a week ago but but still not submitted +scheduler.schedule_daily(00, 10, delete_unsubmitted) diff --git a/spyce-2.1/www/demo-site/docs/examples/session2.spy b/spyce-2.1/www/demo-site/docs/examples/session2.spy new file mode 100755 index 0000000000000000000000000000000000000000..bfd7291ab1560c5b75c5e65a43d5d4352e01bd49 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/session2.spy @@ -0,0 +1,8 @@ +[[.import name=session ]] +[[\ + session['visited'] = session.get('visited', 0) + 1 +]] + + + +You visited us [[= session['visited'] ]] times. diff --git a/spyce-2.1/www/demo-site/docs/examples/spyce.gif b/spyce-2.1/www/demo-site/docs/examples/spyce.gif new file mode 100755 index 0000000000000000000000000000000000000000..926617fd800398dd2eecedc53ed904220ff6d9a2 Binary files /dev/null and b/spyce-2.1/www/demo-site/docs/examples/spyce.gif differ diff --git a/spyce-2.1/www/demo-site/docs/examples/spylambda.spy b/spyce-2.1/www/demo-site/docs/examples/spylambda.spy new file mode 100755 index 0000000000000000000000000000000000000000..2be542e15c25228cd8ba68d4f5eb6d69cdc2b33c --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/spylambda.spy @@ -0,0 +1,33 @@ +[[\ + # table template + table = [[spy! title, data: + + + [[for cell in title: {]] + + [[}]] + + [[for row in data: {]] + + [[for cell in row: {]] + + [[}]] + + [[}]] +
        [[=cell]]
        [[=cell]]
        + ]] + + # table information + title = ['Country', 'Size', 'Population', 'GDP per capita'] + data = [ + [ 'USA', '9,158,960', '280,562,489', '$36,300' ], + [ 'Canada', '9,220,970', '31,902,268', '$27,700' ], + [ 'Mexico', '1,923,040', '103,400,165', '$9,000' ], + ] +]] + +[[-- emit web page --]] + + [[ table(title, data) ]] + + diff --git a/spyce-2.1/www/demo-site/docs/examples/stdout.spy b/spyce-2.1/www/demo-site/docs/examples/stdout.spy new file mode 100755 index 0000000000000000000000000000000000000000..9e6678c3aa3834f4d9d613769146eddc273588bb --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/stdout.spy @@ -0,0 +1,11 @@ + + [[ print '''Using the stdout module redirects + stdout to the response object, so you can use + print!''']]
        + redirecting stdout can be used to... + [[stdout.push()]] + [[print 'capture']] out[[='put']] + [[cached = stdout.pop()]] + ... for later:
        + [[=cached]] + diff --git a/spyce-2.1/www/demo-site/docs/examples/tag.spy b/spyce-2.1/www/demo-site/docs/examples/tag.spy new file mode 100755 index 0000000000000000000000000000000000000000..69d8d63bcdee58414562e47c53f01c68829c8582 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/tag.spy @@ -0,0 +1,6 @@ +[[.taglib name=myTaglib as=me]] + +[[ for x in range(2, 6):{ ]] + size [[= x ]] +[[ } ]] + diff --git a/spyce-2.1/www/demo-site/docs/examples/tagbold.spi b/spyce-2.1/www/demo-site/docs/examples/tagbold.spi new file mode 100755 index 0000000000000000000000000000000000000000..33d9fc309a5f07dc0ffcf2ec675eac635411de92 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/tagbold.spi @@ -0,0 +1,5 @@ +[[.begin name=bold buffers=True]] + +[[=_contents]] + +[[.end]] \ No newline at end of file diff --git a/spyce-2.1/www/demo-site/docs/examples/transform.spy b/spyce-2.1/www/demo-site/docs/examples/transform.spy new file mode 100755 index 0000000000000000000000000000000000000000..a804f21a9cc29f96b3b2d7ca07fad4779cf6fa4f --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/transform.spy @@ -0,0 +1,47 @@ +[[.import name=transform]] +[[\ +def tag(o, tags=[], **kwargs): + import string + pre = string.join(map(lambda x: '<'+x+'>',tags)) + tags.reverse() + post = string.join(map(lambda x: '',tags)) + return pre+str(o)+post +def bold(o, _tag=tag, **kwargs): + kwargs['tags'] = ['b'] + return apply(_tag, (o,), kwargs) +def bolditalic(o, _tag=tag, **kwargs): + kwargs['tags'] = ['b','i'] + return apply(_tag, (o,), kwargs) +myfilter = transform.create(['html_encode', bolditalic]) +mystring = 'bold and italic unsafe string: < > &' +def simpletable(o, **kwargs): + s = '' + for row in o: + s=s+'' + for cell in row: + s=s+'' + s=s+'' + s = s+'
        '+str(cell)+'
        ' + return s +]] + + install an expression filter:
        + [[transform.expr(['html_encode', tag])]] + 1.[[=mystring, tags=['b','i'] ]] +
        + [[transform.expr(myfilter)]] + 2.[[=mystring]] + [[transform.expr()]] +

        + or use a filter directly:
        + 1.[[=transform.create(['html_encode',tag])(mystring,tags=['b','i'])]] +
        + 2.[[=myfilter(mystring)]] +

        + Formatting data in a table...
        + [[=simpletable([ [1,2,3], [4,5,6] ])]] +

        + Though the transform module is flexible,
        + most users will probably only install the
        + html_encode filter. + diff --git a/spyce-2.1/www/demo-site/docs/examples/whatsnew.spy b/spyce-2.1/www/demo-site/docs/examples/whatsnew.spy new file mode 100755 index 0000000000000000000000000000000000000000..1b3d6fb4cd72c23532d544d53485e55965369328 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/whatsnew.spy @@ -0,0 +1,22 @@ + + + + +[[! +def list_new(self, api, name): + if api.db.todo_lists.selectfirst_by(name=name): + raise HandlerError('New list', 'a list with that description already exists') + api.db.todo_lists.insert(name=name) + api.db.flush() +]] + +

        To-do lists

        + +[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]] + + +

        New list

        + +: + + diff --git a/spyce-2.1/www/demo-site/docs/examples/whatsnew2.spy b/spyce-2.1/www/demo-site/docs/examples/whatsnew2.spy new file mode 100755 index 0000000000000000000000000000000000000000..cd96fffd693cc451933834825a99cb8587ca152b --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/examples/whatsnew2.spy @@ -0,0 +1,18 @@ + + +[[! +def list_select(self, api, items): + self.selected = ', '.join(items) +]] + +[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]] + +

        Select lists

        + + + + + +[[ if hasattr(self, 'selected'):{ ]] + Selected: [[= self.selected ]] +[[ } ]] diff --git a/spyce-2.1/www/demo-site/docs/fileupload.src.html b/spyce-2.1/www/demo-site/docs/fileupload.src.html new file mode 100755 index 0000000000000000000000000000000000000000..79ce495af45c4e92c9922c603e1f97d612a44016 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/fileupload.src.html @@ -0,0 +1,229 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/fileupload.spy +
        + +
        [[\ 
        +if request.post('ct'):
        +  response.setContentType(request.post1('ct'))
        +  if request.file('upfile')!=None:
        +    response.write(request.file('upfile').value)
        +  else:
        +    print 'file not properly uploaded'
        +  raise spyceDone
        +]]
        +<html><body>
        +  Upload a file and it will be sent back to you.<br>
        +  [[-- input forms --]]
        +  <hr>
        +  <table>
        +    <form action="[[=request.uri('path')]]" method=post 
        +        enctype="multipart/form-data">
        +      <tr>
        +        <td>file:</td>
        +        <td><input type=file name=upfile></td>
        +      </tr><tr>
        +        <td>content-type:</td>
        +        <td><input type=text name=ct value="text/html"></td>
        +      </tr><tr>
        +        <td><input type=submit value=ok></td>
        +      </tr>
        +    </form>
        +  </table>
        +</body></html>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/filter.src.html b/spyce-2.1/www/demo-site/docs/filter.src.html new file mode 100755 index 0000000000000000000000000000000000000000..be6b1d1f47ad4ccbfda2362eed819889bdd2fdb3 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/filter.src.html @@ -0,0 +1,233 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/filter.py +
        + + + +
        +def htmlFilter(request, d):
        +  # note that spoofing __htmlfields doesn't help attacker get unsafe html in;
        +  # we always call either clean() or escape().
        +  try:
        +    # don't use request['__htmlfields'], or you will recurse infinitely
        +    toClean = request._post['__htmlfields'][0].split(',')
        +  except KeyError:
        +    toClean = []
        +  for key in d:
        +    if key in toClean:
        +      d[key] = [Html.clean(s) for s in d[key]]
        +    else:
        +      d[key] = [Html.escape(s) for s in d[key]]
        +
        +
        + +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/formintro.src.html b/spyce-2.1/www/demo-site/docs/formintro.src.html new file mode 100755 index 0000000000000000000000000000000000000000..9258f5a6bcc7c6f6d1033e6b8016bc93b1576104 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/formintro.src.html @@ -0,0 +1,217 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/formintro.spy +
        + +
        <spy:parent title="Form tag intro" />
        +
        +<f:form>
        +
        +<div class="simpleform">
        +  <f:text name="text1" label="Text input" default="change me" size=10 maxlength=30 />
        +  <f:text name="text2" label="Text input 2" value="change me" size=10 maxlength=30 />
        +</div>
        +
        +<fieldset style="clear: both">
        +  <legend>One or two?  Or both?</legend>
        +  <f:checkboxlist class="radio" name="checkboxlist" data="[(1, 'one'), (2, 'two')]" />
        +</fieldset>
        +
        +<div style="clear: both"><f:submit /></div>
        +
        +</f:form>
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/formtag.src.html b/spyce-2.1/www/demo-site/docs/formtag.src.html new file mode 100755 index 0000000000000000000000000000000000000000..b90f2728920a3106bc5228f1db30ed465b21f5be --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/formtag.src.html @@ -0,0 +1,251 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/formtag.spy +
        + +
        <spy:parent title="Form tag example" />
        +
        +<f:form>
        +
        +<h2>Primitive controls</h2>
        +
        +<div class="simpleform">
        +  <f:text name="mytext" label="Text" default="some text" size=10 maxlength=30 />
        +
        +  <f:password name="mypass" label="Password" default="secret" />
        +
        +  <f:textarea name="mytextarea" label="Textarea" default="rimon" rows=2 cols=50></f:textarea>
        +
        +  <label for="mycheck">Checkbox</label><f:checkbox name=mycheck value=check1 />
        +
        +  <label for="myradio1">Radio option 1</label><f:radio name=myradio value=option1 />
        +  <label for="myradio2">Radio option 2</label><f:radio name=myradio value=option2 />
        +</div>
        +
        +<div style="clear: both">
        +  <h2 style="padding-top: 1em;">Compound controls</h2>
        +  [[-- a simple data source for the compound controls -- in practice
        +	     this would probably come from the database --]]
        +  [[ L = [('option %d' %i, str(i)) for i in range(5)] ]]
        +
        +  <fieldset>
        +    <legend>Radiolist</legend>
        +    <f:radiolist class=radio name=radiolist data="L" default="3" />
        +  </fieldset>
        +
        +  <fieldset>
        +    <legend>Checkboxlist</legend>
        +    <f:checkboxlist class=radio name=checkboxlist data="L" default="=['0', '1']" />
        +  </fieldset>
        +
        +  <fieldset>
        +    <legend>Select</legend>
        +    <f:select name=myselect multiple size=5 data="L" default="2" />
        +  </fieldset>
        +
        +  <fieldset>
        +    <legend>Date</legend>
        +    <f:date name=mydate />
        +  </fieldset>
        +
        +  <h2 style="clear:both; padding-top: 1em;">Test it!</h2>
        +  <input type="submit" name="foo" value="Submit!">
        +
        +</f:form>
        +
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/get.html b/spyce-2.1/www/demo-site/docs/get.html new file mode 100755 index 0000000000000000000000000000000000000000..0da2736bb6c3c5db6210477e6b7d5745fc8ef0e4 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/get.html @@ -0,0 +1,220 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Download
        +
        + + + +Before downloading this software, be sure to read its license agreement. The latest +released version of Spyce is v2.1.3, +and is available +from +the SourceForge +download page. +

        +

        +Spyce requires Python version 2.3 or later. +

        +After downloading, simply uncompress to the directory of your choice. +(/usr/share/spyce is common on Unix-like system; on Windows, +C:\Spyce or C:\Program Files\Spyce are popular.) +

        +Alternatively, you can check out the very latest code from the subversion repository, +by typing the following command:

        + + +
        + +svn co https://svn.sourceforge.net/svnroot/spyce/trunk/spyce/ + +
        +

        +(For windows, Tortoise SVN is usually +the weapon of choice instead of the command line.) +

        +If you would like to submit a patch, please use the SF tracker or mailing list. +

        + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/get.spy b/spyce-2.1/www/demo-site/docs/get.spy new file mode 100755 index 0000000000000000000000000000000000000000..668f3851c7864f2dff08530561cbdd37d716820d --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/get.spy @@ -0,0 +1,47 @@ +[[.compact]] +[[.import name=include]] +[[\ + include.spyce('/inc/head.spi', {'pagename': 'Download', 'page': 'get.html'}) + release='1' + import spyce, verchk +]] + +Before downloading this software, be sure to read its license agreement. The latest +released version of Spyce is v[[=spyce.__version__]].[[=spyce.__release__]], +and is available +from +the SourceForge +download page. + +

        +

        +Spyce requires Python version [[= verchk.REQUIRED ]] or later. + +

        +After downloading, simply uncompress to the directory of your choice. +(/usr/share/spyce is common on Unix-like system; on Windows, +C:\Spyce or C:\Program Files\Spyce are popular.) + +

        + +Alternatively, you can check out the very latest code from the subversion repository, +by typing the following command:

        + + + +
        + + svn co https://svn.sourceforge.net/svnroot/spyce/trunk/spyce/ + +
        +

        +(For windows, Tortoise SVN is usually +the weapon of choice instead of the command line.) + +

        +If you would like to submit a patch, please use the SF tracker or mailing list. +

        + +[[include.spyce('/inc/tail.spi')]] diff --git a/spyce-2.1/www/demo-site/docs/gif.src.html b/spyce-2.1/www/demo-site/docs/gif.src.html new file mode 100755 index 0000000000000000000000000000000000000000..e4db99cbe9e41273d04da61fb7d326064d016d86 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/gif.src.html @@ -0,0 +1,211 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/gif.spy +
        + +
        [[.import name=include ]]
        +[[\
        +  # Spyce can also generate other content types
        +  # The following code displays the Spyce logo
        +  response.setContentType('image/gif')
        +  import os.path, spyce
        +  path = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'spyce.gif')
        +  response.write(include.dump(path, 1))
        +  raise spyceDone
        +]]
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/handlerintro.src.html b/spyce-2.1/www/demo-site/docs/handlerintro.src.html new file mode 100755 index 0000000000000000000000000000000000000000..22206d451e6376a9a46ae6c35d0b185b0ee891d8 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/handlerintro.src.html @@ -0,0 +1,217 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/handlerintro.spy +
        + +
        [[!
        +def calculate(self, api, x, y):
        +    self.result = x * y
        +]]
        +
        +<spy:parent title="Active Handler example" />
        +<f:form>
        +    <f:text name="x:float" default="2" label="first value" />
        +    <f:text name="y:float" default="3" label="second value" />
        +
        +    <f:submit handler="self.calculate" value="Multiply" />
        +</f:form>
        +
        +<p>
        +Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]]
        +</p>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/handlervalidate.src.html b/spyce-2.1/www/demo-site/docs/handlervalidate.src.html new file mode 100755 index 0000000000000000000000000000000000000000..7a783b4c0f27310c98219916ceab131ca405eaf9 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/handlervalidate.src.html @@ -0,0 +1,222 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/handlervalidate.spy +
        + +
        [[!
        +def calculate(self, api, x):
        +    from spyceException import HandlerError
        +    try:
        +        x = float(x)
        +    except ValueError:
        +        raise HandlerError('Value', 'Please input a number')
        +    if x < 0:
        +        raise HandlerError('Value', 'Cannot take the square root of negative numbers!')
        +    self.result = x ** 0.5
        +]]
        +
        +<spy:parent title="Active Handler Validation" />
        +<f:form>
        +    <f:text name="x" default="-1" label="Value:" />
        +    <f:submit handler="self.calculate" value="Square root" />
        +</f:form>
        +
        +<p>
        +Result: [[= hasattr(self, 'result') and self.result or '(no result yet)' ]]
        +</p>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/handlervalidate2.src.html b/spyce-2.1/www/demo-site/docs/handlervalidate2.src.html new file mode 100755 index 0000000000000000000000000000000000000000..9cba8e017680d0234803eeb620bd1248369a2149 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/handlervalidate2.src.html @@ -0,0 +1,215 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/handlervalidate2.spy +
        + +
        [[!
        +def errors(self, api):
        +    from spyceException import HandlerError, CompoundHandlerError
        +    cve = CompoundHandlerError()
        +    cve.add(HandlerError('One', 'First error'))
        +    cve.add(HandlerError('Two', 'Second error'))
        +    if cve:
        +        raise cve
        +]]
        +
        +<spy:parent title="Active Handler Validation 2" />
        +<f:form>
        +    <f:submit handler="self.errors" value="Show me some errors" />
        +</f:form>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/hello-templated.src.html b/spyce-2.1/www/demo-site/docs/hello-templated.src.html new file mode 100755 index 0000000000000000000000000000000000000000..29a7fe64f05b944658b7fa969cde6d15ee6d55d6 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/hello-templated.src.html @@ -0,0 +1,218 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/hello-templated.spy +
        + +
        <spy:parent title="Hello, world!" />
        +
        +[[-- Spyce can embed chunks of Python code, like this. --]]
        +[[\
        +  import time
        +  t = time.ctime()
        +]]
        +
        +[[-- 
        +  pull the count from the GET variable.
        +  the int cast is necessary since request stores everything as a string 
        +--]]
        +[[ for i in range(int(request.get1('count', 2))):{ ]]
        +<div>Hello, world!</div>
        +[[ } ]]
        +
        +The time is now [[= t ]].
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/include.src.html b/spyce-2.1/www/demo-site/docs/include.src.html new file mode 100755 index 0000000000000000000000000000000000000000..b57f0c3e12a2f8797b2fea5ee39d5503d628e8d9 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/include.src.html @@ -0,0 +1,209 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/include.spi +
        + +
        begin include<br>
        +context: [[=include.context ]]<br>
        +from: [[=request.stack()[-2] ]]<br>
        +foo was [[=include.vars.foo]]<br>
        +setting foo to 'new value' [[include.vars.foo = 'new value']]<br>
        +returing 'retval'<br>
        +end include<br>
        +[[ return 'retval' ]]
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/includestatic.src.html b/spyce-2.1/www/demo-site/docs/includestatic.src.html new file mode 100755 index 0000000000000000000000000000000000000000..242c5b9219856637f7871d69d1ebe8f6791a2c7b --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/includestatic.src.html @@ -0,0 +1,205 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/includestatic.spi +
        + +
        begin included file<br>
        +changing value of x<br>
        +[[x=2]]
        +end included file<br>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/license.html b/spyce-2.1/www/demo-site/docs/license.html new file mode 100755 index 0000000000000000000000000000000000000000..1c5dd7049dea541314277acfcdd3feb2e92304ea --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/license.html @@ -0,0 +1,236 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +License
        +
        + + + +This software is distributed at no cost, under the terms of +the following open-source license.

        +
        + +
        Copyright (c) 2002-05 Rimon Barr.
        +All rights reserved.
        +
        +Redistribution and use in source and binary forms, with or without
        +modification, are permitted provided that the following conditions are met:
        +
        +1. Redistributions of source code must retain the above copyright notice and
        +this LICENSE in its entirety including the disclaimer. The LICENSE of this
        +product may only be modified by the Copyright holder.
        +
        +2. Redistributions in binary form must reproduce the above copyright notice
        +and this LICENSE in its entirety including the disclaimer in the documentation
        +and/or other materials provided with the distribution.
        +
        +3. The end-user documentation included with the redistribution, if any, must
        +include the following acknowledgment: "This product uses Spyce, Copyright
        +Rimon Barr." Alternately, this acknowledgment may appear in the software
        +itself, wherever such third-party acknowledgments normally appear. The
        +documentation must also provide a instructions on how to receive an original
        +Spyce distribution, preferably a link to the website
        +http://spyce.sourceforge.net.
        +
        +4. The names "Spyce", or "Rimon Barr" must not be used to endorse or promote
        +products derived from this software without prior written permission. For
        +written permission, please contact rimon-AT-acm.org.
        +
        +5. Products derived from this software may not be called "Spyce", nor may
        +"Spyce" appear in their names, without prior written permission of the
        +Copyright holder.
        +
        +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
        +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
        +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL RIMON BARR
        +OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
        +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
        +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
        +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
        +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
        +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
        +POSSIBILITY OF SUCH DAMAGE.
        +
        +
        +

        + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/license.spy b/spyce-2.1/www/demo-site/docs/license.spy new file mode 100755 index 0000000000000000000000000000000000000000..b271044e09fde78d6bc036d897595502c0f6991c --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/license.spy @@ -0,0 +1,15 @@ +[[.compact]] +[[.import name=include]] +[[include.spyce('/inc/head.spi', {'pagename': 'License', 'page': 'license.html'})]] + +This software is distributed at no cost, under the terms of +the following open-source license.

        + +
        +[[.compact mode=off]] +
        [[=include.dump('../../LICENCE')]]
        +[[.compact]] +
        +

        + +[[include.spyce('/inc/tail.spi')]] diff --git a/spyce-2.1/www/demo-site/docs/links.html b/spyce-2.1/www/demo-site/docs/links.html new file mode 100755 index 0000000000000000000000000000000000000000..67e48776d00ff95b0c553f5472c86a4ade0ff3ca --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/links.html @@ -0,0 +1,256 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Resources
        +
        + + + +A page of links to various sources of information about or related to Spyce. +If you know of any additional Spyce-related articles, tutorials, projects, or +hosts please send email.

        +

        +Powered by Spyce +

        +Articles and various documents online +
          +
        • Scharfe Würzmischun, iX, 12 Dec 2003. +(Link) +
          An article in a popular German IT magazine.
        • +

        • Python wrap-up, Network World Fusion, 16 Jun 2003. +(Link) +
          "Spyce consists of a small core module with additional modules, +including access to HTTP requests, generation of HTTP responses and error +handling."
        • +

        • Python web development, really, comp.lang.python, 25 Jan 2003. +(Link) +
          "I've checked the majority of them out and Spyce gets my thumbs up." +
        • +

        • Web Framework shootout, undated. +(Link) +
          "Spyce is one among a family of systems that embeds Python in HTML, +similar to PHP or ASP. Many of the other attempts have fallen by the +wayside, never developed to their potential..."
        • +

        • Readers react to phpBB & Spyce series, LinuxWorld, 25 Nov +2002. (Link) +
          "But it does demonstrate you can make Python - and therefore Spyce - do +just about anything short of washing your socks."
        • +

        • How to Spyce up your data, LinuxWorld, 18 Nov 2002. (Link) +
          "Third and final installment in our series on tricks, tips and +techniques for making Spyce work with your server."
        • +

        • PHP and Python Hit Prime Time, ComputerWorld, 11 Nov 2002. (Link) +
          "Spyce is a newcomer to the Python Web applications approach, and it +may not only push Webware off the map, it could also eventually give PHP a +run for its money."
        • +

        • Variety is the Spyce of Python, LinuxWorld, 11 Nov 2002. (Link) +
          "Spyce is a versatile means of integrating Python code into HTML and +vice versa. Here are more tricks, tips, and techniques for making spyce work +with your server."
        • +

        • How to add Spyce to your life, LinuxWorld, 4 Nov 2002. (Link currently +broken; maybe LinuxWorld will fix it...) +
          "There's a new method for doing server-side Python scripting for Web +applications in town, and its name is Spyce. Spyce takes an approach similar +to PHP. It lets you intermingle Python scripts, statements and variables +with HTML to produce dynamically generated Web pages."
        • +

        • Spyce Wikipedia entry +
        • +

        +Spyce hosting +

          +
        • WebFaction (formerly python-hosting)
        • +
        • GrokThis.net
        • +
        • WestHost -- inexpensive virtual private servers (VPS)
        • +
        • ServerPronto -- dedicated servers cheaper than some outfits' VPS
        • +
        • Anywhere that lets you run CGI, FastCGI, mod_python, or gives you a VPS +

        + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/links.spy b/spyce-2.1/www/demo-site/docs/links.spy new file mode 100755 index 0000000000000000000000000000000000000000..84085a42676036099bc96f6b2f615578013c877c --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/links.spy @@ -0,0 +1,90 @@ +[[.compact]] +[[.import name=include]] +[[include.spyce('/inc/head.spi', {'pagename': 'Resources', 'page': 'links.html'})]] + +A page of links to various sources of information about or related to Spyce. +If you know of any additional Spyce-related articles, tutorials, projects, or +hosts please send email.

        + +

        +Powered by Spyce +

        + +Articles and various documents online + +
          + +
        • Scharfe Würzmischun, iX, 12 Dec 2003. + (Link) +
          An article in a popular German IT magazine.
        • + +

        • Python wrap-up, Network World Fusion, 16 Jun 2003. + (Link) +
          "Spyce consists of a small core module with additional modules, + including access to HTTP requests, generation of HTTP responses and error + handling."
        • + +

        • Python web development, really, comp.lang.python, 25 Jan 2003. + (Link) +
          "I've checked the majority of them out and Spyce gets my thumbs up." +
        • + +

        • Web Framework shootout, undated. + (Link) +
          "Spyce is one among a family of systems that embeds Python in HTML, + similar to PHP or ASP. Many of the other attempts have fallen by the + wayside, never developed to their potential..."
        • + +

        • Readers react to phpBB & Spyce series, LinuxWorld, 25 Nov + 2002. (Link) +
          "But it does demonstrate you can make Python - and therefore Spyce - do + just about anything short of washing your socks."
        • + +

        • How to Spyce up your data, LinuxWorld, 18 Nov 2002. (Link) +
          "Third and final installment in our series on tricks, tips and + techniques for making Spyce work with your server."
        • + +

        • PHP and Python Hit Prime Time, ComputerWorld, 11 Nov 2002. (Link) +
          "Spyce is a newcomer to the Python Web applications approach, and it + may not only push Webware off the map, it could also eventually give PHP a + run for its money."
        • + +

        • Variety is the Spyce of Python, LinuxWorld, 11 Nov 2002. (Link) +
          "Spyce is a versatile means of integrating Python code into HTML and + vice versa. Here are more tricks, tips, and techniques for making spyce work + with your server."
        • + +

        • How to add Spyce to your life, LinuxWorld, 4 Nov 2002. (Link currently + broken; maybe LinuxWorld will fix it...) +
          "There's a new method for doing server-side Python scripting for Web + applications in town, and its name is Spyce. Spyce takes an approach similar + to PHP. It lets you intermingle Python scripts, statements and variables + with HTML to produce dynamically generated Web pages."
        • + +

        • Spyce Wikipedia entry +
        • + +

        + +Spyce hosting + +

          +
        • WebFaction (formerly python-hosting)
        • +
        • GrokThis.net
        • +
        • WestHost -- inexpensive virtual private servers (VPS)
        • +
        • ServerPronto -- dedicated servers cheaper than some outfits' VPS
        • +
        • Anywhere that lets you run CGI, FastCGI, mod_python, or gives you a VPS +

        + + +[[include.spyce('/inc/tail.spi')]] diff --git a/spyce-2.1/www/demo-site/docs/login-optional.src.html b/spyce-2.1/www/demo-site/docs/login-optional.src.html new file mode 100755 index 0000000000000000000000000000000000000000..9cf2ccbbf84eb9c61c294923d0b6b2c70d64ad8f --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/login-optional.src.html @@ -0,0 +1,216 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/login-optional.spy +
        + +
        <html><body>
        +
        +<f:form>
        +  [[ if request.login_id():{ ]]
        +    You are logged in with user id [[= request.login_id() ]]
        +    <spy:logout />
        +  [[ } else: { ]]
        +    <spy:login />
        +    You are not logged in.  (You may login with username/password: spyce/spyce.)
        +  [[ } ]]
        +  
        +  <p>
        +  (Here is some content that is the same before and after login.)
        +</f:form>
        +
        +</body></html>
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/login-required.src.html b/spyce-2.1/www/demo-site/docs/login-required.src.html new file mode 100755 index 0000000000000000000000000000000000000000..989cd7c84f89f77c42f86ef29c5dd2ca132110c9 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/login-required.src.html @@ -0,0 +1,209 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/login-required.spy +
        + +
        <spy:parent title="Login example" />
        +
        +<f:form>
        +  <spy:login_required />
        +
        +  You are logged in.
        +  <spy:logout />
        +</f:form>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/mail.html b/spyce-2.1/www/demo-site/docs/mail.html new file mode 100755 index 0000000000000000000000000000000000000000..8f3b519db35208442a12c028f92064030cde9b46 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/mail.html @@ -0,0 +1,279 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Spyce Powered
        +
        + + + +If you use and like Spyce, you can also help broaden the Spyce community by +placing one of the logos below on your website, with a link to the Spyce site. +Code snippets are provided so that you can simply copy-and-paste into your +HTML:
        + + + + + + + + + + + + + + + + + + + + + + +
        LogoHTML code
        + + +
        +<a href="http://spyce.sourceforge.net">
        +  <img width=120 height=50 border=0 
        +    src="http://spyce.sourceforge.net/spycepow.gif">
        +</a>
        +
        + + +
        +<a href="http://spyce.sourceforge.net">
        +  <img width=72 height=32 border=0 
        +    src="http://spyce.sourceforge.net/spycepow2.gif">
        +</a>
        +
        + + +
        +<a href="http://spyce.sourceforge.net">
        +  <img width=30 height=36 border=0 
        +    src="http://spyce.sourceforge.net/spyce.gif">
        +</a>
        +
        + + +
        +<a href="http://spyce.sourceforge.net">
        +  <img width=134 height=32 border=0 
        +    src="http://spyce.sourceforge.net/spycepow3.gif">
        +</a>
        +

        +The very cute Spyce mascot is called 'Pilpel', which means pepper (as in the +vegetable and the spice) in Hebrew. It's a specially-shaped pepper that +reminds one of a Python snake. Pilpel was designed and drawn by Natalya Katsnelson.

        +Direct assistance +

        +If you would like get your hands dirty, there are things for you to do: +

          +
        • report bugs and help in their resolution
        • +
        • send new code or patches (but please make sure that new code does not +break things that work)
        • +
        • write more documentation, more examples and demos
        • +

        +If you would like to join the Spyce project as a developer, documenter, +publicist, or in some other capacity, just fire an email.

        +Spyce evangelism +

        +Making Spyce more popular means more support from the user community, more bug +reports, more ideas, and more people. All of this can boost Spyce development +and bug fixing activities, improve the quality of support provided by the +community in mailing lists, etc. Here are some evangelism ideas: +

          +
        • place links to the Spyce site at your Web pages
        • +
        • rate Spyce in software directories such as freshmeat.net and +OSDir.com
        • +
        • recommend Spyce in web forums, mailing lists, etc.
        • +
        • write Spyce reviews for web sites, printed magazines, etc.
        • +
        • tell your friends
        • +

        +Your help in spreading the word and broadening the community is appreciated! +

        + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/mail.spy b/spyce-2.1/www/demo-site/docs/mail.spy new file mode 100755 index 0000000000000000000000000000000000000000..9f0d7a88fe5491d79cb592ab41ce59b551759e7a --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/mail.spy @@ -0,0 +1,118 @@ +[[.compact]] +[[.import names="include,transform"]] +[[include.spyce('/inc/head.spi', {'pagename': 'Spyce Powered', 'page': 'mail.html'})]] + +If you use and like Spyce, you can also help broaden the Spyce community by +placing one of the logos below on your website, with a link to the Spyce site. +Code snippets are provided so that you can simply copy-and-paste into your +HTML:
        + + + + + + + + + + + + + + + + + + + + + + + +
        LogoHTML code
        + + +
        [[=transform.html_encode('''
        +
        +  
        +''')]]
        +
        + + +
        [[=transform.html_encode('''
        +
        +  
        +''')]]
        +
        + + +
        [[=transform.html_encode('''
        +
        +  
        +''')]]
        +
        + + +
        [[=transform.html_encode('''
        +
        +  
        +''')]]
        +

        + +The very cute Spyce mascot is called 'Pilpel', which means pepper (as in the +vegetable and the spice) in Hebrew. It's a specially-shaped pepper that +reminds one of a Python snake. Pilpel was designed and drawn by Natalya Katsnelson.

        + + +Direct assistance +

        + +If you would like get your hands dirty, there are things for you to do: + +

          + +
        • report bugs and help in their resolution
        • + +
        • send new code or patches (but please make sure that new code does not + break things that work)
        • + +
        • write more documentation, more examples and demos
        • + +

        + +If you would like to join the Spyce project as a developer, documenter, +publicist, or in some other capacity, just fire an email.

        + +Spyce evangelism +

        + +Making Spyce more popular means more support from the user community, more bug +reports, more ideas, and more people. All of this can boost Spyce development +and bug fixing activities, improve the quality of support provided by the +community in mailing lists, etc. Here are some evangelism ideas: + +

          + +
        • place links to the Spyce site at your Web pages
        • + +
        • rate Spyce in software directories such as freshmeat.net and + OSDir.com
        • + +
        • recommend Spyce in web forums, mailing lists, etc.
        • + +
        • write Spyce reviews for web sites, printed magazines, etc.
        • + +
        • tell your friends
        • + +

        + +Your help in spreading the word and broadening the community is appreciated! +

        + + +[[include.spyce('/inc/tail.spi')]] diff --git a/spyce-2.1/www/demo-site/docs/myModule.src.html b/spyce-2.1/www/demo-site/docs/myModule.src.html new file mode 100755 index 0000000000000000000000000000000000000000..8fb119335e6e3126f1db125b31071cf30c787bdc --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/myModule.src.html @@ -0,0 +1,226 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/myModule.py +
        + + + +
        +from spyceModule import spyceModule
        +
        +class myModule(spyceModule):
        +  def foo(self):
        +    print 'foo called'
        +
        +
        +
        + +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/myTaglib.src.html b/spyce-2.1/www/demo-site/docs/myTaglib.src.html new file mode 100755 index 0000000000000000000000000000000000000000..f355b3bc6f14c8b99df3ddd591d5520310e752fe --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/myTaglib.src.html @@ -0,0 +1,238 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/myTaglib.py +
        + + + +
        +from spyceTag import spyceTagLibrary, spyceTagPlus
        +
        +class tag_foo(spyceTagPlus):
        +  name = 'foo'
        +  mustend = 1
        +  def syntax(self):
        +    self.syntaxPairOnly()
        +    self.syntaxNonEmpty('val')
        +  def begin(self, val):
        +    self.getOut().write('<font size="%s"><b>' % str(val))
        +  def end(self):
        +    self.getOut().write('</b></font><br>')
        +
        +class myTaglib(spyceTagLibrary):
        +  tags = [
        +    tag_foo, 
        +  ]
        +
        +
        +
        + +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/pool.src.html b/spyce-2.1/www/demo-site/docs/pool.src.html new file mode 100755 index 0000000000000000000000000000000000000000..90fb6656b21fd453dc0ca9ff8723531d8d01f50b --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/pool.src.html @@ -0,0 +1,216 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/pool.spy +
        + +
        [[.import names="pool"]]
        +<html><body>
        +  The pool module supports long-lived server-pooled objects,<br>
        +  useful for database connections, and other variables<br>
        +  that are expensive to compute.<br>
        +  [[\
        +    if 'foo' in pool:
        +      print 'Pooled object foo EXISTS.'
        +    else:
        +      pool['foo'] = 1
        +      print 'Pooled object foo CREATED.'
        +  ]]
        +  <br>
        +  Value: [[=pool['foo'] ]] <p>
        +</body></html>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/programmaticUsage.src.html b/spyce-2.1/www/demo-site/docs/programmaticUsage.src.html new file mode 100755 index 0000000000000000000000000000000000000000..4c67f80ed809bbb1230618ba0fdcb68c69b43224 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/programmaticUsage.src.html @@ -0,0 +1,290 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/programmaticUsage.py +
        + + + +
        +import os
        +import spyce
        +
        +#--------------------------------------------------[ a hardcoded fake request ]
        +class TestRequest(spyce.spyceRequest):
        +    environment = {'QUERY_STRING': '',
        +            'REQUEST_METHOD': 'get'}
        +    headers = {}
        +    
        +    def env(self, name=None):
        +        if not name:
        +            return self.environment
        +        return self.environment[name]
        +    
        +    def getHeader(self, type=None):
        +        return self.headers[type]
        +    
        +    def getServerID(self):
        +        os.getpid()
        +
        +
        +#--------------------------------------------------[ a hardcoded fake response ]
        +class TestResponse(spyce.spyceResponse):
        +    returncode = spyce.spyceResponse.RETURN_OK
        +    out = ''
        +    err = ''
        +    headers = {}
        +    def write(self, s):
        +        self.out = self.out+s
        +    def writeErr(self, s):
        +        self.err = self.err+s
        +    def close(self):
        +        pass
        +    def clear(self):
        +        self.out = ''
        +    def sendHeaders(self):
        +        pass
        +    def clearHeaders(self):
        +        self.headers = {}
        +    def setContentType(self, content_type):
        +        self.headers['content-type'] = content_type
        +    def setReturnCode(self, code):
        +        self.returncode = code
        +    def addHeader(self, type, data, replace=0):
        +        self.headers[type] = data
        +    def flush(self, *args):
        +        pass
        +    def unbuffer(self):
        +        pass
        +
        +
        +#--------------------------------------------------[ invoking Spyce code ]
        +import spyceConfig
        +    
        +req = TestRequest()
        +resp = TestResponse()
        +
        +spyceCode  = 'Hello, the following names are defined: "[[print dir(),]]", and '
        +spyceCode += 'these were the parameters passed: [[print (a, b, c, d)]]\n',
        +
        +spyce.spyceStringHandler(
        +             req,
        +             resp,
        +             spyceCode,
        +             sig='a, b, c="asd", d=None',
        +             args=(1, 'two'),
        +             kwargs={'c': 'aa'},
        +             config=spyceConfig)
        +print resp.err
        +print resp.out
        +
        +
        + +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/redirect.src.html b/spyce-2.1/www/demo-site/docs/redirect.src.html new file mode 100755 index 0000000000000000000000000000000000000000..8d41e75861a4d89f360eb284fc0e37cbf31d7dfa --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/redirect.src.html @@ -0,0 +1,240 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/redirect.spy +
        + +
        [[.import name=redirect]]
        +<html><body>
        +  [[ type = request['type']
        +     url = request['url']
        +     if url and not type: {
        +       ]] 
        +       <font color=red><b>
        +         please select a redirect type
        +       </b></font><br> 
        +       [[
        +     }
        +     if type and url: {
        +       if type=='internal': redirect.internal(url)
        +       if type=='external': redirect.external(url)
        +       if type=='externalRefresh': redirect.externalRefresh(url, 3)
        +       ]] Received POST info: [[=request.post1()]] [[
        +     }
        +  ]]
        +  <form action="[[=request.uri('path')]]" method=post>
        +    Redirection url:
        +    <input type=text name=url value=hello.spy><br>
        +    Redirection type:
        +    <table border=0>
        +      <tr><td>
        +        <input type=radio name=type value=internal>
        +        internal
        +      </td></tr>
        +      <tr><td>
        +        <input type=radio name=type value=external>
        +        external
        +      </td></tr>
        +      <tr><td>
        +        <input type=radio name=type value=externalRefresh>
        +        externalRefresh (3 seconds)
        +      </td></tr>
        +    </table>
        +    <input type=submit value=redirect>
        +  </form>
        +</body></html>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/request.src.html b/spyce-2.1/www/demo-site/docs/request.src.html new file mode 100755 index 0000000000000000000000000000000000000000..3038b993f86dfcd64b408263c5c6ff7850238fea --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/request.src.html @@ -0,0 +1,236 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/request.spy +
        + +
        <html><body>
        +  Using the Spyce request object, we can obtain 
        +  information sent along with the request. The 
        +  table below shows some request methods and their 
        +  return values. Use the form below to post form 
        +  data via GET or POST. <br>
        +  <hr>
        +  [[-- input forms --]]
        +  <form action="[[=request.uri('path')]]" method=get>
        +    get: <input type=text name=name>
        +    <input type=submit value=ok>
        +  </form>
        +  <form action="[[=request.uri('path')]]" method=post>
        +    post: <input type=text name=name>
        +    <input type=submit value=ok>
        +  </form>
        +  <hr>
        +  [[-- tabulate response information --]]
        +  <table border=1>
        +    <tr>
        +      <td><b>Method</b></td>
        +      <td><b>Return value</b></td>
        +    </tr>
        +    [[ for method in ['uri()', 'uri("path")',
        +      'uri("query")', 'method()','query()',
        +      'get()','get1()', 'post()','post1()',
        +      'getHeader()','env()', 'filename()']: { 
        +    ]]
        +      <tr>
        +        <td valign=top>request.[[=method]]</td>
        +        <td>[[=eval('request.%s' % method)]]</td>
        +      </tr>
        +    [[ } ]]
        +  </table>
        +</body></html>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/scheduling.src.html b/spyce-2.1/www/demo-site/docs/scheduling.src.html new file mode 100755 index 0000000000000000000000000000000000000000..ab5dcc7db2b5078626af37215f0cd87b458020b8 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/scheduling.src.html @@ -0,0 +1,229 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/scheduling.py +
        + + + +
        +import spyce, scheduler
        +
        +def delete_unsubmitted():
        +    db = spyce.SPYCE_GLOBALS['dbpool'].connection()
        +    sql = "DELETE FROM alerts WHERE status = 'unsubmitted' AND created < now() - '1 week'::interval"
        +    db.execute(sql)
        +
        +# delete alerts that were created over a week ago but but still not submitted 
        +scheduler.schedule_daily(00, 10, delete_unsubmitted)
        +
        +
        + +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/session2.src.html b/spyce-2.1/www/demo-site/docs/session2.src.html new file mode 100755 index 0000000000000000000000000000000000000000..feab26c84631c98e10756bfc5e0754e889a84968 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/session2.src.html @@ -0,0 +1,209 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/session2.spy +
        + +
        [[.import name=session ]]
        +[[\
        +  session['visited'] = session.get('visited', 0) + 1
        +]]
        +
        +<spy:parent title="Session example" />
        +
        +You visited us [[= session['visited'] ]] times.
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/spylambda.src.html b/spyce-2.1/www/demo-site/docs/spylambda.src.html new file mode 100755 index 0000000000000000000000000000000000000000..2cc1fd509336ed89819d504e99c0f2f3069d177f --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/spylambda.src.html @@ -0,0 +1,234 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/spylambda.spy +
        + +
        [[\
        +  # table template
        +  table = [[spy! title, data: 
        +    <table>
        +      <tr>
        +        [[for cell in title: {]]
        +          <td><b>[[=cell]]</b></td>
        +        [[}]]
        +      </tr>
        +      [[for row in data: {]]
        +        <tr>
        +          [[for cell in row: {]]
        +            <td>[[=cell]]</td>
        +          [[}]]
        +        </tr>
        +      [[}]]
        +    </table> 
        +  ]]
        +
        +  # table information
        +  title = ['Country', 'Size', 'Population', 'GDP per capita']
        +  data = [
        +    [ 'USA', '9,158,960', '280,562,489', '$36,300' ],
        +    [ 'Canada', '9,220,970', '31,902,268', '$27,700' ],
        +    [ 'Mexico', '1,923,040', '103,400,165', '$9,000' ],
        +  ]
        +]]
        +
        +[[-- emit web page --]]
        +<html><body>
        +  [[ table(title, data) ]]
        +</body></html>
        +
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/stdout.src.html b/spyce-2.1/www/demo-site/docs/stdout.src.html new file mode 100755 index 0000000000000000000000000000000000000000..38b61454ae2a8e62a8ca2aa248fb9e54a19cd093 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/stdout.src.html @@ -0,0 +1,212 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/stdout.spy +
        + +
        <html><body>
        +  [[ print '''Using the stdout module redirects 
        +    stdout to the response object, so you can use
        +    <b>print</b>!''']]<br>
        +  redirecting stdout can be used to...
        +  [[stdout.push()]]
        +  [[print 'capture']] out[[='put']]
        +  [[cached = stdout.pop()]]
        +  ... for later: <br>
        +  [[=cached]]
        +</body></html>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/tag.src.html b/spyce-2.1/www/demo-site/docs/tag.src.html new file mode 100755 index 0000000000000000000000000000000000000000..74f619599b24f3360e5c95f39f4911252e4f2992 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/tag.src.html @@ -0,0 +1,207 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/tag.spy +
        + +
        [[.taglib name=myTaglib as=me]]
        +<html><body>
        +[[ for x in range(2, 6):{ ]]
        +  <me:foo val="=x">size [[= x ]]</me:foo>
        +[[ } ]]
        +</body></html>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/tagbold.src.html b/spyce-2.1/www/demo-site/docs/tagbold.src.html new file mode 100755 index 0000000000000000000000000000000000000000..116860316378bd94c748019d7135ab1c74b42eac --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/tagbold.src.html @@ -0,0 +1,205 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/tagbold.spi +
        + +
        [[.begin name=bold buffers=True]]
        +
        +<b>[[=_contents]]</b>
        +
        +[[.end]]
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/transform.src.html b/spyce-2.1/www/demo-site/docs/transform.src.html new file mode 100755 index 0000000000000000000000000000000000000000..a9140c276e90850fc1feee1e9f4af5eea9360fdf --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/transform.src.html @@ -0,0 +1,248 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Examples
        +
        + + + +
        +examples/transform.spy +
        + +
        [[.import name=transform]]
        +[[\
        +def tag(o, tags=[], **kwargs):
        +  import string
        +  pre = string.join(map(lambda x: '<'+x+'>',tags))
        +  tags.reverse()
        +  post = string.join(map(lambda x: '</'+x+'>',tags))
        +  return pre+str(o)+post
        +def bold(o, _tag=tag, **kwargs):
        +  kwargs['tags'] = ['b']
        +  return apply(_tag, (o,), kwargs)
        +def bolditalic(o, _tag=tag, **kwargs):
        +  kwargs['tags'] = ['b','i']
        +  return apply(_tag, (o,), kwargs)
        +myfilter = transform.create(['html_encode', bolditalic])
        +mystring = 'bold and italic unsafe string: < > &'
        +def simpletable(o, **kwargs):
        +  s = '<table border=1>'
        +  for row in o:
        +    s=s+'<tr>'
        +    for cell in row:
        +      s=s+'<td>'+str(cell)+'</td>'
        +    s=s+'</tr>'
        +  s = s+'</table>'
        +  return s
        +]]
        +<html><body>
        +  install an expression filter:<br>
        +  [[transform.expr(['html_encode', tag])]]
        +  1.[[=mystring, tags=['b','i'] ]]
        +  <br>
        +  [[transform.expr(myfilter)]]
        +  2.[[=mystring]]
        +  [[transform.expr()]]
        +  <p>
        +  or use a filter directly:<br>
        +  1.[[=transform.create(['html_encode',tag])(mystring,tags=['b','i'])]]
        +  <br>
        +  2.[[=myfilter(mystring)]]
        +  <p>
        +  Formatting data in a table...<br>
        +  [[=simpletable([ [1,2,3], [4,5,6] ])]]
        +  <p>
        +  Though the transform module is flexible, <br>
        +  most users will probably only install the <br>
        +  <b>html_encode</b> filter.
        +</body></html>
        +
        +
        +
        + + Run this code + +
        +

        Back to List of Examples + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/tutorial.html b/spyce-2.1/www/demo-site/docs/tutorial.html new file mode 100755 index 0000000000000000000000000000000000000000..5537043834f4a7ee9ec5a1f8fac5fa95413d3fef --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/tutorial.html @@ -0,0 +1,262 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +Download
        +
        + + + +

        Requirements

        + +

        +This tutorial assumes that you've installed Spyce as described in "3 steps to Spyce" +on the Spyce home page. + +

        +You will also need Sqlite and pysqlite installed. (The changes needed for +other databases are obvious, but this tutorial will assume Sqlite.) + +

        Creating your project

        + +

        +Start a shell in the directory you wish to contain your wiki application, e.g. +/var/www on Unix or C:\ on Windows. Then run + +

        +spyceProject.py spycewiki
        +
        + +

        +This will create a directory named spycewiki and a skeleton project. (This page describes what is created by spyceProject in more +detail.) + +

        +Start the Spyce standalone server: + +

        +spyceCmd.py -l --conf ~/spycewiki/config.py
        +
        + +

        +Point your browser to http://localhost/8000. You should see a "Welcome" page. +This is served from spycewiki/www/index.spy. Have a look at that file: +so far, there's not much besides a reference to spy:parent. This tag tells +Spyce to take the content of the child page (index.spy) and inject it into +the body of the parent template (/spycewiki/www/parent.spi). There's no +need for separate header and footer fragments in Spyce. Have a look at +parent.spi before moving on; child.title corresponds to the "Welcome" title +in the spy:parent tag, and child._body is where the child contents are injected. + +

        Setting up your database

        + +

        +This wiki will have a very simple data model: a single table called pages. +Run + +

        +sqlite spycewiki/wiki.db
        +
        + +and create the table by pasting in the following: + +
        +CREATE TABLE pages (
        +  name    varchar(64) PRIMARY KEY,
        +  data    text NOT NULL
        +);
        +
        + +

        +Next, edit spycewiki/config.py and change the line db = None to +db = SqlSoup('sqlite:///spycewiki/wiki.db'). + +

        +If you still have the shell open that was running the Spyce server, +note that Spyce recognized that its config file was changed and +restarted itself. You'll never have to manually restart your Spyce +server; it will notice whenever you change anything. (For production +use, you can turn this feature off since it's a slight performance hit.) + + + + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/tutorial.spy b/spyce-2.1/www/demo-site/docs/tutorial.spy new file mode 100755 index 0000000000000000000000000000000000000000..10f5f8612c766962b08c239c7e09ac9682c7d96a --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/tutorial.spy @@ -0,0 +1,77 @@ +[[.import name=include]] +[[ include.spyce('/inc/head.spi', {'pagename': 'Download', 'page': 'get.html'}) ]] + +

        Requirements

        + +

        +This tutorial assumes that you've installed Spyce as described in "3 steps to Spyce" +on the Spyce home page. + +

        +You will also need Sqlite and pysqlite installed. (The changes needed for +other databases are obvious, but this tutorial will assume Sqlite.) + +

        Creating your project

        + +

        +Start a shell in the directory you wish to contain your wiki application, e.g. +/var/www on Unix or C:\ on Windows. Then run + +

        +spyceProject.py spycewiki
        +
        + +

        +This will create a directory named spycewiki and a skeleton project. (This page describes what is created by spyceProject in more +detail.) + +

        +Start the Spyce standalone server: + +

        +spyceCmd.py -l --conf ~/spycewiki/config.py
        +
        + +

        +Point your browser to http://localhost/8000. You should see a "Welcome" page. +This is served from spycewiki/www/index.spy. Have a look at that file: +so far, there's not much besides a reference to spy:parent. This tag tells +Spyce to take the content of the child page (index.spy) and inject it into +the body of the parent template (/spycewiki/www/parent.spi). There's no +need for separate header and footer fragments in Spyce. Have a look at +parent.spi before moving on; child.title corresponds to the "Welcome" title +in the spy:parent tag, and child._body is where the child contents are injected. + +

        Setting up your database

        + +

        +This wiki will have a very simple data model: a single table called pages. +Run + +

        +sqlite spycewiki/wiki.db
        +
        + +and create the table by pasting in the following: + +
        +CREATE TABLE pages (
        +  name    varchar(64) PRIMARY KEY,
        +  data    text NOT NULL
        +);
        +
        + +

        +Next, edit spycewiki/config.py and change the line db = None to +db = SqlSoup('sqlite:///spycewiki/wiki.db'). + +

        +If you still have the shell open that was running the Spyce server, +note that Spyce recognized that its config file was changed and +restarted itself. You'll never have to manually restart your Spyce +server; it will notice whenever you change anything. (For production +use, you can turn this feature off since it's a slight performance hit.) + + + +[[include.spyce('/inc/tail.spi')]] diff --git a/spyce-2.1/www/demo-site/docs/whats-new-2-1.html b/spyce-2.1/www/demo-site/docs/whats-new-2-1.html new file mode 100755 index 0000000000000000000000000000000000000000..d5e2626287e4b624cdabef79e67a615e5c0c9905 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/whats-new-2-1.html @@ -0,0 +1,364 @@ + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +What's new in 2.1
        +
        + + + + + + +New features in Spyce 2.1 include + +
          +
        • Login tags +
        • SQLAlchemy integration +
        • Validation in handlers +
        • More powerful form tags +
        • Improved handler integration +
        + +Let's have a look at these in the context of an example. (You can log in as spyce / spyce.) + +

        + +
        + examples/whatsnew.spy +
        + +
        <spy:parent title="What's new in Spyce 2.1" />
        +
        +<spy:login_required />
        +
        +[[!
        +def list_new(self, api, name):
        +    if api.db.todo_lists.selectfirst_by(name=name):
        +        raise HandlerError('New list', 'a list with that description already exists')
        +    api.db.todo_lists.insert(name=name)
        +    api.db.flush()
        +]]
        +
        +<h2>To-do lists</h2>
        +
        +[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]]
        +<spy:ul data="[L.name for L in lists]" />
        +
        +<h2>New list</h2>
        +<f:form>
        +<f:submit value="New list" handler=self.list_new />:
        +<f:text name=name value="" />
        +</f:form>
        +
        +
        +
        + + Run this code + +
        + +

        Login tags

        + +

        +Login tags allow you to easily require authentication for your pages: + +

        +<spy:login_required />
        +
        + +That's it! + +

        +Login policy (what to allow as valid logins) is configured with a simple function in your Spyce config file. The Spyce login framework will pass the login and password to your function, and your function returns either a login ID, or None for a failed login. Here's what a simple database-backed validator might look: + +

        +def validator(login, password):
        +    user = db.users.selectone_by(name=login, password=password)
        +    if user:
        +        return user.id
        +    return None
        +
        + +

        +You can override the default authentication funtion on a per-page basis; you can also eaily customize the look of the generated login. Logout and (non-required) login tags are also provided. + +

        +Login tags are covered in detail in the core tag library docs. + + +

        SQLAlchemy integration

        + +

        +Besides request and response, Spyce now automatically adds a hook to db in each script's namespace (meaning these references are available without any explicit imports). db is instance of sqlalchemy.ext.SqlSoup, which allows you to easily access your data without SQL or even pre-defined Python classes: + +

        +[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]]
        +
        + +Or with the api prefix in a handler, + +
        +api.db.todo_lists.selectfirst_by(name=name)
        +
        + +

        +Jonathan Ellis blogged about an early version SqlSoup a few months ago; the biggest change since in the final version is Join support. Spyce 2.1 includes up-to-date docs. + + +

        Validation in handlers

        + +
        +raise HandlerError('New list', 'a list with that description already exists')
        +
        + +That's all it takes! Try to create a duplicate list to see how Spyce renders this in a user-friendly way. + +

        +To display multiple errors at once, use a CompoundHandlerError. See the Active Handlers docs for details. + +

        Time for one more example

        + +The last two sections will reference the following example. This code is very similar to the last example, but it illustrates a couple more features: + +

        + +
        + examples/whatsnew2.spy +
        + +
        <spy:parent title="What's new in Spyce 2.1 (part 2)" />
        +
        +[[!
        +def list_select(self, api, items):
        +    self.selected = ', '.join(items)
        +]]
        +
        +[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) ]]
        +
        +<h2>Select lists</h2>
        +<f:form>
        +<f:select name="items:list" multiple="True" data="[(L.name, L.name) for L in lists]" />
        +<f:submit value="Go" handler=self.list_select />
        +</f:form>
        +
        +[[ if hasattr(self, 'selected'):{ ]]
        +  Selected: [[= self.selected ]]
        +[[ } ]]
        +
        +
        +
        + + Run this code + +
        + +

        More powerful form tags

        + +

        +Spyce 2.1 includes compound form controls, similar to the list control used in the first +example (spy:ul). f:select is one: + +

        +<f:select name="items:list" multiple="True" data="[(L.name, L.name) for L in lists]" />
        +
        + +The other compound form controls are checkboxlist and radiolist. See the docs on the form tag library for details. (And the core tag library for details on the list and table controls.) + +

        There's also an example of all the form tags. + +

        Improved handler integration

        + +Spyce 2.1 allows you to define data types for your form elements by appending :int, :list, :float, or :bool to their name. When you do, Spyce will perform the appropriate casts before passing the data to the handler method, so you don't have to write boilerplate like "items = list(int(i) for i in items)" in your handlers anymore. So in the same line we looked at earlier, + +
        +<f:select name="items:list" multiple="True" data="[(L.name, L.name) for L in lists]" />
        +
        + +

        +since we specify :list in the select's name, Spyce collects all the selected values into the "items" handler parameter. + +

        That's it!

        + +Welcome to Spyce 2.1! Please don't hesitate to drop us a line on the mailing list if you have any questions! + + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/docs/whats-new-2-1.spy b/spyce-2.1/www/demo-site/docs/whats-new-2-1.spy new file mode 100755 index 0000000000000000000000000000000000000000..cf0b301ae942e4202948145de7179c52a7a83728 --- /dev/null +++ b/spyce-2.1/www/demo-site/docs/whats-new-2-1.spy @@ -0,0 +1,115 @@ +[[.import names="include"]] +[[ include.spyce('/inc/head.spi', {'pagename': "What's new in 2.1", 'page': 'whatsnew.html'}) ]] +[[.include file="/inc/static.spi"]] + +New features in Spyce 2.1 include + +

          +
        • Login tags +
        • SQLAlchemy integration +
        • Validation in handlers +
        • More powerful form tags +
        • Improved handler integration +
        + +Let's have a look at these in the context of an example. (You can log in as spyce / spyce.) + +

        +[[ includeCode('examples/whatsnew.spy') ]] + +

        Login tags

        + +

        +Login tags allow you to easily require authentication for your pages: + +

        +<spy:login_required />
        +
        + +That's it! + +

        +Login policy (what to allow as valid logins) is configured with a simple function in your Spyce config file. The Spyce login framework will pass the login and password to your function, and your function returns either a login ID, or None for a failed login. Here's what a simple database-backed validator might look: + +

        +def validator(login, password):
        +    user = db.users.selectone_by(name=login, password=password)
        +    if user:
        +        return user.id
        +    return None
        +
        + +

        +You can override the default authentication funtion on a per-page basis; you can also eaily customize the look of the generated login. Logout and (non-required) login tags are also provided. + +

        +Login tags are covered in detail in the core tag library docs. + + +

        SQLAlchemy integration

        + +

        +Besides request and response, Spyce now automatically adds a hook to db in each script's namespace (meaning these references are available without any explicit imports). db is instance of sqlalchemy.ext.SqlSoup, which allows you to easily access your data without SQL or even pre-defined Python classes: + +

        +\[[ lists = db.todo_lists.select(order_by=db.todo_lists.c.name) \]]
        +
        + +Or with the api prefix in a handler, + +
        +api.db.todo_lists.selectfirst_by(name=name)
        +
        + +

        +Jonathan Ellis blogged about an early version SqlSoup a few months ago; the biggest change since in the final version is Join support. Spyce 2.1 includes up-to-date docs. + + +

        Validation in handlers

        + +
        +raise HandlerError('New list', 'a list with that description already exists')
        +
        + +That's all it takes! Try to create a duplicate list to see how Spyce renders this in a user-friendly way. + +

        +To display multiple errors at once, use a CompoundHandlerError. See the Active Handlers docs for details. + +

        Time for one more example

        + +The last two sections will reference the following example. This code is very similar to the last example, but it illustrates a couple more features: + +

        +[[ includeCode('examples/whatsnew2.spy') ]] + +

        More powerful form tags

        + +

        +Spyce 2.1 includes compound form controls, similar to the list control used in the first +example (spy:ul). f:select is one: + +

        +<f:select name="items:list" multiple="True" data="[(L.name, L.name) for L in lists]" />
        +
        + +The other compound form controls are checkboxlist and radiolist. See the docs on the form tag library for details. (And the core tag library for details on the list and table controls.) + +

        There's also an example of all the form tags. + +

        Improved handler integration

        + +Spyce 2.1 allows you to define data types for your form elements by appending :int, :list, :float, or :bool to their name. When you do, Spyce will perform the appropriate casts before passing the data to the handler method, so you don't have to write boilerplate like "items = list(int(i) for i in items)" in your handlers anymore. So in the same line we looked at earlier, + +
        +<f:select name="items:list" multiple="True" data="[(L.name, L.name) for L in lists]" />
        +
        + +

        +since we specify :list in the select's name, Spyce collects all the selected values into the "items" handler parameter. + +

        That's it!

        + +Welcome to Spyce 2.1! Please don't hesitate to drop us a line on the mailing list if you have any questions! + +[[include.spyce('/inc/tail.spi') ]] diff --git a/spyce-2.1/www/demo-site/dump.spy b/spyce-2.1/www/demo-site/dump.spy new file mode 100755 index 0000000000000000000000000000000000000000..b0cdf353a57791b5be356a7324b28e56018fe0d4 --- /dev/null +++ b/spyce-2.1/www/demo-site/dump.spy @@ -0,0 +1,17 @@ +[[ import re ]] +[[.import name=include ]] +[[\ + path = request['path'] + f = open(path) + if path.endswith('.spy') or path.endswith('.spi'): + print "
        %s
        " % include.spycecode(string=f.read()) + elif path.endswith('.py'): + import pp + s = "%s" % pp.prettyhtml(f) + print s + else: + response.setContentType('text/plain') + print f.read() + + f.close() +]] diff --git a/spyce-2.1/www/demo-site/inc/head.spi b/spyce-2.1/www/demo-site/inc/head.spi new file mode 100755 index 0000000000000000000000000000000000000000..b64365682fe09e3217cf9fb0f90fa33fecc485a4 --- /dev/null +++ b/spyce-2.1/www/demo-site/inc/head.spi @@ -0,0 +1,23 @@ +[[.import name=include]] +[[include.spyce('head1.spi', include.context)]] + + + + + +
        + home +     + documentation +     + download +     + + Spyce logo +
        + +

        +[[=include.context['pagename'] ]]
        +
        + + diff --git a/spyce-2.1/www/demo-site/inc/head1.spi b/spyce-2.1/www/demo-site/inc/head1.spi new file mode 100755 index 0000000000000000000000000000000000000000..52512ab5a5288db788b77f11f6a7c7f98b6e1963 --- /dev/null +++ b/spyce-2.1/www/demo-site/inc/head1.spi @@ -0,0 +1,166 @@ + +[[! + try: pre = include.context['prefix']+'/' + except: pre = '/' +]] +[[ pre = spyceImpl.pre ]] + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + [[\ + try: + print '' % include.vars.nexturl + print '' % include.vars.nexturl + except: pass + try: + print '' % include.vars.nexturl + print '' % include.vars.prevurl + print '' % include.vars.prevurl + except: pass + try: + print '' % include.vars.contentsurl + print '' % include.vars.contentsurl + except: pass + ]] + + + + + [[-- close off left "frame" --]] + + +[[-- + +--]] + + +
        + + +
        +spyce +
        + + + + + +
                  diff --git a/spyce-2.1/www/demo-site/inc/static.spi b/spyce-2.1/www/demo-site/inc/static.spi new file mode 100755 index 0000000000000000000000000000000000000000..f90fd2cb0317874b73626246716cd93886e30639 --- /dev/null +++ b/spyce-2.1/www/demo-site/inc/static.spi @@ -0,0 +1,53 @@ +[[\ + import spyce + release = spyce.__release__ + egprefix = request['egprefix'] or '/docs/examples/' +]] + +[[.import name=toc]] + +[[\ +def toc_info(): + L = [] + node = toc.node + while node: + tag, numbering, title = node.data + L.append(title) + node = node.parent + L = L[:-1][::-1] + return ' / '.join(L) +]] + +[[ def includeCode(filename, run=1): { + import pp + import spyceUtil +]] +[[ open('_eginfo.txt', 'a').write('%s:%s:%s:%s\n' % (toc_info(), toc.getTag(), filename, run)) ]] + +
        + [[=filename]] +
        + +[[\ + if filename.endswith('.py'): + s = pp.prettyhtml(open(spyceUtil.url2file(filename, request.filename()))) + else: + s = "
        %s
        " % include.spycecode(filename) +]] + [[=s]] +
        +[[if run==1: {]] +
        + + [[\ + import os + href = '/'.join(os.path.split(filename)) + L = filename.split('examples/') + if len(L) > 1: + href = egprefix + L[1] + ]] + Run this code + +[[ } ]] +
        +[[ } ]] diff --git a/spyce-2.1/www/demo-site/inc/tail.spi b/spyce-2.1/www/demo-site/inc/tail.spi new file mode 100755 index 0000000000000000000000000000000000000000..632dc00c7380868675cea005ae4e8f8dbd648155 --- /dev/null +++ b/spyce-2.1/www/demo-site/inc/tail.spi @@ -0,0 +1,26 @@ +[[import time, spyce]] + +

        +

        transparent

        + + + +
        + Spyce logo +
        + Python Server Pages
        version [[=spyce.__version__]].[[=spyce.__release__]]
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/index.html b/spyce-2.1/www/demo-site/index.html new file mode 100755 index 0000000000000000000000000000000000000000..ec96727d931537eb696e532bcf242ef5ddbec06e --- /dev/null +++ b/spyce-2.1/www/demo-site/index.html @@ -0,0 +1,213 @@ + + + + + + + + + +Spyce - Python Server Pages (PSP) + + + + + + + + + + + + + + + + + + + +
        + + +
        +spyce +
        + + + + + +
                  + + +[[ Spyce ]] + +

        Web development is fun again!

        +

        Spyce will not waste your time with

        +
          +
        • Clunky code reuse constructs +
        • XML, YAML, or a templating language you've never seen before; everything is Python +
        • Manually accessing GET and POST page parameters +
        • Writing boilerplate code for form validation +
        • Editing multiple files for each user-visible page +
        • Writing SQL queries by hand +
        • Rolling your own authentication framework +
        +Spyce takes the drudgery out. All you need is Python to get started! +

        3 steps to Spyce

        +
          +
        • Download (version XXY released on XXZ) +
        • Install: rpm -i, tar -xzf or unzip +
        • python -u installationpath/spyceCmd.py -l +
            +
          • (If installed from RPM: /usr/share/spyce/spyceCmd.py -l) +
          +
        +Congratulations, you're running the built-in Spyce webserver. Go to +http://localhost:8000/index.spy and check it out! +

        What's next?

        + + +

        +


        + + + +
        + Spyce logo +
        + Python Server Pages
        version 2.1.3
        +
        + Spyce Powered + SourceForge Logo + +
        + +
        + + diff --git a/spyce-2.1/www/demo-site/index.spy b/spyce-2.1/www/demo-site/index.spy new file mode 100755 index 0000000000000000000000000000000000000000..3298149fda86757bf46601cf34cfa968ffc806ee --- /dev/null +++ b/spyce-2.1/www/demo-site/index.spy @@ -0,0 +1,101 @@ +[[-- autogenerate docs if necessary --]] +[[\ + import sys, os, os.path, threading + import spyce, spyceUtil, spyceException + + docdir = os.path.join(spyce.getServer().config.SPYCE_HOME, 'www', 'docs') + + from spyceCmd import spyceCmdlineRequest, spyceCmdlineResponse + def one(script): + outfile = script[:-3] + 'html' + output = open(outfile, 'w') + rq = spyceCmdlineRequest(sys.stdin, os.environ, script) + rsp = spyceCmdlineResponse(output, sys.stderr, False) + spyce.spyceFileHandler(rq, rsp, script) + rsp.close() + spyce.SPYCE_GLOBALS.setdefault('_docgenerated', []).append(script) + def docgen(request=request): + L = [os.path.join(docdir, f) for f in os.listdir(docdir) if f.endswith('.spy')] + L.sort() # make sure doc.spy runs before eg.spy + for script in L: + one(script) + del spyce.SPYCE_GLOBALS['_docgenerate'] + one(request.filename()) # do this after deleting the "lock" + del spyce.SPYCE_GLOBALS['_docgenerated'] + + if not os.path.exists(os.path.join(docdir, 'doc.html')) or '_docgenerate' in spyce.SPYCE_GLOBALS: + if not '_docgenerate' in spyce.SPYCE_GLOBALS: + spyce.SPYCE_GLOBALS['_docgenerate'] = 1 + threading.Thread(target=docgen).start() + L = spyce.SPYCE_GLOBALS.get('_docgenerated', []) + s = ''' + + + + Please wait + + + Please wait, autogenerating docs (should only take a couple seconds)... +
          %s
        + + + ''' % ''.join(['
      • ' + script + '' for script in L]) + response.write(s) + raise spyceException.spyceDone() +]] + +[[.compact]] + +[[.import name=include]] +[[.include file="/inc/static.spi"]] +[[include.spyce('inc/head1.spi')]] + + + \[[ Spyce \]] + + +

        Web development is fun again!

        + +

        Spyce will not waste your time with

        + +
          +
        • Clunky code reuse constructs +
        • XML, YAML, or a templating language you've never seen before; everything is Python +
        • Manually accessing GET and POST page parameters +
        • Writing boilerplate code for form validation +
        • Editing multiple files for each user-visible page +
        • Writing SQL queries by hand +
        • Rolling your own authentication framework +
        + +Spyce takes the drudgery out. All you need is Python to get started! + +

        3 steps to Spyce

        +
          +
        • Download (version XXY released on XXZ) +
        • Install: rpm -i, tar -xzf or unzip +
        • python -u installationpath/spyceCmd.py -l +
            +
          • (If installed from RPM: /usr/share/spyce/spyceCmd.py -l) +
          +
        +Congratulations, you're running the built-in Spyce webserver. Go to +http://localhost:8000/index.spy and check it out! + +

        What's next?

        + + + +[[include.spyce('inc/tail.spi') ]] diff --git a/spyce-2.1/www/demo-site/parent.spi b/spyce-2.1/www/demo-site/parent.spi new file mode 100755 index 0000000000000000000000000000000000000000..29073855ef4ecdfdaf1f04b6ef1a16ecef2c8b16 --- /dev/null +++ b/spyce-2.1/www/demo-site/parent.spi @@ -0,0 +1,121 @@ +[[\ + import urllib, os.path + # The child variable is automatically injected into a parent template's context. + # it contains the '_body' key which holds any text generated by the child, + # as well as any other (string) keys passed as attributes to the spy:parent tag. + # These may be accessed as child['foo'] or child.foo. + L = child.get('extracode', []) + L.append(('parent template', request.filename())) + + # check the filesystem to see if we're running with the SF cgi file layout + cwd = os.path.dirname(request.stack()[-1]) + path = os.path.join(cwd, 'dump.spy') + if os.path.exists(path): + sf = False + dump = '/dump.spy' + else: + sf = True + dump = '/cgi-bin/dump.spy' + + # the request stack tracks Spyce code contexts: each time a new context loads, + # its filename is pushed onto the stack. [0] is the original page requested; + # [-1] is the page currently being processed (here, it would be the default + # site template). + page_source = urllib.urlencode([('path', request.stack()[0])]) +]] + + + + [[= child.title ]] + + + + + +

        [[= child.title ]]

        +
        + + [[= child._body ]] + + + + + diff --git a/spyce-2.1/www/demo-site/pp.py b/spyce-2.1/www/demo-site/pp.py new file mode 100755 index 0000000000000000000000000000000000000000..ee2f3fe5061f0e0e053dd5474a26ba8d57e54189 --- /dev/null +++ b/spyce-2.1/www/demo-site/pp.py @@ -0,0 +1,69 @@ +from __future__ import generators +import cgi +import sys +import keyword +import token +import tokenize + +def prettyhtml(fd): + return """ + +
        +%s
        +
        + """ % ''.join(list(prettyprint(fd))) + +def prettyprint(fd): + """Pretty print code into HTML/CSS. + + This returns a generator, which generates tokens to be printed to + some HTML document. You'll need to define a style sheet to get the + colors you like. + + """ + + end = (0, 0) + last = (None,) * 5 + for tok in tokenize.generate_tokens(fd.readline): + start = tok[2] + if start[1] != end[1]: + if start[0] != end[0]: + # What to do here? Punt. + yield '\nprettyprint punting: %s %s\n' % (tok, end) + else: + yield tok[4][end[1]:start[1]] + end = tok[3] + if end[1] == len(tok[4]): + # Prevent punting on newlines + end = (end[0] + 1, 0) + if tok[0] == token.NAME: + if keyword.iskeyword(tok[1]): + style = 'KEYWORD' + elif last[1] in ('class', 'def'): + style = 'FUNCTION' + else: + style = 'NAME' + else: + style = token.tok_name.get(tok[0]) + s = tok[1].expandtabs() + txt = cgi.escape(s) + if style: + last = tok + yield ('%s' + % (style, txt)) + else: + yield s diff --git a/spyce-2.1/www/demo-site/pp.pyc b/spyce-2.1/www/demo-site/pp.pyc new file mode 100755 index 0000000000000000000000000000000000000000..c5fd30205c39342f6782b855b6cd8531f0b82c50 Binary files /dev/null and b/spyce-2.1/www/demo-site/pp.pyc differ diff --git a/spyce-2.1/www/demo-site/spyce.gif b/spyce-2.1/www/demo-site/spyce.gif new file mode 100755 index 0000000000000000000000000000000000000000..220dae4990cebabfef8983643af43221f8d37609 Binary files /dev/null and b/spyce-2.1/www/demo-site/spyce.gif differ diff --git a/spyce-2.1/www/demo-site/spyce12.png b/spyce-2.1/www/demo-site/spyce12.png new file mode 100755 index 0000000000000000000000000000000000000000..4cd91023b147533acee5f7cee108ebcd53bdd62a --- /dev/null +++ b/spyce-2.1/www/demo-site/spyce12.png @@ -0,0 +1,9 @@ + + +404 Not Found + +

        Not Found

        +The requested URL /spyce12.png was not found on this server.

        +


        +
        Apache/1.3.33 Server at spyce.sourceforge.net Port 80
        + diff --git a/spyce-2.1/www/demo-site/spyce16.png b/spyce-2.1/www/demo-site/spyce16.png new file mode 100755 index 0000000000000000000000000000000000000000..f36b7ab116b9513ed2fd90f9eed7eca4d1890045 --- /dev/null +++ b/spyce-2.1/www/demo-site/spyce16.png @@ -0,0 +1,9 @@ + + +404 Not Found + +

        Not Found

        +The requested URL /spyce16.png was not found on this server.

        +


        +
        Apache/1.3.33 Server at spyce.sourceforge.net Port 80
        + diff --git a/spyce-2.1/www/demo-site/spyce2.png b/spyce-2.1/www/demo-site/spyce2.png new file mode 100755 index 0000000000000000000000000000000000000000..c3bd014e2bd24f9d6107b7c902834dc107b9ad48 Binary files /dev/null and b/spyce-2.1/www/demo-site/spyce2.png differ diff --git a/spyce-2.1/www/demo-site/spyce32.png b/spyce-2.1/www/demo-site/spyce32.png new file mode 100755 index 0000000000000000000000000000000000000000..cc1cc70902c9f05fbd18f301ac558030ce306014 --- /dev/null +++ b/spyce-2.1/www/demo-site/spyce32.png @@ -0,0 +1,9 @@ + + +404 Not Found + +

        Not Found

        +The requested URL /spyce32.png was not found on this server.

        +


        +
        Apache/1.3.33 Server at spyce.sourceforge.net Port 80
        + diff --git a/spyce-2.1/www/demo-site/spycefav.ico b/spyce-2.1/www/demo-site/spycefav.ico new file mode 100755 index 0000000000000000000000000000000000000000..7c6c80e2dff4c0dc3fc890d449c05c89a470f700 Binary files /dev/null and b/spyce-2.1/www/demo-site/spycefav.ico differ diff --git a/spyce-2.1/www/demo-site/spycepow.gif b/spyce-2.1/www/demo-site/spycepow.gif new file mode 100755 index 0000000000000000000000000000000000000000..9efdde0e829a4676da9bb41b0a60bf3603fe0f80 Binary files /dev/null and b/spyce-2.1/www/demo-site/spycepow.gif differ diff --git a/spyce-2.1/www/demo-site/spycepow2.gif b/spyce-2.1/www/demo-site/spycepow2.gif new file mode 100755 index 0000000000000000000000000000000000000000..59e610c4badb3ea9d2f353f4b56d8dce73d03348 Binary files /dev/null and b/spyce-2.1/www/demo-site/spycepow2.gif differ diff --git a/spyce-2.1/www/demo-site/spycepow3.gif b/spyce-2.1/www/demo-site/spycepow3.gif new file mode 100755 index 0000000000000000000000000000000000000000..53cc1091c9f117ab5e19f16bb7ba507589838932 Binary files /dev/null and b/spyce-2.1/www/demo-site/spycepow3.gif differ diff --git a/spyce-2.1/www/demo-site/trans1x1.gif b/spyce-2.1/www/demo-site/trans1x1.gif new file mode 100755 index 0000000000000000000000000000000000000000..1d11fa9ada9e93505b3d736acb204083f45d5fbf Binary files /dev/null and b/spyce-2.1/www/demo-site/trans1x1.gif differ diff --git a/spyce-2.1/www/index.spy b/spyce-2.1/www/index.spy new file mode 100755 index 0000000000000000000000000000000000000000..20b98f5318fb2f872dacf24647de1be9d3d788c4 --- /dev/null +++ b/spyce-2.1/www/index.spy @@ -0,0 +1,292 @@ + + + + +Konvertierungsserver + + + + + + + + +
        +

        Konvertierungsserver (PDF)

        +
        +
        +
        +
        +
        + Anleitung:
        +
        Wählen Sie eine Datei zum Konvertieren und ein Ausgabeformat + aus.
        +
        + Nur PDF-Dateien: Bei Bedarf können Sie Kennwörter für + das Öffnen der Datei und zum Ändern der Zugriffsrechte vergeben. + Außerdem lässt sich die Anzahl der zu konvertierenden Seiten bestimmen, + beispielsweise "1-2" für die erste und zweite Seite. Bei anderen + Zielformaten sind diese Angaben ohne Wirkung.
        +
        + Datei auswählen:
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
         
          
        Ausgabeformat:
          
          
        Optionen: 
        Öffnen-Kennwort: + +  
        Rechte-Kennwort:
         
        (erfordert Rechte-Kennwort) 
         
          
        +
        +
        +
        +<%@import name=include %> +<%\ +######## config ############ + +extlist_doc = {'.ods': 'calc_pdf_Export', + '.xls': 'calc_pdf_Export', + '.png2': 'draw_pdf_Export', + '.jpg': 'draw_pdf_Export', + '.ppt': 'impress_pdf_Export', + } +extlist_html = {'.ods': 'HTML (StarCalc)', + '.xls': 'HTML (StarCalc)', + '.png': 'draw_html_Export', + '.jpg': 'draw_html_Export', + '.ppt': 'impress_html_Export', + } + + +####### config end ########### + +useBasicMacro = False + +#Properties für StarOffice-Objekte +def UnoProps(**args): + props = [] + for key in args: + prop = PropertyValue() + prop.Name = key + prop.Value = args[key] + props.append(prop) + return tuple(props) + +# Unterprogramm zum Ausführen des Basic-Makros +def executeScript( ctx, script, args ): + masterScriptProvider = ctx.ServiceManager.createInstanceWithContext( + "com.sun.star.script.provider.MasterScriptProviderFactory", ctx ) + scriptProvider = masterScriptProvider.createScriptProvider( " " ) + myScript = scriptProvider.getScript( script ) + myScript.invoke(args,(),()) + +# Hauptprogramm +if request.file('userfile')!= None: + import uno, os, string, re, sys + from com.sun.star.beans import PropertyValue + + #temporäre Datei erzeugen + (up_path, up_file) = os.path.split(request.file('userfile').filename) + psep=os.path.sep + #tempfile = open(os.getcwd()+'\\temp\\'+up_file, 'wb') + tempfile = open(os.getcwd()+psep+'temp'+psep+up_file, 'wb') + tempfile.write(request.file('userfile').value) + tempfile.close() + + + #Dateiname für die exportierte Datei + f=request.post('formats') + + ext = f[0].lower() + #url = os.getcwd()+'\\temp\\'+up_file + url = os.getcwd()+psep+'temp'+psep+up_file + url_req = url + "." + ext + url_save = uno.systemPathToFileUrl(url + "." + ext) + (basename, extension) = os.path.splitext(up_file) + extension = extension.lower() + file_save = basename + "." + ext + + + #Dateiformat anhand der Endung ermitteln und passenden Exportfilter wählen + #f=request.post('formats') + if f[0] == "PDF": + if extlist_doc.has_key(extension): + export_format = extlist_doc[extension] + else: + export_format = 'writer_pdf_Export' + if f[0] == "HTML": + if extlist_html.has_key(extension): + export_format = extlist_html[extension] + else: + export_format = 'HTML (StarWriter)' + if f[0] == "DOC": + export_format = 'MS Word 97' + if f[0] == "RTF": + export_format = 'Rich Text Format' + response.write(export_format) + + #Optionen lesen und entsprechende Eigenschaften setzen + properties =[] + + if request.post1('pwdopen')!='': + + p=PropertyValue() + p.Name = "EncryptFile" + p.Value = True + properties.append(p) + + p=PropertyValue() + p.Name="DocumentOpenPassword" + p.Value=request.post1('pwdopen') + properties.append(p) + + useBasicMacro = True + + if request.post1('pwdpermissions')!='': + p=PropertyValue() + p.Name = "RestrictPermissions" + p.Value = True + properties.append(p) + p=PropertyValue() + p.Name = "PermissionPassword" + p.Value = pwdopen=request.post1('pwdpermissions') + properties.append(p) + useBasicMacro = True + if request.post1('DenyPrint') == "yes": + p=PropertyValue() + p.Name = "Printing" + p.Value = 0 + properties.append(p) + + if request.post1('numpages')!='': + p=PropertyValue() + p.Name = "PageRange" + p.Value = request.post1('numpages') + properties.append(p) + useBasicMacro = True + + if request.post1('tagged') == "yes": + p=PropertyValue() + p.Name = "UseTaggedPDF" + p.Value = True + properties.append(p) + useBasicMacro = True + + #Verbindung zu OpenOffice.org herstellen (Port 2002) + context = uno.getComponentContext() + resolver = context.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", context) + ctx = resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext") + + + # Wenn Optionen gesetzt sind, Basic Makro verwenden + if useBasicMacro: + executeScript( ctx, "vnd.sun.star.script:Standard.pdf.ExPortDoc?language=Basic&location=application", (url,url_save,export_format,tuple(properties),)) + #Ohne Optionen, Python verwenden + else: + loadproperties = UnoProps(Hidden=True) + + smgr = ctx.ServiceManager + desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop",ctx) + doc = desktop.loadComponentFromURL('file:///'+url, "_blank", 0, loadproperties) + outproperties = UnoProps(FilterName=export_format, Hidden=True, Overwrite=True) + try: + doc.storeToURL(url_save, outproperties) + except: + print "Fehler beim Schreiben:" + doc.dispose() + + #Content-Type aus der Endung ermitteln + if f[0] == "PDF": + ContentType = 'application/pdf' + if f[0] == "HTML": + ContentType = 'test/html' + if f[0] == "DOC": + ContentType = 'application/msword' + if f[0] == "RTF": + ContentType = 'text/rtf' + + + + #Datei an den Browser senden + response.setContentType(ContentType) + response.addHeader('Content-Disposition', 'attachment; filename="%s"' %file_save, 1) + response.clear() + response.flush() + response.write(include.dump(url_req, 1)) + + # tmp files entfernen + if os.path.exists(url): + os.remove(url) + if os.path.exists(url + "." + ext): + os.remove(url + "." + ext) + + raise spyceDone +%> +

        Suche in ODT-Dateien

        + + + diff --git a/spyce-2.1/www/search.spy b/spyce-2.1/www/search.spy new file mode 100755 index 0000000000000000000000000000000000000000..120b074066be3408d040c83f313e851f49f1e544 --- /dev/null +++ b/spyce-2.1/www/search.spy @@ -0,0 +1,268 @@ + + + + + +ODT-Suche + + + + + + + + + + + +
        +

        ODT-Suche (Textdokumente)

        +
        +
        +
        +
        +
        + Anleitung:
        +
        + Geben Sie den Pfad zu einem Ordner an, in dem die Dateien liegen, die durchsucht werden sollen. Auch Unterverzeichnisse werden berücksichtigt. Die Suche erfolgt nur in *.odt-Dateien. +
        +
        + Die Dokumente werden nach "Suchbegriff" durchsucht. Dabei wird nicht zwischen Groß- und Kleinschreibung unterschieden, wenn ein Häkchen hinter "Groß-/Kleinschreibung ignorieren" gesetzt ist. Das Suchergebnis enthält auch Zeilen, in denen nur ein Teil des Begriffs vorkommt. + +
        +
        + Ordner auswählen:
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + +  
        + + Suchbegriff:
        + +
         
          
          
          
        Optionen: 
         
         
          
        +
        + + + +
        +
        +[[.import name=cookie]] +[[\ + +import unohelper + +from com.sun.star.io import IOException, XOutputStream + +def ODTSearch(userfile, searchterm): + if os.path.isfile(userfile): + (up_path, up_file) = os.path.split(userfile) + url = uno.systemPathToFileUrl(userfile) + url_save = "private:stream" + + # Properties Datei oeffnen + properties = UnoProps(Hidden=True) + + context = uno.getComponentContext() + resolver = context.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", context) + ctx = resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext") + smgr = ctx.ServiceManager + desktop = smgr.createInstanceWithContext("com.sun.star.frame.Desktop",ctx) + #Datei oeffnen + doc = desktop.loadComponentFromURL(url, "_blank", 0, properties) + export_format="Text" + outputprops = UnoProps(FilterName=export_format, OutputStream=OutputStream(), Overwrite=True) + # stdout vorbereiten + old_stdout = sys.stdout + sys.stdout = mystdout = StringIO() + + try: + # Textdatei in einem Stream speichern + doc.storeToURL(url_save, outputprops ) + except: + print ("Fehler beim Schreiben:") + + doc.dispose() + sys.stdout = old_stdout + myTxt = mystdout.getvalue() + out="" + # Text vor und hinter dem Suchbegriff 20 Zeichen + startend=20 + count=0 + start=startend + end=startend + for x in myTxt: + out = out + x + print ('

        Suche in: ' + userfile + '

        ') + # Suche im Text + if ignorecase == 'yes': + dosearch = re.compile(searchterm, re.IGNORECASE) + else: + dosearch = re.compile(searchterm) + + for m in dosearch.finditer(out): + if m.start() > startend: + start=startend + if m.end < len(out)-startend: + end=startend + count += 1 + print ('Gefunden: ' + repr(count) + '
        ') + print (out[m.start()-start:m.end()+end]) + print ('
        ') + else: + print ("Datei nicht gefunden") + +class OutputStream(unohelper.Base, XOutputStream): + def __init__(self): + self.closed = 0 + + def closeOutput(self): + self.closed = 1 + + def writeBytes(self, seq): + try: + sys.stdout.buffer.write(seq.value) + except AttributeError: + sys.stdout.write(seq.value) + + def flush(self): + pass + +def UnoProps(**args): + props = [] + for key in args: + prop = PropertyValue() + prop.Name = key + prop.Value = args[key] + props.append(prop) + return tuple(props) + +# Hauptprogramm + +if request.post1('cookies')!=None: + if request.post1('cookies') == "yes": + cookie.delete('case') + cookie.delete('userdir') + cookie.delete('searchterm') + +if request.post1('userdir')!=None: + import sys + import uno, os, string, re + from cStringIO import StringIO + + from com.sun.star.beans import PropertyValue + from com.sun.star.beans.PropertyState import DIRECT_VALUE + + userdir=request.post1('userdir') + searchterm=request.post1('searchterm') + ignorecase='' + if request.post1('cookies')==None: + cookie.set('userdir',userdir) + cookie.set('searchterm',searchterm) + if request.post1('case') == "yes": + ignorecase='yes' + cookie.set('case','checked') + else: + ignorecase='no' + cookie.delete('case') + # Dateiliste erstellen + for root, dirs, files in os.walk(userdir): + for file in files: + if file.endswith(".odt"): + #Rekursiv durchsuchen + ODTSearch(os.path.join(root, file),searchterm) + +]] +

        Konvertierungsserver (PDF)

        + + + diff --git a/spyce-2.1/www/site.css b/spyce-2.1/www/site.css new file mode 100755 index 0000000000000000000000000000000000000000..17339e7bdb9ff3a5f45d3e1634dae263f33f50e2 --- /dev/null +++ b/spyce-2.1/www/site.css @@ -0,0 +1,62 @@ +/* CSS-Layout */ +#masthead { +} + +#top_nav { +} + +#container { + position: relative; + width: 100%; + height: 320px; +} +#left_col { + padding: 5px 0px 0px 5px; + margin: 0px; + width: 180px; + position: absolute; + left: 0px; + top: 0px; + background-color: #F1F1F1; + height: 310px; +} +#page_content { + padding: 5px 5px 0px 5px; + margin-left: 190px; + position: absolute; + height: 400px; + width: 600px; +} +#footer { + font-family: Verdana, Arial, Helvetica, Sans-Serif; + font-size: 90%; + padding: 5px; + background-color: #FF9966; +} +.header { + background-color: #FF9966; +} +.body { + font-family: Verdana, Arial, Helvetica, Sans-Serif; + font-size: 80%; +} +.style1 { + background-color: #FFFFCC; + vertical-align: middle; + padding: 0 0 0 10px; + margin: 5px; +} +.tab1 { + font-family: Verdana, Arial, Helvetica, Sans-Serif; + font-size: 100%; +} + +.green { +background-color: green; +color:white; +} + +.orange { +background-color: #FF9966; +color:black; +}