myghty-1.1/0000755000175000017500000000000010501065765011677 5ustar malexmalexmyghty-1.1/CHANGES0000644000175000017500000005726010501064125012671 0ustar malexmalex1.1 - added unicode support ([#9]) - changed handling of non-string arguments to m.write and m.apply_escapes. all object, except None, get coerced to a string. (None get converted to the empty string.) - the out_buffer argument of m.create_subrequest now works ([#12]) 1.0.2 - disable_wrapping argument to request will allow a full request to take place without calling any inherited components (inheritance relationship still exists though) - fix to do_remove() on MemoryNamespaceManager 1.0.1 - fixed lexer bug reporting error with closing tag missing - html exception reporting html-escapes error message - session object takes "cookie_expires" - True (end of session), False (never), datetime, or timedelta - fixed closure-with-content bug 1.0 - lots more docstrings, plus a generated pydoc section in the docs. still many more docstrings to go. - m.abort takes optional "reason" parameter along with return code - License changed to MIT License - WSGIHandler no longer url un-escapes PATH_INFO - MODULE:modname:callable syntax can also be called as: @modname:callable. - oops again, its "__all__", not "__ALL__" ! make sure all your code that tries to "from myghty import *" still works. - added more docs for configuration, resolution - added helper functions for writing custom resolvers: 'cache_args' to ModuleComponentSource, default 'file modification time' for FileComponentSource - added module_root_adjust in place of 'adjust' for ResolvePathModule, component_root_adjust in place of 'adjust' for FileResolver. - ResolvePathModule arguments are also global config parameters - argument to path_translate/PathTranslate() can be a callable instead of a list 0.99a - added 'ZBlog' demo, a bigger MVC framework which interfaces to SQLAlchemy as well as an enhanced Myghtyjax (mjax docs forthcoming hopefully) - component-calls-with-content can send named arguments to m.content(), function can get at them via m.content_args[] - fixed InterpreterName not working with ApacheHandler bug - added "deletion check" to synchronizer, dbmcontainer/cache to gracefully re-create the directory if not present, allows straight deletion of cache/lock directories to reset an apps cache without restart - added last modified time to modulecomponents so that they support m.cache_self() and other functions - corrections to module importer to allow accurate "last modified" times; new unit tests contributed by dairiki - fixed AbortRequest exceptions to subclass Error so that they properly return their text representation - HTTPServerHandler runner wasnt sending error headers properly, should be fixed... - added 'require_publish' option, picked up by ResolvePathModule, to enable 'positive publish' checking on functions and methods. looks for 'publish' attribute on the callable - might make a decorator function for this. - added get_method on FunctionComponent. so when you have an implicit module component that is a method on an object, you can call get_method to get another FunctionComponent for another method on the object. - added "path_stringtokens" and "path_moduletokens" arguments to ResolvePathModule. it will use these two lists as "default" tokens to search for if none are found otherwise. - added __delitem__ to util.InheritedDict, allows "del" to be called on request.attributes and component.attributes to remove locally set attributes - bugfix to objgen.py, involving indentation calculation of lines with : #. Which could be: if x==5: # check for x or could be: m.write("font-color: #F0F0F0"); oopsie ! - escapes in substitutions should be compiled in order now, so they execute in the order they were placed in the substitution. - exception HTML page correctly formats spaces in quoted code sections, courtesy Deron Meranda - %closure funcs can now be called as component-calls-with-content 0.99 - added routes resolver, uses Routes for rails-like resolution - added built-in Paste templates for creating sample installations - switched to setuptools to allow Paste entry points plus dependency installation - added display of the wrapped exception name when reporting exceptions - added handling of .pyo files along with .pyc files when python is run with -O flag - changed behavior of resolution Group to maintain url adjustments when transferring from its internal chain of rules to the external chain - fixed import bug in Container which was not allowing "type='dbm'" - added "silent=True" option to resolver NotFound rule, to allow "fall-through" into another framework without dumping tons of errors into the log. propigates into TopLevelNotFound and is picked up by HTTPHandler. 0.98c - <%requestlocal> and <%threadlocal> do not get top-level component arguments in their generated method signatures, so that they can be called off of a <%method> call as well as a top-level component - <%filter> section also gets m, ARGS, plus global arguments in its generated method signature so that they are available, are supplied at runtime - small fix to "magic number" recompiling to work properly with the current file importer, as the filter change has bumped up the "compile id" up to 4 0.98b - fixed non-standard global variables, such as 'r' and user-defined globals, so that they appear by name in <%threadlocal> and <%requestlocal> sections - fixed component dynamic args, which were not working before - fixed component attributes so they inherit an arbitrary number of levels towards the parent, before they were stopping after one level - session fully implements dict interface - added memcached extension module for container, works with cache and session - beefed up use_static_source. all file-stats and reloads are disabled if use_static_source is enabled. templates will not even be re-compiled across restarts unless their corresponding .py files are deleted from the cache directory, although module components get loaded on each server startup. 0.98a - empty component-calls-with-content bug repaired - fixed global_args so that request-level global_args are definitely unique vs. the interpreter-wide global_args, and also against other requests. - global_args default to None for each global, so you can declare just allow_globals and thats all. - fixed small import thing in interp that could happen in certain configs - added __delitem__ to cache so that "del" is supported for cache keys 0.98 - simplified HTTP interface to use get_handler() and handle() functions; WSGI gets application(environ, start_response) callable as well. - custom global_args can now be safely specified at both the interpreter level and the request level simultaneously. - corrected ResolvePathModule to not resolve into modules within modules - docs docs docs 0.98alpha2 - fixed reentrant-related bug in memory caching with m.cache_self() - reorganization of file-based module loading. file-based modules are immediately removed from sys.modules thereby eliminating any need for management upon component deletion. 'delete_modules' option removed. - added <%closure> tag - defines a function in the local scope which is callable like a regular component. supports <%args>, <%init>, <%cleanup> as well. - on-change component reloading added for compiled-in-memory components - fixed the backslash operator for suppressing newlines - SimpleHTTPHandler was url_unescape()ing path_info, removed that. - docs - slowly but surely 0.98alpha - added implicit module components, module components that are created directly from a plain function, callable object, or object method. - old-style ModuleComponents, called 'explicit module components', no longer require you declare the args in the do_component_init method; it will look them up automatically if you dont supply - added module_root/ResolvePathModule which resovles all types of module components via path traversal, in the style of mod_python publisher. - overhauled internal component args handling and added new <%args scope="dynamic"> option. - added options to ResolveModule to support implicit module components - changed shopping cart example to use implicit module components, added commandline runner - redid escapes module to use cgi.escape, urllib.quote_plus with a unicode fix-it step - added 'x' escape flag plus xml_escape to escapes - added myghty.escapes.html_entities_escape - HTTPHandler sets content type text/html before reporting errors - removed unused "feature" that affected urls with : in them - overhaul of docs for config, module components, resolution in progress 0.97b - fixes to module memory management so it works with in-memory components - fix to a potential race condition in module memory management - some cleanup of examples, slight documentation tweaks 0.97a - ApacheHandler imports myghty.resolver symbols to do resolver_strategy in httpd.conf - fixed interp and request handling of MODULE:module:class -style component calls - added formvisitor example - added raise_error config param to directly raise exceptions with no handling - deprecated docroot param in standalone server and integrated plain- document serving with Interpreter directives for more flexible setups - took the plunge; all source code tabs converted to spaces - added a rudimentary not-yet-documented module components arguments thing to the resolver 0.97 - changed error_handler to accept regular m and optional r argument - added error handling for the error_handler itself - request object has resolver instance for lead component attached to it - fixes to WSGIHandler to help demo site run in Paste - demo server uses just one resolver setup 0.97alpha3 - soft send_redirect will parse out query arguments right of the ? and add them to the request_args for the subexec - fixed dir_name bug in resolver where dir_name was blank for a uri with no slashes - added check in component to insure it doesnt locate its parent recursively - added test cases for resolver, component 0.97alpha2 - new resolver implementation, is totally rule-driven and allows total control over the resolution of all uris, including path translation, module components, file components, dhandlers, autohandlers, caching of uris. allows custom rule chains and subchains to be constructed which can be triggered via regular expression or contextual commands - reworked demo runner to illustrate resolver rules - added Ajax toolkit example - rolled out unittest-based test framework, added unit tests for resolver - fixed glitch in syntax highlighter - added error_handler argument to requestimpl to allow custom error handling - added FileContainer implementation, replaces DBMContainer as default for Session - added new interpreter debug option "debug_elements", fine-grained control over debug output - seldom-needed stability enhancements to exception formatting (i.e., when template processing itself is broken for some strange reason, it wont crash) 0.97alpha - code_cache_size is now measured in bytes. the default is the affable 16777216. - when a FileComponent is garbage collected, because it has fallen out of the code_cache, its __del__ method carefully and synchronously checks that it is definitely out of use, then removes its originating module from sys.modules, thereby freeing up the memory it used. - to test this sensitive operation, test/MemUsage.py tester added to illustrate memory usage when the code cache needs to expire many components, all of which might be called up again. - components have the "persistent" flag to specify their module definitely should/should not be deleted from sys.modules, interpreter has the "delete_modules" option (defaults to True) to switch the entire feature off - added namespace_class parameter to session, for providing custom NamespaceManager implementations - added encoding flag, which allows a component to declare its character encoding. translates directly in to the generated python module's encoding comment. - fixed glitch where demo server didnt browse paths correctly on windows platforms - fixed glitch with exception string formatting for exceptions called before interpreter execute 0.96f - fix to session.py so that secret-encoded cookies dont get trampled by other cookies - flags added to containment/sync APIs to not encode filenames - session uses the flag so that session files correspond directly to IDs - auto-id generation added to MemoryComponentSource, id argument optional to interpreter.make_component() again - some session parameters added to shopping cart example for illustration 0.96e - fix to HTTPHandler clone() method to allow configuration args use_session, global_args, output_errors, log_errors to propigate into subrequests 0.96d - interpreter_name param added (MyghtyInterpreterName in mod_python conf), allows HTTPHandler to keep separate instances of interpreter based on name - added path_translate configuration parameter, a list of regexp search and replaces that will be performed on paths before resolving, as well as m.interpreter.resolver.translate_path() for programmatic access - refactoring of HTTPHandler implementations to be more consistent, correctly sends content-type header for error responses - simplifications to demo server making use of new features 0.96c - setup.py script had a path error in it, wasnt installing HTTP modules 0.96b - request_path was broken in non-dhandler classes, was fixed, but also it is now the original request_path in all cases. - added dhandler_path, which is the request_path adjusted to the currently serving dhandler. dhandler_arg is still there which references the same thing. - added root_request_path, root_request request members to reference the root request. parent_request member documented. - WSGIHandler working in a rudimentary fashion. Tested against the PEAK WSGI reference server so far, available in the CVS tree of http://peak.telecommunity.com/. - interp.make_component() requires a component id, as python interpreter needs unique identifiers when creating in-memory components - added python syntax highlighting to documentation and examples 0.96a - fixed issue with source browser 0.96 - session module added, adds session support to all HTTP environments, independent of mod_python - module components - a third form of component which are pure Python modules, run similarly to a servlet with full myghty namespace and request support - most get_XXX methods of Request and Component have been changed to straight attribute access, to make the interface more "Pythonic" and better-performing. Old methods mostly remain for compatibility. - added 'file' data member to Component. returns the filesystem path of the component, if one exists. - an attributes dictionary has been added to Interpreter, which is set via configuration parameters or programmatically. This same hierarchical attribute interface has been added to Request and Component, replacing the notes() and attributes() methods (old accessors still exist). - <%args scope="request"> will refer to the request arguments sent to the topmost request, so that the main request and subrequests all reference the same original request. <%args scope="subrequest"> added to allow access to local subrequest arguments. - indent calculation for % lines overhauled. handles compound statements which can contain any mix of single- line or multi-line statement suites - shopping cart example added, has comprehensive usage of module components, session, introduces form handling and state machine paradigms - "new style classes" largely implemented, many methods of request and component are now properties (but the methods still exist for backwards compatibility) - cache arguments for the get_cache(), cache.get() methods can be specified with or without "cache_" prefix. session arguments also are accessed with our without "session_" prefix. When specified as configuration parameters, the prefixes are required. - http handlers refactored to extend from a common base HTTPHandler. handlers have all been moved into their own "http/" directory, but are still importable via the "myghty" package. - WSGI handler not completed but partially implemented. - standalone HTTP handler building off of python BaseHTTPHandler package added. - demo/docs server added on top of standalone HTTP handler, serves documentation, all examples, full distribution source code browser - ApacheHandler catches IOErrors during exception writing to more cleanly handle dropped client connections - implemented remove() method in NamespaceManager objects, cleans up dbm files or memory - synchronizer cleans out lockfiles when locks fall out of use - more rewriting of documentation, particularly the Installation, Configuration, Inheritance, Request, Components sections. Configuration parameters, data members and methods are cross-referenced via hyperlinks. New sections include Session, Module Components. 0.95c - fixed m.decline(), had a python error - fixed m.dhandler_arg() behavior to work with root dhandlers - fixed overall behavior of decline()/dhandler_arg() to work... correctly ! I think ! - added m.log() method to request, RequestImpl - added "#" comment lines - documentation gen system genericized 0.95b - 'self' variable is available in components - methods of file-based components cleaned up, available in subcomponents as well, also added to documentation. - methods can be programmatically called from the top level Interpreter - return value of a component returned by Interpreter.execute() - compiler upgrades will automatically recompile components with incompatible magic numbers, instead of raising an error 0.95a - m.abort() takes optional HTTP return code argument and returns code via HTTP, else silently aborts - added fetch_all(), caller(), callers(), call_stack(), caller_args() to request - added 'raise_error=True' option to m.fetch_component() - documentation edits, new sections, going going going.... 0.95 - subcomponents and methods are allowed %cleanup sections - "scope" added to %python tag, replacing %init, %cleanup, %global, %threadlocal, %requestlocal tags and their synonyms (though those tags are still available) - "scope" added to %args tag, allowed values None, "component", and "request". "request" brings the original request arguments to the component no matter where it is, "component" are the component specific args - compiler magic number checking added, will force an error if files compiled with an incompatible compiler version are pulled in by interpreter - streamlining of synchronization code, container code, to be clearer, use less memory, take less time - docs docs docs, overhauled, corrected, beefed up, includes new paradigms - ApacheHandler handle() method sends all **params when it first creates its Interpreter object 0.94c/0.94d - container issue with busy lock fixed - container test suite improved to catch aforementioned bug in the future - synchronizer thread strategy glitch fixed with reentrant read lock call - dbm container has optional flag "nonblocking", accessible via cache as "cache_nonblocking", which allows compatibility with gdbm, or any dbm that cant have multiple writers opening the file - dbm container also has "dbmmodule" parameter which is a pointer to the dbm that should be used, defaults to 'anydbm' - request has experimental call_content() method added, allows you to call an embedded content function without it being buffered (you do the push_buffer yourself) 0.94b - bug fixes to utility classes - small adjustments to caching code - small documentation adjustments 0.94a - tweaks to container to elimainte circular references, fixes high-volume operation - refactoring of syncdict - bug fixes to synchronizer - caching properly sets file based caching as default when data_dir is present - cache_self can optionally cache for a component other than the current 0.94 - implemented full cache API - implemented a file (DBM)/memory container API for usage by the cache, and in the future a session object - overhauled synchronization code to allow for containment API's more complex usage patterns - small bug fixes with Lexer, exception reporting 0.93e - fixed major bugs with windows platforms regarding path arithmetic and component loading - bug fixes in new exception reporting, tracking of source lines. added HTML formatting of exception reporting, error lines shown in context, all non-myghty-internal tracelines shown in friendly stack trace - added sha-encoded path schemes to use in creating lockfiles as well as cache files 0.93d - fixed bug in component.py that affected components with both %requestlocal and %threadlocal sections - fixed bug in lexer.py that was returning incorrect source line for syntax errors - fixed bug in lexer.py that was leading to "Lexer Bug?" error messages with component-close tags - added reverse-template-lookup exception reporting for file-compiled components ! 0.93c - %flags section can also be specified as key=value pairs within %method or %def tags - 'trim' flag added with possible values 'left', 'right', 'both': trims whitespace around component output, implies autoflush=False - fixed improper flock() usage that was breaking on solaris platforms - added support for plain directory requests to resolve to dhandlers - refactored file/thread synchronizer objects to use two separate strategies, one for file-only synchronization, the other for thread-only synchronization - continued ruminating on caching architecture 0.93b - documentation worked to be in multiple files - synchronizer library added, applied to component compilation in interp.py to lock multiple forks from overwriting each other - small bugs - interp.make_component(), subcomponent.get_dir_name() methods fixed - apachehandler/cgihandler can log errors either as single line or stack traces to the log file, using new config name log_error, also output of errors to browser can be disabled via config name output_error 0.93a - LRUCache fixed, interpreter properly caches loaded components (was more noticeable with very small cache sizes) - interpreter can log expensive operations to a file with debug_file option - the autoflush %flag in a component file is now inherited from superclass components, if not present locally - request maintains a cache of fetched components to greatly reduce the number of filesystem checks within a request, for a repeatedly referenced component, comopnent.get_parent_component() also uses this service to speed up method searches - request object properly checks if it was called more than once 0.93 - recursive inheritance schemes are trapped and reported - slight presentation glitch with exception reporting in ApacheHandler fixed - component calls were not returning their return value for a dynamic call, which applies to any component with a %threadlocal or %requestlocal section, so thats fixed (requires that you recompile those pages) - added "dynamic template" example that is also illustrated on the site - ApacheHandler uses _content_type_set request member to determine whether or not to set default content type of text/html 0.92a - buffering methodology in request simplified, sped up handling for autoflush->enabled - autoflush = True|False flag added to %flags section for all components, will override parent component's/request's autoflush settings. Particularly useful for filter components. - out_buffer argument can be sent to ApacheHandler.handle() for optional capturing of the top-level apache request - documentation editing 0.92 - %global section can call fetch_component from request instance - %threadlocal and %requestlocal have access to m, ARGS, globals now - m.comp() properly returns the component return value - flags and attr work properly, were stringifying everything before 0.91b - tweaks to util, etc. to handle Python 2.2 - documentation page mostly completed 0.91a - added send_redirect to request, both hard and soft redirects - more documentation (still not done...) - added examples section - CGIHandler and ApacheHandler have greater control over headers - improved subrequests behavior and error handling 0.91 initial release myghty-1.1/doc/0000755000175000017500000000000010501065764012443 5ustar malexmalexmyghty-1.1/doc/components/0000755000175000017500000000000010501065764014630 5ustar malexmalexmyghty-1.1/doc/components/autohandler0000644000175000017500000000073410501064114017051 0ustar malexmalex<%flags>inherit =None % m.set_output_encoding('latin1', 'htmlentityreplace') <& REQUEST:title &> % m.call_next()
<%method title> Welcome to Myghty! myghty-1.1/doc/components/configuration.myt0000644000175000017500000004351110501064114020222 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="configuration", description="Configuration", &>

Below are configuration instructions for the three most basic types of configuration: the standalone handler, a CGI handler, and running under mod_python. For further information on configuration, see:

  • How-to's_Configuration - User-contributed configuration information at www.myghty.org, including:
  • <&formatting.myt:link, path="parameters" &> - full index of configuration parameters
  • <&formatting.myt:link, path="configuration_programmatic" &> - writing Python modules that interface to Myghty in various ways
  • <&formatting.myt:link, path="resolver" &> - developing custom rulesets to match URIs to components
  • <&formatting.myt:link, path="caching_options" &> - configuring component-caching support
  • <&formatting.myt:link, path="session_options" &> - configuring session-storage support

<&|doclib.myt:item, name="standalone", description="Standalone Handler", &>

The standalone handler is a web server built off of Python's BaseHTTPServer class, and is great for local workstation development and testing. Here is a quick example to simply serve pages from the current directory - place the following code into a file run_server.py:

<&|formatting.myt:code, syntaxtype="python"&> #!/usr/local/bin/python import myghty.http.HTTPServerHandler as HTTPServerHandler # create the server. httpd = HTTPServerHandler.HTTPServer( # port number to listen on. port = 8080, # HSHandler list. indicates paths where the file should be interpreted # as a Myghty component request. # Format is a list of dictionaries, each maps a regular expression matching a URI # to an instance of HTTPServerHandler.HSHandler, containing the arguments for # a Myghty interprter. for HTTP requests of type "text/*", this list is matched # first against the incoming request URI. handlers = [ {'.*\.myt' : HTTPServerHandler.HSHandler(data_dir = './cache', component_root = './')}, ], # document root list. indicates paths where the file should be served # like a normal file, with the appropriate MIME type set as the content-type. # These are served for all non "text/*" HTTP requests, and all # incoming URIs that do not match the list of handlers above. docroot = [{'.*' : './'}], ) # run the server httpd.serve_forever()

Then, in that same directory, place another file, called index.myt:

<&|formatting.myt:code, syntaxtype="myghty"&> Test Page Welcome to Myghty !

To run the server, type:

<&|formatting.myt:code, syntaxtype=None&> python ./run_server.py

and point a web browser to http://localhost:8080/index.myt. The data directory ./cache will be automatically created to store data files.

Other examples of the Standalone handler are available in examples/shoppingcart/run_cart.py for a straightforward example, or tools/run_docs.py for an advanced example that also makes use of custom resolution strategies.

<&|doclib.myt:item, name="cgi", description="CGI", &>

Serving Myghty template files directly via CGI can be achieved with the <&|formatting.myt:codeline&>myghty.cgi utility located in the <&|formatting.myt:codeline&>/tools directory. This program is a simple interface to the <&|formatting.myt:codeline&>CGIHandler, which converts the "path-info" of the requested URI into a Myghty component call. The script's configuration information is present within the top half of the script itself, to allow the most straightforward configuration, although it could certainly be modified to load configuration information from any other source such as other Python modules or configuration files.

It requires configuration of the component root, data directory, and optionally additional Python libraries to run. Other configuration parameters can be added to the handle() call as well. The cgi handler will handle requests of the form:

<&|formatting.myt:codeline&><% "http:////myghty.cgi/"|h %>.

The script is below. Modify the appropriate lines and copy the myghty.cgi program into your webserver's cgi-bin directory for a basic cgi-based template handler. For usage within custom CGI scripts, see the next section detailing programmatic methods of calling Myghty.

<&|formatting.myt:code, syntaxtype="python" &> #!/usr/local/bin/python # myghty cgi runner. place in cgi-bin directory and address Myghty templates # with URLs in the form: # http://mysite.com/cgi-bin/myghty.cgi/path/to/template.myt # component root. this is where the Myghty templates are. component_root = '/path/to/croot' # data directory. this is where Myghty puts its object files. data_dir = '/path/to/datadir' # libraries. Put paths to additional custom Python libraries here. lib = ['/path/to/custom/libraries'] import sys [sys.path.append(path) for path in lib] import myghty.http.CGIHandler as handler handler.handle(component_root=component_root, data_dir=data_dir) <&|doclib.myt:item, name="mod_python", description="mod_python", &>

This section assumes familiarity with basic Apache configuration. Myghty includes a handler known as myghty.http.ApacheHandler which can interpret requests from the mod_python request object. This handler can be configured directly within the httpd.conf file of Apache, using regular Apache configuration file directives to configure its options. Alternatively, the ApacheHandler can be used within an external Python module that defines its own mod_python handler, which allows most of the configuration of the handler to be stated within a Python file instead of the httpd.conf file. The first method, described in this section, is expedient for a straightforward Myghty template service, or a simple view-controller setup. While the full range of configurational options can be present directly in http.conf stated as Apache configuration directives, including Python imports, functions, and datastructures, the syntax of embedding Python into conf directives can quickly become burdensome when configuring an application with a complex resolution stream. Therefore it is recommended that Apache users also become familiar with programmatic configuration, described in the section <&formatting.myt:link, path="configuration_programmatic_httphandler_ApacheHandler"&>.

Myghty configuration parameters are written in the Apache httpd.conf file as "Myghty" plus the parameter name in InitialCaps. The full range of configuration parameters in <&formatting.myt:link, path="params" &> may be used. The values (right-hand side of the configuration directive) are Python evaluable expressions. In the simplest case, this is just a string, which is mostly easily identified as <% 'r""' |h%> so that Apache does not strip the quotes out. Any Python structure can be used as the value, such as a list, dictionary, lambda, etc., as long as the proper Apache conf escaping is used.

Below is a straightforward example that routes all Apache requests for files with the extension ".myt" to the Myghty ApacheHandler:

<&|formatting.myt:code, syntaxtype="conf" &> # associate .myt files with mod_python # mod_python 3.1 uses 'mod_python' AddHandler mod_python .myt # or for mod_python 2.7 use 'python-program' # AddHandler python-program .myt # set the handler called by mod_python to be the Myghty ApacheHandler PythonHandler myghty.ApacheHandler::handle # set python library paths - this line is used to indicate # additional user-defined library paths. this path is not required for the # Myghty templates themselves. PythonPath "sys.path+[r'/path/to/my/libraries']" # set component root. # for this example, an incoming URL of http://mysite.com/files/myfile.myt # will be resolved as: /path/to/htdocs/files/myfile.myt PythonOption MyghtyComponentRoot r"/path/to/htdocs" # data directory where myghty will write its object files, # as well as session, cache, and lock files PythonOption MyghtyDataDir r"/path/to/writeable/data/directory/" # other options - simply write as 'Myghty' with the param name in # InitialCaps format, values should be legal python expressions # watch out for using quotes "" as apache.conf removes them - # use r"value" or '"value"' instead PythonOption Myghty

When this is done, requests to Apache which refer to pages with the extension .myt will be routed to the ApacheHandler, which will resolve the filename into a component which is then executed.

An additional format for the "MyghtyComponentRoot" parameter, a list of multiple paths, can be specified as a list of dictionaries. An example: <&|formatting.myt:code, syntaxtype="conf"&> # Multiple Roots, each has a key to identify it, # and their ordering determines their precedence PythonOption MyghtyComponentRoot <% "\\" %> "[ <% "\\" %> {'components':'/optional/path/to/components'}, <% "\\" %> {'main':'/path/to/htdocs/htdocs'} <% "\\" %> ]"

Keep in mind that the MyghtyComponentRoot parameter (normally called component_root) defines filesystem paths that have no relationship to Apache DocumentRoots. From the perspective of Apache, there is only a single mod_python handler being invoked, and it has no awareness of the component_root. This means that any incoming URL which matches the Myghty handler will be matched to a root in the component_root and served as a Myghty template, effectively allowing "access" to files that are not directly access-controlled by Apache. To restrict direct access to Myghty component files which are meant for internal use, an alternate file extension, such as ".myc" can be used, so that while a Myghty component can internally find those files within the component_root, the Apache server has no knowledge of the .myc extension and they are not served. This also requires that the .myc files are kept in a directory or directories separate from any configured Apache DocuementRoots.

Myghty also has the ability to handle directory requests and requests for nonexistent files via various mechanisms, including <&formatting.myt:link, path="specialtempl_dhandler"&> and <&formatting.myt:link, path="modulecomponents"&>. However, the example above is configured to only respond to URLs with the specific file extension of ".myt". To handle directory requests without a filename being present, or requests for many different file extensions at once, replace the usage of <&|formatting.myt:codeline&>AddHandler with the Apache <&|formatting.myt:codeline&>SetHandler directive combined with the <% "" |h%>, <% "" |h%>, or <% "" | h%> Apache directives. These directives are described in the Apache documentation. For example:

<&|formatting.myt:code, syntaxtype="conf"&><%text> SetHandler mod_python PythonHandler myghty.ApacheHandler::handle PythonPath "sys.path+[r'/path/to/my/libraries']" # module component directive. matches regular expressions to Python classes and functions. PythonOption MyghtyModuleComponents "[ \ {'/store/product/.*':store:ProductHandler},\ {'/store/checkout/.*':store:CheckoutHandler},\ ]" PythonOption MyghtyComponentRoot r"/path/to/htdocs" PythonOption MyghtyDataDir r"/path/to/writeable/data/directory/"

The above MyghtyModuleComponents, the apache version of <&formatting.myt:link, path="parameters", param="module_components"&>, is just one way to serve module components; there is also <&formatting.myt:link, path="parameters", param="module_root"&>, as well as the Routes resolver.

When serving all files within a directory, one should take care that Myghty is not be used to handle binary files, such as images. Also, it might be inappropriate to serve other kinds of text files such as stylesheets (.css files) and javascript files (.js files), even though one could use Myghty templating to serve these. To get around these issues, when straight file-extension matching is not enough, the Myghty and non-Myghty files can be placed in different directories and Apache correspondingly configured to enable Python handling in the Myghty directory only.

<&|doclib.myt:item, name="multiple", description="Advanced mod_python Configuration - Multiple ApacheHandlers", &>

Several Interpreters can be configured with their own set of configuration parameters and executed all within the same Python interpreter instance, through the use of the <&formatting.myt:link, path="parameters", param="interpreter_name"&> configuration parameter. In the example below, a site configures three main dynamic areas: a documentation section, a catalog section, and the default section for all requests that do not correspond to the previous two. It uses three ApacheHandlers each with different component_root configuration parameters, but they share a common data_dir. The handlers are each configured inside a <% "" | h %> directive where they are given a unique name that identifies them upon each request.

<&|formatting.myt:code, syntaxtype="conf"&><%text> # Apache resolves the LocationMatch directives in the order they # appear so the most general directive is last. # note that every path within each "component root" parameter has a unique name, since # all the Myghty interpreters happen to be using the same data directory. # also, the PythonPath is set for each SetHandler. while this should work as a single # configuration for all three sections, in testing it seems to work sporadically, so its # set explicitly for each one. # set Myghty data dir, will be used by all sub-directives PythonOption MyghtyDataDir r"/web/myapp/cache/" # '/docs/', serves product documentation # set myghty handler SetHandler mod_python PythonHandler myghty.ApacheHandler::handle PythonPath "['/foo/bar/lib', '/usr/local/myapp/lib'] + sys.path" # set interpreter name PythonOption MyghtyInterpreterName r"document_interp" # set component root PythonOption MyghtyComponentRoot "[\ {'docs_components' : r'/web/myapp/components'},\ {'docs_htdocs' : r'/web/myapp/documents'},\ ]" # add a translation rule to trim off the /docs/ # when resolving the component PythonOption MyghtyPathTranslate "[\ (r'/docs/(.*)' : r'\1'),\ ]" # '/catalog/', serves a browseable catalog via a module component # users can also log in to this area # set myghty handler SetHandler mod_python PythonHandler myghty.ApacheHandler::handle PythonPath "['/foo/bar/lib', '/usr/local/myapp/lib'] + sys.path" # set interpreter name PythonOption MyghtyInterpreterName r"catalog_interp" # set component root PythonOption MyghtyComponentRoot "[\ {'catalog_components' : r'/web/myapp/catalog/comp'},\ ]" # configure some module components PythonOption MyghtyModuleComponents "[\ {r'/catalog/login' : 'myapp.components:Login'},\ {r'/catalog/.*' : 'myapp.components:Catalog'},\ ]" # default: all other site docs # set myghty handler SetHandler mod_python PythonHandler myghty.ApacheHandler::handle PythonPath "['/foo/bar/lib', '/usr/local/myapp/lib'] + sys.path" PythonOption MyghtyInterpreterName r"default_interp" # set component root PythonOption MyghtyComponentRoot "[\ {'default' : r'/web/myapp/htdocs'},\ ]"

Configuring component roots and path translation based on the incoming URI can also be accomplished within the scope of a single Interpreter by defining a custom set of resolver rules. This technique is described in <& formatting.myt:link, path="resolver" &>.

myghty-1.1/doc/components/doclib.myt0000644000175000017500000001020610501064114016602 0ustar malexmalex <%python scope="global"> import sys, string, re # datastructure that will store the whole contents of the documentation class TOCElement: def __init__(self, filename, name, description, parent = None, ext = None, header = None, last_updated = 0): self.filename = filename self.name = name self.parent = parent self.path = self._create_path() self.header = header if self.parent is not None: self.root = parent.root self.root.pathlookup[self.path] = self if self.parent.filename != self.filename: self.root.filelookup[self.filename] = self self.isTop = True else: self.root = self self.pathlookup = {} self.pathlookup[''] = self self.filelookup = {} self.filelookup[filename] = self if ext is not None: self.ext = ext else: self.ext = self.root.ext self.last_updated = last_updated self.description = description self.content = None self.previous = None self.next = None self.children = [] if parent: if len(parent.children): self.previous = parent.children[-1] parent.children[-1].next = self parent.children.append(self) if last_updated > parent.last_updated: parent.last_updated = self.last_updated def get_file(self, name): name = re.sub("\.\w+$", "", name) return self.root.filelookup[name] def lookup(self, path): return self.root.pathlookup[path] def get_link(self, includefile = True, anchor = True): if includefile: if anchor: return "%s%s#%s" % (self.filename, self.ext, self.path) else: return "%s%s" % (self.filename, self.ext) else: if anchor: return "#" + self.path else: return "" def _create_path(self): elem = self tokens = [] while elem.parent is not None: tokens.insert(0, elem.name) elem = elem.parent path = string.join(tokens, '_') return path <%python scope="request"> current = Value() filename = Value() <%args scope="request"> paged = 'yes' <%python scope="init"> try: a = r isdynamic = True ext = ".myt" except: isdynamic = False ext = ".html" request_comp = m.request_comp() if isdynamic and not m.interpreter.attributes.get('docs_static_cache', False): page_cache = True else: page_cache = False # for dynamic page, cache the output of the final page if page_cache: if m.cache_self(key="doc_%s" % paged, component = request_comp): return list_comp = m.fetch_next() files = request_comp.attributes['files'] title = request_comp.attributes.setdefault('title', "Documentation") version = request_comp.attributes['version'] wrapper = request_comp.attributes['wrapper'] index = request_comp.attributes['index'] onepage = request_comp.attributes['onepage'] def buildtoc(): root = TOCElement("", "root", "root element", ext = ext) current.assign(root) for file in files: filename.assign(file) comp = m.fetch_component(file + ".myt") main = m.scomp(comp) return root if not page_cache: # non-dynamic (i.e. command-line) page, cache the datastructure so successive # pages are fast (disables auto-recompiling) cache = m.get_cache(list_comp) toc = cache.get_value('toc', createfunc = buildtoc) else: toc = buildtoc() last_updated = toc.last_updated m.comp(wrapper, isdynamic=isdynamic, ext = ext, toc = toc, comp = request_comp, onepage = onepage, paged = paged, title = title, version = version, index=index, last_updated = last_updated) <%method title> <% m.request_comp().get_attribute('title', inherit = True) or "Documentation" %> <%method item> <%doc>stores an item in the table of contents <%args> # name should be a URL friendly name used for hyperlinking the section name # description is the heading for the item description escapedesc = False header = None <%python scope="init"> if escapedesc: description = m.apply_escapes(description, ['h']) current(TOCElement(filename(), name, description, current(), header = header, last_updated = m.caller.component_source.last_modified)) current().content = m.content() current(current().parent) <%method current> <%init>return current() myghty-1.1/doc/components/formatting.myt0000644000175000017500000002315310501064114017525 0ustar malexmalex<%doc>formatting.myt - library of HTML formatting functions to operate on a TOCElement tree <%global> import string, re import highlight <%def printtocelement> <%doc>prints a TOCElement as a table of contents item and prints its immediate child items <%args> item includefile bold = False full = False children = True % if children: % <%def printsmtocelem> <%args> item includefile children = False <%method printtoc> <%args> root includefile current = None full = False children = True % header = False % for i in root.children: % if i.header: % if header: % % header = True <% i.header %>
% <& printtocelement, item=i, includefile = includefile, bold = (i == current and includefile), full = full, children=children &> % % if header:
% <%method paramtable> <% m.content() %>
<%method param> <%args> name classname type users = 'all' default = None version = None % if default is None: default = 'None' <&|SELF:fliprow, flip=True &> <% name %> (<% type %>)
% if users is not None: for users: <% users %>
% default: <% default %>
used by: <% classname %> % if version:
since version: <% version %> %
<&|SELF:fliprow, flip=False &>

<% m.content() %>

<%method function_doc> <%args> name = "" alt = None arglist = [] rettype = None <&|SELF:fliprow, flip=True&> <% name %>(<% string.join(map(lambda k: "%s" % k, arglist), ", ")%>) <&|SELF:fliprow, flip=False&>
<% m.content() %> % if alt is not None:

Also called as: <% alt %> %
<%method member_doc> <%args> name = "" type = None <&|SELF:fliprow, flip=True&> <% name %> <&|SELF:fliprow, flip=False&>
<% m.content() %>
<%method fliprow trim="both"> <%args>flip=True <%python> flipper = m.get_attribute("formatflipper") if flipper is None: flipper = Value("light") m.set_attribute("formatflipper", flipper) % if flip: flipper({"light":"dark", "dark": "light"}[flipper()]) <% m.content() %> <%method printitem> <%doc>prints the description and contents of a TOC element and recursively prints its child items <%args> item indentlevel = 0 includefile omitheader = False root = None % if root is None: root = item % if not omitheader: %
% if not omitheader: <% item.description %> %
<%python> regexp = re.compile(r"__FORMAT:LINK{(?:\@path=(.+?))?(?:\@xtra=(.+?))?(?:\@text=(.+?))?(?:\@href=(.+?))?(?:\@class=(.+?))?}") def link(matchobj): path = matchobj.group(1) xtra = matchobj.group(2) text = matchobj.group(3) href = matchobj.group(4) class_ = matchobj.group(5) if class_ is not None: class_ = 'class="%s"' % class_ else: class_ = '' if href: return '%s' % (href, class_, text or href) else: try: element = item.lookup(path) if xtra is not None: return '%s' % (element.get_link(includefile), xtra, class_, text or xtra) else: return '%s' % (element.get_link(includefile), class_, text or element.description) except KeyError: if xtra is not None: return '%s' % (text or xtra) else: return '%s' % text or path re2 = re.compile(r"'''PYESC(.+?)PYESC'''", re.S) content = regexp.sub(link, item.content) content = re2.sub(lambda m: m.group(1), content) #m.write(item.content) m.write(content)
% for i in item.children: <& printitem, item=i, indentlevel=indentlevel + 1, includefile = includefile, root=root &> % % if root is not None and len(item.children) == 0: back to section top % % if indentlevel == 0: % if includefile: <& SELF:pagenav, item=item, includefile=includefile &> % else:
% # %
<%method pagenav> <%args> item includefile
% if not includefile: Top | % % if item.previous is not None: Previous: <& SELF:itemlink, item=item.previous, includefile = includefile &> % # end if % if item.next is not None: % if item.previous is not None: | % # end if Next: <& SELF:itemlink, item=item.next, includefile = includefile &> % # end if
<%method formatplain> <%filter> import re f = re.sub(r'\n[\s\t]*\n[\s\t]*', '

\n

', f, re.S) f = "

" + f + "

" return f <% m.content() | h%> <%method itemlink trim="both"> <%args> item includefile <% item.description %> <%method codeline trim="both"> <% m.content() %> <%method code autoflush=False> <%args> title = None syntaxtype = 'myghty' html_escape = True <%init> def fix_indent(f): f =string.expandtabs(f, 4) g = '' lines = string.split(f, "\n") whitespace = None for line in lines: if whitespace is None: match = re.match(r"^([ ]*).+", line) if match is not None: whitespace = match.group(1) if whitespace is not None: line = re.sub(r"^%s" % whitespace, "", line) if whitespace is not None or re.search(r"\w", line) is not None: g += (line + "\n") return g.rstrip() content = highlight.highlight(fix_indent(m.content()), html_escape = html_escape, syntaxtype = syntaxtype)
% if title is not None:
<% title %>
%
<% content %>
<%method link trim="both"> <%args> path = None param = None method = None member = None text = None href = None class_ = None <%init> if href is None and path is None: path = m.comp('doclib.myt:current').path extra = (param or method or member) __FORMAT:LINK{<% path and "@path=" + path or "" %><% extra and "@xtra=" + extra or "" %><% text and "@text=" + text or "" %><% href and "@href=" + href or "" %><% class_ and "@class=" + class_ or "" %>} <%method popboxlink trim="both"> <%args> name=None show='show' hide='hide' <%init> if name is None: name = m.attributes.setdefault('popbox_name', 0) name += 1 m.attributes['popbox_name'] = name name = "popbox_" + repr(name) javascript:togglePopbox('<% name %>', '<% show %>', '<% hide %>') <%method popbox trim="both"> <%args> name = None class_ = None <%init> if name is None: name = 'popbox_' + repr(m.attributes['popbox_name']) <%method poplink trim="both"> <%args> link='sql' <%init> href = m.scomp('SELF:popboxlink') '''PYESC<& SELF:link, href=href, text=link, class_="codepoplink" &>PYESC''' <%method codepopper trim="both"> '''PYESC<&|SELF:popbox, class_="codepop" &><% m.content() %>PYESC''' <%method poppedcode trim="both"> '''PYESC
<% m.content() %>
PYESC''' myghty-1.1/doc/components/index.myt0000644000175000017500000000005610501064114016457 0ustar malexmalex<%flags>inherit="document_base.myt" myghty-1.1/doc/components/printsection.myt0000644000175000017500000000572510501064114020101 0ustar malexmalex<%args> toc paged comp isdynamic index ext onepage <%python scope="init"> # get the item being requested by this embedded component from the documentation tree try: current = toc.get_file(comp.get_name()) except KeyError: current = None % if paged == 'yes': % if current is None: <& toc, includefile = True, **ARGS &> % else: <& topnav, item=current, **ARGS &>
<& formatting.myt:printitem, item=current, includefile = True, omitheader = True &>
% # if/else % else: <& toc, includefile = False, **ARGS &>
% for i in toc.children:
<& topnav, item=i, **ARGS &>
<& formatting.myt:printitem, item=i, includefile = False, omitheader = True &>
% # for i
% # if/else <%method topnav> <%args> isdynamic paged item index ext onepage % ispaged = (paged =='yes')
<& topnavcontrol, **ARGS &>
<% item.description %>
<& formatting.myt:printtoc, root = item, includefile = False, current = None, full = True &>
<%method topnavcontrol> <%args> isdynamic paged index ext onepage % ispaged = (paged =='yes')
% if ispaged: View: Paged  |  One Page % else: View: Paged  |  One Page %
<%method toc> <%args> toc includefile = True isdynamic paged index ext onepage
<& topnavcontrol, **ARGS &> Table of Contents    (view full table)

<& formatting.myt:printtoc, root = toc, includefile = includefile, current = None, full = False, children=False &>
Table of Contents: Full    (view brief table)

<& formatting.myt:printtoc, root = toc, includefile = includefile, current = None, full = True, children=True &>
myghty-1.1/doc/components/pydoc.myt0000644000175000017500000001063610501064114016473 0ustar malexmalex<%global> import re, types def format_paragraphs(text): return re.sub(r'([\w ])\n([\w ])', r'\1 \2', text or '', re.S) <%method obj_doc> <%args> obj functions = None classes = None <%init> import types isclass = isinstance(obj, types.ClassType) or isinstance(obj, types.TypeType) name= obj.__name__ if not isclass: if hasattr(obj, '__all__'): objects = obj.__all__ sort = True else: objects = obj.__dict__.keys() sort = True if functions is None: functions = [getattr(obj, x, None) for x in objects if getattr(obj,x,None) is not None and (isinstance(getattr(obj,x), types.FunctionType)) and not getattr(obj,x).__name__[0] == '_' ] if sort: functions.sort(lambda a, b: cmp(a.__name__, b.__name__)) if classes is None: classes = [getattr(obj, x, None) for x in objects if getattr(obj,x,None) is not None and (isinstance(getattr(obj,x), types.TypeType) or isinstance(getattr(obj,x), types.ClassType)) and not getattr(obj,x).__name__[0] == '_' ] if sort: classes.sort(lambda a, b: cmp(a.__name__, b.__name__)) else: if functions is None: functions = ( [getattr(obj, x).im_func for x in obj.__dict__.keys() if isinstance(getattr(obj,x), types.MethodType) and (getattr(obj, x).__name__ == '__init__' or not getattr(obj,x).__name__[0] == '_') ] + [(x, getattr(obj, x)) for x in obj.__dict__.keys() if isinstance(getattr(obj,x), property) and not x[0] == '_' ] ) functions.sort(lambda a, b: cmp(getattr(a, '__name__', None) or a[0], getattr(b, '__name__', None) or b[0] )) if classes is None: classes = [] if isclass: description = "Class " + name if hasattr(obj, '__mro__'): description += "(" + obj.__mro__[1].__name__ + ")" else: description = "Module " + name <&|doclib.myt:item, name=obj.__name__, description=description &> <&|formatting.myt:formatplain&><% format_paragraphs(obj.__doc__) %> % if not isclass and len(functions): <&|doclib.myt:item, name="modfunc", description="Module Functions" &> <&|formatting.myt:paramtable&> % for func in functions: <& SELF:function_doc, func=func &> % % else: % if len(functions): <&|formatting.myt:paramtable&> % for func in functions: % if isinstance(func, types.FunctionType): <& SELF:function_doc, func=func &> % elif isinstance(func, tuple): <& SELF:property_doc, name = func[0], prop=func[1] &> % % % % % if len(classes): <&|formatting.myt:paramtable&> % for class_ in classes: <& SELF:obj_doc, obj=class_ &> % % <%method function_doc> <%args>func <%init> import inspect argspec = inspect.getargspec(func) argnames = argspec[0] varargs = argspec[1] varkw = argspec[2] defaults = argspec[3] or () argstrings = [] for i in range(0, len(argnames)): if i >= len(argnames) - len(defaults): argstrings.append("%s=%s" % (argnames[i], repr(defaults[i - (len(argnames) - len(defaults))]))) else: argstrings.append(argnames[i]) if varargs is not None: argstrings.append("*%s" % varargs) if varkw is not None: argstrings.append("**%s" % varkw) <&| formatting.myt:function_doc, name="def " + func.__name__, arglist=argstrings &> <&|formatting.myt:formatplain&><% format_paragraphs(func.__doc__) %> <%method property_doc> <%args> name prop <&| formatting.myt:member_doc, name=name + " = property()" &> <&|formatting.myt:formatplain&><% format_paragraphs(prop.__doc__) %> myghty-1.1/doc/components/section_wrapper.myt0000644000175000017500000000134310501064114020554 0ustar malexmalex<%python scope="global"> import string, re, time <%args> comp toc title version = None last_updated = None index paged onepage isdynamic ext
 
<% title %>
% if version is not None:
Version: <% version %> Last Updated: <% time.strftime('%x %X', time.localtime(last_updated)) %>
%
<& printsection.myt, paged = paged, toc = toc, comp = comp, isdynamic=isdynamic, index=index, ext=ext,onepage=onepage &>
myghty-1.1/doc/content/0000755000175000017500000000000010501065764014115 5ustar malexmalexmyghty-1.1/doc/content/cache.myt0000644000175000017500000003464410501064115015713 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="caching", description="Data Caching"&>

Myghty provides the ability to cache any kind of data, including component output, component return values, and user-defined data structures, for fast re-retrieval. All components, whether file-based or subcomponent, are provided with their own cache namespace, which in turn stores any number of key/value pairs. Included with Myghty are implementations using files, dbm files, direct in-memory dictionaries, and memcached. User-defined implementations are also supported and can be specified at runtime. The particular implementation used can optionally be specified on a per-key basis, so that any single namespace can store individual values across any number of implementations at the same time.

Caching is generally used when there are process-intensive and/or slow operations that must be performed to produce output, and those operations also have little or no dependencies on external arguments, i.e. their value is not likely to change in response to request variables. Examples of things that are good for caching include weather information, stock data, sports scores, news headlines, or anything other kind of data that can be expensive to retrieve and does not need to be updated with real-time frequency.

The front-end to the mechanism is provided by the <&|formatting.myt:codeline&>myghty.cache package, and can be configured and used through global configuration parameters, per-component flags, and a programmatic interface.

<&|doclib.myt:item, name="component", description="Caching a Component's Output"&>

The simplest form of caching is the caching of a component's return value and output content via flags. An example using a subcomponent (subcomponents are explained in <& formatting.myt:link, path="components_subcomponent"&>):

<&|formatting.myt:code&><%text> <%def heavytext> <%flags> use_cache = True cache_type = 'dbm' cache_expiretime = 30 <%init> data = big_long_query() Your big query returned: <% data.get_info() %>

In this example, the component's output text and its return value (if any) will be stored the first time it is called. Any calls to this component within the next 30 seconds will automatically return the cached value, and the %init section and body will not be executed. At the moment 30 seconds have elapsed, the first call to occur within this new period will result in the component executing in full again and recreating its output text and return value. Subsequent calls that occur during this second execution will continue to return the prior value until the new value is complete. Once the new value is complete, it is stored in the cache and the expiration counter begins again, for the next 30 seconds.

Note that the parameter <&|formatting.myt:codeline&>cache_type is set to <&|formatting.myt:codeline&>'dbm', which indicates that dbm-based caching is used. This is the default setting when a data_dir parameter is configured with the Myghty application.

For components that contain a %filter section, the result of filtering is stored in the cache as well. This allows the cache to be useful in limiting the execution of a process-intensive or time-consuming filtering function.

When a component is recompiled, its cache contents are automatically expired, so that the cache can be refreshed with the value returned by the newly modified component. This means it is safe to set a cache setting with no expire time at all for a component whose output never changes, and in fact such a component only executes once per compilation and never at all again, for the life of the cache.

<&|doclib.myt:item, name="interface", description="Programmatic Interface"&>

The traditional caching interface looks like this: <&|formatting.myt:code&><%text> <%init> def create_data(): return get_some_data() cache = m.get_cache() data = cache.get_value('mydata', type='memory', createfunc=create_data, expiretime=60) Where "mydata" is a key that the data will be keyed upon, the type of cache is in memory only, the create_data() function is used to create the initial value of 'mydata' as well as regenerating it when it is expired, and the expire time is 60 seconds.

The creation function argument is optional, and the cache can be populated externally as well: <&|formatting.myt:code&><%text> <%init> cache = m.get_cache() if not cache.has_key('mydata'): cache.set_value('mydata', get_some_data(), expiretime=60) data = cache.get_value('mydata') This is a more familiar way to check a dictionary for a value and set it. However, the previous "creation function" methodology has a significant advantage, in that it allows the cache mechanism to execute the function in the context of a global "busy" lock, which prevents other processes and threads from executing the same function at the same time, and instead forces them to retrieve the previous expired value until the new value is completed. If no previous value exists, they all wait for the single process/thread to create the new value. For a creation function that is slow or uses a lot of resources, this limits those resources to only one concurrent usage at a time, and once a cache value exists, only one request will experience any slowness upon recreation.

To programmatically cache the output text of a component, use the <&|formatting.myt:codeline&>m.cache_self() method on request, which is a reentrant component-calling method: <&|formatting.myt:code&><%text> <%init> if m.cache_self(key="mykey"): return # rest of component For an uncached component, the cache_self method will execute the current component a second time. Upon the second execution, when the cache_self line is encountered the second time, it returns false and allows the component to complete its execution. The return value and output is cached, after being sent through any output filters present. Then returning up to the initial cache_self call, it returns true and delivers the components output and optionally its return value. Filtering is also disabled in the component as it should have already occurred within the original caching step. The process for an already cached component is that it simply returns true and delivers the component output.

To get the component's return value via this method: <&|formatting.myt:code&><%text> <%init> ret = Value() if m.cache_self(key="mykey", retval = ret): return ret() # rest of component return 3 + 5 A value object is used here to pass a return value via a method parameter. The return value is simply the cached return value of the component.

Generally, the %flags method of caching a component's output and return value is a lot easier than the programmatic interface. The main advantage of the programmatic interface is if the actual key is to be programmatically decided based on component arguments it can be figured out at runtime and sent as the "key" argument. This also applies if any of the other properties of the cache are to be determined at run-time rather than compile time.

<&|doclib.myt:item, name="details", description="More on Caching"&>

The cached information may be shared within the scope of one process or across multiple processes. Synchronization mechanisms are used to insure that the regeneration is only called by one thread of one process at a time, returning the expired value to other processes and threads while the regeneration is occuring. This maximizes performance even for a very slow data-regeneration mechanism. In the case of a non-memory-based cache, an external process can also access the same cache.

Note that Myghty only includes thread-scoped synchronization for the Windows platform (contributors feel free to contribute a Win32 file locking scheme). The "file" and "dbm" cache methodologies therefore may not be entirely synchronized across multiple processes on Windows. This only occurs if multiple servers are running against the same cache since Windows doesnt have any forking capability and therefore an Apache server or similar is only using threads.

Caching has an assortment of container methodolgies, such as <&|formatting.myt:codeline&>MemoryContainer and <&|formatting.myt:codeline&>DBMContainer, and provides a base Container class that can be subclassed to add new methodologies. A single component's cache can have containers of any number of different types and configurations.

Caching of the URI resolution step can also be done to improve performance. See <&formatting.myt:link, path="parameters", param="use_static_source"&> for more information on using the URICache.

<&|doclib.myt:item, name="options", description="Cache Options"&>

Caching options are all specified as Myghty configuration parameters in the form <&|formatting.myt:codeline&>cache_XXXX, to identify them as options being sent to the Cache object. When calling the m.get_cache() method, parameters may be specified with or without the cache_ prefix; they are stripped off. While some cache options apply to the Cache object itself, others apply to specific forms of the Container class, the two current classes being <&|formatting.myt:codeline&>MemoryContainer and <&|formatting.myt:codeline&>DBMContainer.

The full list of current options is as follows:

<&|formatting.myt:paramtable&> <&|formatting.myt:param, name="cache_container_class", classname="Cache", type="class object" , users =None&> This is a class object which is expected to be a subclass of <&|formatting.myt:codeline&>myghty.container.Container, which will be used to provide containment services. This option need only be used in the case of a user-defined container class that is not built into the static list of options in the Cache class. To use one of the built in container classes, use <&|formatting.myt:codeline&>cache_type instead. <&|formatting.myt:param, name="cache_data_dir", classname="DBMContainer", type="string", default="same as Interpreter data_dir", users=None &> This is the data directory to be used by the DBMContainer (file-based cache) to store its DBM files as well as its lockfiles. It is set by default to be the same as the data_dir parameter for the Myghty application. As it creates its own subdirectories for its files (as does Interpreter), the files are kept separate from Myghty compiled pages. <&|formatting.myt:param, name="cache_dbm_dir", classname="DBMContainer", type="string", default="cache_data_dir + '/container_dbm'", users=None &> This value indicates the directory in which to create the dbm files used by DBMContainer; if not present, defaults to a subdirectory beneath <&|formatting.myt:codeline&>cache_data_dir. <&|formatting.myt:param, name="cache_dbmmodule", classname="DBMContainer", type="module", default="anydbm", users=None &>

DBMContainer uses dbm files via the Python built-in <&|formatting.myt:codeline&>anydbm module, which searches for a platform specific dbm module to use. Any Python dbm module can be specified here in place of it. To specify this option under mod_python as an Apache configuration directive, use this format:

<&|formatting.myt:code&><%text> PythonOption MyghtyCacheDbmmodule "__import__('gdbm')" <&|formatting.myt:param, name="cache_debug_file", classname="Cache", type="file object", users=None &> If pointing to a Python file object, container operations performed by the caching system will be logged, allowing the viewing of how often data is being refreshed as well as how many concurrent threads and processes are hitting various containers. When running with ApacheHandler or CGIHandler, this parameter can be set to the standard Apache log via the parameter <&|formatting.myt:codeline&>log_cache_operations. <&|formatting.myt:param, name="cache_lock_dir", classname="DBMContainer", type="string", default="cache_data_dir + '/container_dbm_lock'", users=None &> This value indicates the directory in which to create the lock files used by DBMContainer; if not present, defaults to a subdirectory beneath <&|formatting.myt:codeline&>cache_data_dir. <&|formatting.myt:param, name="cache_url", classname="MemcachedNamespaceManager", type="string", default="None", users=None &> The memcached URL to connect to for memcached usage, e.g. "localhost:11211". <&|formatting.myt:param, name="cache_type", classname="Cache", type="module", users =None, default="file or memory" &>

This is the type of container being used. Current options are <&|formatting.myt:codeline&>file, <&|formatting.myt:codeline&>dbm, <&|formatting.myt:codeline&>memory, and <&|formatting.myt:codeline&>ext:memcached.

This option defaults to <&|formatting.myt:codeline&>dbm if a <&|formatting.myt:codeline&>data_dir option is present in the current application, else uses <&|formatting.myt:codeline&>memory.

<&|formatting.myt:param, name="log_cache_operations", classname="ApacheHandler or CGIHandler", type="boolean", default="False", users=None &> Sets the Cache <&|formatting.myt:codeline&>cache_debug_file argument to point to the Apache error log or standard error output. See <&|formatting.myt:codeline&>cache_debug_file. <&|doclib.myt:item, name="types", description="Cache Types"&>
  • 'dbm' - uses the anydbm module combined with cPickle to store data.
  • 'file' - uses the cPickle module combined with regular file access to store data. This method may be faster than 'dbm' if the entire contents of the file are retrieved often, whereas dbm is faster for pulling a single key out of a larger set of data.
  • 'memory' - uses a regular Python dictionary. Speed is the fastest, but the cache is not useable across processes nor is it persistent across server restarts. It also has the highest utilization of RAM.
  • 'ext:memcached' - uses memcached for caching and requires the Python memcached module to be installed.
myghty-1.1/doc/content/components.myt0000644000175000017500000007635110501064115017036 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="components", description="Components", &>

All templates in Myghty are referred to as "Components". Components, while they may look like plain HTML pages with a few lines of Python in them, are actually callable units of code which have their own properties, attributes, input arguments, and return values. One can think of a Component anywhere within the spectrum between "HTML page" and "callable Python object", and use it in either way or a combination of both.

A single template, while it is a component itself, also can contain other components within it, which are referred to as "subcomponents", and a subcategory of subcomponents called "methods". The template's component itself is called a "file-based component", and when it is the main component referenced by a request it is also known as the "top level component".

When a component is instantiated within the runtime environment, it is a Python object with the usual reference to "self" as well as its own attributes and methods, and the body of the component occurs within a function call on that object. All components extend from the base class myghty.component.Component. File-based components extend from myghty.component.FileComponent and subcomponents extend from myghty.component.SubComponent.

Additionally, unique to Myghty is a non-template form of component called a "module component", which is written instead as a regular Python class. These components are described in the next section, <&formatting.myt:link,path="modulecomponents"&>, extending from the class myghty.component.ModuleComponent.

<&|doclib.myt:item, name="example", description="Component Calling Example", &>

The most basic component operation is to have one template call another. In the simplest sense, this is just an HTML include, like a server side include. To have a template "page.myt" call upon another template "header.myt":

<&|formatting.myt:code, title="page.myt"&><%text> <& header.myt &> ... rest of page <&|formatting.myt:code, title="header.myt"&><%text> header

Produces:

<&|formatting.myt:code, syntaxtype="html"&><%text> header ... rest of page <&|doclib.myt:item, name="pathscheme", description="Component Path Scheme", &>

When calling components, the path to the component is given as an environment-specific URI, and not an actual filesystem path. While the path can be specified as absolute, it is still determined as relative to one or more of the configured Myghty component roots. Myghty uses the <&formatting.myt:link, path="parameters", param="component_root"&> parameter as a list of filesystem paths with which to search for the requested component. If a relative path is used, the path is converted to an absolute path based on its relation to the location of the calling component, and then matched up against the list of component roots.

<&|doclib.myt:item, name="args", description="Component Arguments - the <%args> tag", &>

Components, since they are really Python functions, support argument lists. These arguments are supplied to the top-level component from the client request arguments, such as via a POST'ed form. When a component is called from another component, the calling component specifies its own local argument list to be sent to the called component.

A component can access all arguments that were sent to it by locating them in the ARGS dictionary. More commonly, it can specify a list of arguments it would like to receive as local variables via the <% "<%args>" %> tag:

<&|formatting.myt:code&><%text> <%args> username password # sessionid defaults to None sessionid = None <%python scope="init"> if sessionid is None: sessionid = create_sid() if password != get_password(username): m.send_redirect("loginfailed.myt", hard=True)

In the above example, the 'username' and 'password' arguments are required; if they are not present in the argument list sent to this component, an error will be raised. The 'sessionid' argument is given a default value, so it is not required in the argument list.

A component like the one above may be called with a URL such as:

<&|formatting.myt:code, syntaxtype=None&><%text> http://foo.bar.com/login.myt?username=john&password=foo&sessionid=57347438

The values sent in an <% "<%args>" %> section are analgous to a Python function being called with named parameters. The above set of arguments, expressed as a Python function, looks like this:

<&|formatting.myt:code, syntaxtype="python"&><%text> def do_run_component(self, username, password, sessionid = None):

What this implies is that the default value sent for "sessionid" only takes effect within the body of the component. It does not set a default value for "sessionid" anywhere else. Different components could easily set different default values for "sessionid" and they would all take effect if "sessionid" is not present in the argument list.

Components can pass argument lists to other components when called. Below, a component 'showuser.myt' is called by another component 'page.myt':

<&|formatting.myt:code, title="showuser.myt"&><%text> <%args> username email Username: <% username %>
Email: <% email %>
<&|formatting.myt:code, title="page.myt"&><%text> Login information: <& showuser.myt, username='john', email='jsmith@foo.com' &>

The component call tags "<% '<& &>' %>" take a named-parameter list just like a Python function, and can also have as the very last argument dictionary with ** notation just like in regular Python:

<&|formatting.myt:code&><%text> <& comp.myt, arg1=3.7, **ARGS &>

Above, the dictionary ARGS always has a list of all arguments sent to the component, regardless of whether or not they were specified in the <% "<%args>" %> section. This is a way of passing through "anonymous" arguments from a component's caller to its components being called.

<&|doclib.myt:item, name="argscope", description="Argument Scope: component/request/subrequest/dynamic", &>

So far we have talked about "component-scoped" arguments, that is, the list of arguments that are sent directly to a component by its caller. While the very first component called in a request (i.e. the top-level component) will receive the client request arguments as its component-scoped arguments, any further component calls will only receive the arguments sent by its direct caller.

To make it easier for all components to see the original request arguments, the attribute scope="request" may be specified in any <% "<%args>" %> section to indicate that arguments should be taken from the original request, and not the immediate caller. Normally, the default value of scope is component, indicating that only the immediate component arguments should be used.

<&|formatting.myt:code&><%text> <%args scope="request"> sessionid <%args scope="component"> username hello <% username %>, to edit your preferences click here.

Above, the argument "sessionid" must be sent by client request, but the argument "username" must be sent by the calling component. If this component is called as the top-level component, both arguments must be present in the client request.

Request arguments are also always available via the <&formatting.myt:link, path="request_members", member="request_args"&> and <&formatting.myt:link, path="request_members", member="root_request_args"&> members of the request object.

Note that there is special behavior associated with request-scoped arguments when using subrequests (described later in this section). Since a subrequest is a "request within a request", it is not clear whether the "request" arguments should come from the originating request, or the immediate, "child" request. The attribute scope="subrequest" indicates that arguments should be located in the immediate request, whether or not it is a subrequest, in contrast to scope="request" which always refers to the arguments of the ultimate root request. Subrequests are described in the section <&formatting.myt:link, path="components_programmatic_subrequests"&> below.

The component that wants to be flexible about its arguments may also specify its arguments with "dynamic" scope. In dynamic scope, the argument is located within the most local arguments first, and then upwards towards the request until found. The following component will retrieve its arguments locally if present, or if not, from the request, no matter where it is called:

<&|formatting.myt:code&><%text> <%args scope="dynamic"> username email hello <% username %>, your email address is <% email %>. <&|doclib.myt:item, name="subcomponent", description="How to Define a Subcomponent", &>

A subcomponent is a component defined within a larger template, and acts as a callable "subsection" of that page. Subcomponents support almost all the functionality of a file-based component, allowing Python blocks of init, cleanup and component scope, but not global, request or thread scope.

A subcomponent, albeit a nonsensical one, looks like this:

<&|formatting.myt:code&><%text> <%def mycomponent> <%args> arg1 = 'foo' arg2 = 'bar' <%python scope="init"> string = arg1 + " " + arg2 i am mycomponent ! <% string %>

A regular subcomponent like this one is always called by another component, i.e. it can never be called directly from the request as a top-level component. Furthermore, subcomponents defined with the <% "<%def>" %> tag are private to the template file they appear in.

The subcomponent has access to all global variables, such as m and r, but it does not have access to the local variables of its containing component. This would include variables specified in the body of the main component, i.e. in init-, cleanup- and component-scoped <% "<%python>" %> blocks. Variables declared in global-, request- and thread-scoped <% "<%python>" %> blocks are available within subcomponents, subject to the restrictions on variables declared in those blocks. Variables declared within the body of a subcomponent remain local to that subcomponent.

<&|doclib.myt:item, name="calling", description="Calling a Subcomponent", &>

Subcomponents are called in a similar manner as a file-based component:

<&|formatting.myt:code&><%text> welcome to <& title, username='john' &> <%def title> <%args> username bling bling bling <% username %> bling bling bling

Note that a subcomponent can be defined at any point in a template and is still available throughout the entire template.

<&|doclib.myt:item, name="method", description="How to Define a Method", &>

A method is similar to a subcomponent, except its functionality is available outside of the file it appears in, and it can take advantage of inheritance from other template files.

<&|formatting.myt:code&><%text> <%method imamethod> <%args> radius coeffiecient = .5424 <%python scope="init"> foob = call_my_func(radius, coefficient) fractional fizzywhatsle: <% foob %> <&|doclib.myt:item, name="calling", description="Calling a Method", &>

A method can be called from within the file it appears in the same fashion as a subcomponent, i.e. simply by its name, in which case it is located in the same way as a subcomponent. The other format of method calling is <% ":" | h%>, where location specifies what component to initially search for the method in. The location may be specified as the URI of the desired component file, or one of the special keywords SELF, REQUEST, or PARENT.

<&|formatting.myt:code&><%text> # call the method print_date in # component file /lib/functions.myt <& /lib/functions.myt:print_date, date="3/12/2004" &> # call a method in the local template <& tablerow, content="cell content" &> # call a method in the base component, taking # advantage of inheritance <& SELF:printtitle &>

With the compound method calling format, if the method is not located in the specified component, the component's inherited parent will be searched, and in turn that component's inherited parent, etc., until no more parents exist. The parent-child relationship of components, as well as the keywords SELF, REQUEST, and PARENT are described in the section <&formatting.myt:link, path="inheritance"&>.

<&|doclib.myt:item, name="closure", description="How to Define a Closure", &> Version 0.98 introduces a lightweight alternative to %def and %method called <% "<%closure>" %>. This tag defines a local function definition using Python keyword def, which is callable via the same mechanism as that of subcomponents. A closure is in fact not a component at all and can only be used within the block that it is defined, and also must be defined before it is called. It offers the advantage that it is created within the scope of the current code body, and therefore has access to the same variable namespace: <&|formatting.myt:code&><%text> <%closure format_x> <%args> format = '%d' <% format % x %> % for x in (1,2,3): <& format_x, format='X:%d' &> %

Closures support the <% "<%args>" | h%> tag, as well as <&formatting.myt:link, path="scopedpython_init" &> and <& formatting.myt:link, path="scopedpython_cleanup" &>. Closures can also be nested. They currently do not support the "component call with content" calling style but this may be added in the future.

<&|doclib.myt:item, name="flags", description="Subcomponent Flags", &>

Subcomponents and methods also may specify flags as described in the section <&formatting.myt:link, path="otherblocks_flags"&>. The most useful flags for a subcomponent are the <&|formatting.myt:codeline&>trim and <&|formatting.myt:codeline&>autoflush flags, described in <&formatting.myt:link, path="filtering"&>.

There are two formats for specifing flags in a subcomponent:

<&|formatting.myt:code&><%text> # traditional way <%def buffered> <%flags> autoflush=False this is a buffered component # inline-attribute way <%method hyperlink trim="both"> <%args> link name name <&|doclib.myt:item, name="callwithcontent", description="Component Calls with Content", &>

Subcomponents and methods can also be called with a slightly different syntax, in a way that allows the calling component to specify a contained section of content to be made available to the called component as a Python function. Any subcomponent can query the global m object for a function that executes this content, if it is present. When the function is called, the code sent within the body of the component call with content is executed; this contained code executes within the context and namespace of the calling component.

<&|formatting.myt:code&><%text> <&| printme &> i am content that will be grabbed by PRINTME.

The called component can then reference the contained content like this:

<&|formatting.myt:code&><%text> <%def printme> I am PRINTME, what do you have to say ?
<% m.content() %>

The method <&|formatting.myt:codeline&>m.content() executes the code contained within the <% "<&| &>/ " | h%> tags in its call to "printme", and returns it as a string. A component may query the request object m for the presense of this function via the method <&|formatting.myt:codeline&>m.has_content().

A component call with content is one of the most powerful features of Myghty, allowing the creation of custom tags that can produce conditional sections, iterated sections, content-grabbing, and many other features. It is similar but not quite the same as the template inheritance methodology, in that it allows the body of a component to be distilled into a callable function passed to an enclosing component, but it involves a client explicitly opting to wrap part of itself in the body of another component call, rather than a client implicitly opting to wrap its entire self in the body of another component call.

<&|doclib.myt:item, name="programmatic", description="Calling Components Programmatically", &>

The special component calling tags described above are just one way to call components. They can also be called directly off of the request object m, which is handy both for calling components within %python blocks as well as for capturing either the return value or the resulting content of a subcomponent. Component objects can also be directly referenced and executed via these methods.

The full index of request methods and members can be found in <&formatting.myt:link, path="request" &>.

<&|doclib.myt:item, name="comp", description="m.comp(component, **params)", &>

This method allows you to call a component just like in the regular way except via Python code. The arguments are specified in the same way to the function's argument list. The output will be streamed to the component's output stream just like any other content. If the component specifies a return value, it will be returned. A component, since it is really just a python function, can have a return value simply by using the python statement <&|formatting.myt:codeline&>return. The value of component can be an actual component object obtained via <&|formatting.myt:codeline&>fetch_component(), or a string specifying a filesystem path or local subcomponent or method location.

<&|formatting.myt:code&><%text> <%python> m.comp('/foo/bar.myt', arg1='hi') <&|doclib.myt:item, name="scomp", description="m.scomp(component, **params)", &>

This method is the same as "comp" except the output of the component is captured in a separate buffer and returned as the return value of scomp().

<&|formatting.myt:code&><%text> <%python> component = m.fetch_component('mycomponent') content = m.scomp(component) m.write("
" + content + "
") <&|doclib.myt:item, name="subrequests", description="Subrequests", &>

A subrequest is an additional twist on calling a component, it calls the component with its own request object that is created from the current request. A subrequest is similar to an internal redirect which returns control back to the calling page. The subrequest has its own local argument list as well, separate from the original request arguments.

<&|formatting.myt:code&><%text> <%python> ret = m.subexec('/my/new/component.myt', arg1='hi')

The subexec method takes either a string URI or a component object as its first argument, and the rest of the named parameters are sent as arguments to the component.

Use subrequests to call a component when you want its full inheritance chain, i.e. its autohandlers, to be called as well.

The subexec method is comprised of several more fine-grained methods as illustrated in this example:

<&|formatting.myt:code&><%text> <%python> # get a component to call component = m.fetch_component('mysubreq.myt') # make_subrequest - # first argument is the component or component URI, # following arguments are component arguments subreq = m.make_subrequest(component, arg1 = 'foo') # execute the request. return value is sent ret = subreq.execute()

The make_subrequest method can also be called as create_subrequest, which in addition to supporting component arguments, lets you specify all other request object arguments as well: <&|formatting.myt:code&><%text> <%python> import StringIO # get a component to call component = m.fetch_component('mysubreq.myt') # make a subrequest with our own # output buffer specified buf = StringIO.StringIO() subreq = m.create_subrequest(component, out_buffer = buf, request_args = {'arg1':'foo'}, ) # execute the request. # return value is sent, our own buffer # is populated with the component's # content output ret = subreq.execute() <&|doclib.myt:item, name="callself", description="call_self(buffer, retval)", &>

The "call_self" method is provided so that a component may call itself and receive its own content in a buffer as well as its return value. "call_self" is an exotic way of having a component filter its own results, and is also the underlying method by which component caching operates. Note that for normal filtering of a component's results, Myghty provides a full set of methods described in the section <&formatting.myt:link, path="filtering_filtering"&>, so call_self should not generally be needed.

call_self uses a "reentrant" methodology, like this:

<&|formatting.myt:code&><%text> <%python scope="init"> import StringIO # value object to store the return value ret = Value() # buffer to store the component output buffer = StringIO.StringIO() if m.call_self(buffer, ret): m.write("woop! call self !" + buffer.getvalue() + " and we're done") return ret() # ... rest of component

The initial call to call_self will return True, indicating that the component has been called and its output and return value captured, at which point the component should return. Within the call_self call, the component is executed again; call_self returns False to indicate that the thread of execution is "inside" of the initial call_self and that content is being captured.

It is recommended that call_self be called only in an init-scoped Python block before any other content has been written, else that content will be printed to the output as well as captured.

<&|doclib.myt:item, name="methods", description="Component Methods", &>

Instances of Component objects can be accessed many ways from the request object, including the <&formatting.myt:link, path="request_methods", method="fetch_component"&> method, and the <&formatting.myt:link, path="request_members", method="current_component"&>, <&formatting.myt:link, path="request_members", method="request_component"&>, and <&formatting.myt:link, path="request_members", method="base_component"&> members. The <&formatting.myt:link, path="request_methods", method="fetch_next"&> method returns the next component in an inheritance chain. Finally, <&|formatting.myt:codeline&>self refers to the current component.

The methods available directly from <&|formatting.myt:codeline&>Component are listed below, followed by the members.

<&|formatting.myt:paramtable&> <&|formatting.myt:function_doc, name="call_method", arglist=['methodname', '**params']&>

Calls a method on the current component, and returns its return value. This method is shorthand for locating the current request via <&|formatting.myt:codeline&>request.instance(), locating the method via <&|formatting.myt:codeline&>locate_inherited_method() and calling <&|formatting.myt:codeline&>m.comp().

<&|formatting.myt:function_doc, name="get_flag", arglist=["key", "inherit=False"]&>

Returns the value of a flag for this component. If "inherit" is True, the value of the flag will be searched for in the chain of parent components, if it is not found locally.

<&|formatting.myt:function_doc, name="get_sub_component", arglist=['name']&>

Returns the subcomponent denoted by name. For a subcomponent or method, returns the subcomponent of the owning (file-based) component. Note this is not for methods; for those, use locate_inherited_method.

<&|formatting.myt:function_doc, name="has_filter", arglist=[]&>

Returns true if this component has a filter section, and or a "trim" flag.

<&|formatting.myt:function_doc, name="locate_inherited_method", arglist=['name']&>

Returns the method component associated with name. The method is searched for in the current component and up the inheritance chain. For a subcomponent or method, returns the method of the owning (file-based) component. Note this is not for non-method subcomponents; for those, use get_sub_component(). <&|formatting.myt:function_doc, name="scall_method", arglist=['methodname', '**params']&>

Calls a method on the current component, captures its content and returns its content as a string. This method is shorthand for locating the current request via <&|formatting.myt:codeline&>request.instance(), locating the method via <&|formatting.myt:codeline&>locate_inherited_method() and calling <&|formatting.myt:codeline&>m.scomp().

<&|formatting.myt:function_doc, name="use_auto_flush", arglist=[]&>

Returns True or False if this component requires autoflush be turned on or off for its execution, or returns None if no requirement is set. This method searches within the current components flags, or within parent component flags if not present.

<&|doclib.myt:item, name="members", description="Component Members", &> <&|formatting.myt:paramtable&> <&|formatting.myt:member_doc, name="args"&> A list of argument names, corresponding to variable names in the component-scoped <% "<%args>" %> section, which have a default value specified. <&|formatting.myt:member_doc, name="attr"&> A dictionary of component attributes, corresponding to those set by the <% "<%attr>" %> tag. The attributes can be set at once by assigning a dictionary to this member. However, to set and retrieve individual attributes, it is best to use the special <&formatting.myt:link, member="attributes"&> member which takes advantage of inheritance. <&|formatting.myt:member_doc, name="attributes"&> A dictionary accessor for the <&formatting.myt:link, member="attr"&> dictionary member that locates its members first in the local component attributes, then searches up the inheritance chain for the attribute. <&|formatting.myt:member_doc, name="dir_name"&>

The web-specific directory name where the current component resides, i.e. for this component its "<% self.dir_name %>". For a subcomponent or method, it is the directory name of the owning (file-based) component.

<&|formatting.myt:member_doc, name="file"&>

The actual filesystem location of a component, for a component that was loaded directly from a Myghty template file, else it is None.

<&|formatting.myt:member_doc, name="filter"&>

Reference to the function that will perform filtering on the component. This filter is directly stated in the <% "<%filter>" %> section described in <&formatting.myt:link, path="filtering_filtering_filter"&>.

<&|formatting.myt:member_doc, name="flags"&> Dictionary of flags for this component. Also can be accessed via the <&formatting.myt:link, path="components_programmatic_methods", method="get_flag"&> method. <&|formatting.myt:member_doc, name="id" &>

A unique identifier for the current component, which is comprised of the key name for its component root joined with its web-specific path, i.e. for this component its "<% self.id %>". <&|formatting.myt:member_doc, name="is_file_component", arglist=[]&>

True if this component is a file based component.

<&|formatting.myt:member_doc, name="is_method_component", arglist=[]&>

True if this component is a method component.

<&|formatting.myt:member_doc, name="is_module_component", arglist=[]&>

True if this component is a module component.

<&|formatting.myt:member_doc, name="is_sub_component", arglist=[]&>

True if this component is a subcomponent or method component.

<&|formatting.myt:member_doc, name="name", &>

The filename or name of the current component, i.e. for this component its "<% self.name %>". <&|formatting.myt:member_doc, name="owner"&>

For a subcomponent or method subcomponent, the owning file-based component, otherwise it is self.

<&|formatting.myt:member_doc, name="parent_component"&>

For a component in an inheritance chain (i.e. via %flags or via autohandlers), the inheriting component. For a subcomponent or method, returns the parent of the owning (file-based) component. Inheritance is described in <&formatting.myt:link, path="inheritance"&>.

<&|formatting.myt:member_doc, name="path"&>

The URI corresponding to the component. For this component its "<% self.path %>". For a subcomponent or method, it is the URI of the owning (file-based) component.

<&|formatting.myt:member_doc, name="request_args"&> A list of argument names, corresponding to variable names in the request-scoped <% '<%args scope="request">' %> section, which do have a default value specified. <&|formatting.myt:member_doc, name="required_args"&> A list of argument names, corresponding to variable names in the component-scoped <% '<%args>' %> section, which do not have a default value specified., i.e. they are required component arguments. <&|formatting.myt:member_doc, name="required_request_args"&> A list of argument names, corresponding to variable names in the request-scoped <% '<%args scope="request">' %> section, which do not have a default value specified, i.e. are required request arguments. <&|formatting.myt:member_doc, name="required_subrequest_args"&> A list of argument names, corresponding to variable names in the subrequest-scoped <% '<%args scope="subrequest">' %> section, which do not have a default value specified, i.e. are required subrequest arguments. <&|formatting.myt:member_doc, name="subrequest_args"&> A list of argument names, corresponding to variable names in the subrequest-scoped <% '<%args scope="subrequest">' %> section, which do have a default value specified. myghty-1.1/doc/content/docstrings.myt0000644000175000017500000000125310501064115017015 0ustar malexmalex<%flags>inherit='document_base.myt' <%attr>title='Modules and Classes' <&|doclib.myt:item, name="docstrings", description="Modules and Classes" &> <%init> import myghty.interp as interp import myghty.request as request import myghty.resolver as resolver import myghty.csource as csource import myghty.component as component import myghty.session as session import myghty.container as container <& pydoc.myt:obj_doc, obj=request &> <& pydoc.myt:obj_doc, obj=component &> <& pydoc.myt:obj_doc, obj=interp &> <& pydoc.myt:obj_doc, obj=resolver &> <& pydoc.myt:obj_doc, obj=csource &> <& pydoc.myt:obj_doc, obj=session &> myghty-1.1/doc/content/document_base.myt0000644000175000017500000000132010501064115017441 0ustar malexmalex<%flags>inherit="doclib.myt" <%python scope="global"> files = [ 'whatsitdo', 'installation', 'configuration', 'programmatic', 'embedding', 'scopedpython', 'globals', 'components', 'modulecomponents', 'request', 'otherblocks', 'inheritance', 'specialtempl', 'filtering', 'session', 'cache', 'resolver', 'unicode', 'params', 'technical', 'docstrings' ] <%attr> files=files wrapper='section_wrapper.myt' onepage='documentation' index='index' title='Myghty Documentation' version = '1.1' myghty-1.1/doc/content/embedding.myt0000644000175000017500000002000510501064115016550 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="embedding", description="Embedding Python in HTML", header="The Myghty Environment" &>

A Myghty file usually is laid out as an HTML file, or other text file, containing specialized tags that are recognized by the Myghty page compiler. When compiled, the file is converted into a python module which is then executed as regular python code. A Myghty template in its compiled form is typically referred to as a component. A component can also contain one or more subcomponents and/or methods, which are described further in the section <&formatting.myt:link, path="components", text="Components"&>. Basically when we say "component" we are talking about a self contained unit of Myghty templating code. The term "top level component" refers to the outermost component within a request, which is the page itself being executed. The term "file based component" refers to a component corresponding to the file that it appears in.

<&|doclib.myt:item, name="control", description="Control Lines", &>

Control lines look like this:

<&|formatting.myt:code &> <%text> % for x in range(0, 10): hello!
% # end for

The percent sign must be the very first character on the line, and the rest of the text is interpreted directly as Python code. The whitespace of the line, as well as the presence of a colon at the end of the line, is significant in determining the indentation of subsequent, non-control lines. As in the example above, the "for" statement followed by a colon, sets the indentation to be one level deeper, which causes the html text "hello!" to be iterated within the block created by the Python statement. The block is then closed by placing another control line, containing only whitespace plus an optional comment.

A more involved example:

<&|formatting.myt:code &><%text> % for mood in ['happy', 'happy', 'sad', 'sad', 'happy']: % if mood == 'happy': Hey There ! % else: Buzz Off ! % #if statement % #for loop

Note that the whitespace is not significant in plain HTML or text lines. When this template is converted to Python code, the plain text will be embedded in <&|formatting.myt:codeline&>write statements that are indented properly within the block. Block-closing statements like "else:" and "except:" are also properly interpreted.

<&|doclib.myt:item, name="comment", description="Comment Lines", &>

Note that a blank control line, i.e. starting with '%', is significant in affecting the whitespace indentation level, whether or not it contains a comment character '#' followed by a comment. To do line-based comments without affecting whitespace, use '#' as the very first character on a line:

<&|formatting.myt:code &><%text> % for x in (1,2,3): hi <% x %> # a comment % # a block-closing control line

Comments can also be done with the <% "<%doc>" %> tag described in <&formatting.myt:link, path="otherblocks_doc" &>.

<&|doclib.myt:item, name="substitutions", description="Substitutions", &>

A substitution is an embedded python expression, whos evaluated value will be sent to the output stream of the component via a <&|formatting.myt:codeline&>m.write() statement:

<&|formatting.myt:code &><%TEXT> Three plus five is: <% 3 + 5 %>

produces:

<&|formatting.myt:code &><%TEXT> Three plus five is: 8

Substitutions can also span multiple lines which is handy in conjunction with triple-quoted blocks.

The text of substitutions can also be HTML or URL escaped via the use of flags. For a description of escape flags, see the section <&formatting.myt:link, path="filtering_escaping"&>.

<&|doclib.myt:item, name="blocks", description="Python Blocks", &>

A python block is a block of pure python code:

<&|formatting.myt:code &><%text> <%python> user = usermanager.get_user() m.write("username is %s" % user.get_name())

Code within a %python block is inserted directly into the component's generated Python code, with its whitespace normalized to that of the most recent control line. The %python tags, as well as the code within the tags, can be at any indentation level in relation to the rest of the document, including other %python blocks; it is only necessary that the indentation of the code within the block is internally consistent with itself. See the next section for an example.

There are also variations upon the %python block, where the contained Python code is executed within a specific context of a component's execution, such as initialization, cleanup, or global. These special variations are explained in <&formatting.myt:link, path="scopedpython"&>.

<&|doclib.myt:item, name="whitespace", description="Controlling Whitespace with the Backslash", &>

To allow for finer control of the whitespace inherently generated by multiline HTML or Python control structures, the backslash character "\" can be used at the end of any line to suppress newline generation: <&|formatting.myt:code&><%text> \ % for x in (1,2,3): [<% x %>]\ % Which will produce: <&|formatting.myt:code&><%text> [1][2][3] <&|doclib.myt:item, name="indentation", description="Indentation Behavior", &>

The interaction of control lines, plain text, and python blocks is regulated by the Myghty engine to create an overall indentation scheme that is functional but also allows HTML and python blocks to be laid out with a high degree of freedom. The following example illustrates the indentation behavior:

<&|formatting.myt:code &><%text> <%python> forecasts = { 'monday':'stormy', 'tuesday':'sunny', 'wednesday':'sunny', 'thursday':'humid', 'friday':'tornado' } % for day in ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']: <%python> weather = forecasts[day] if weather == 'tornado': advice = "run for the hills !" elif weather == 'stormy': advice = "bring an umbrella" else: advice = "enjoy the day...." Weather for <% day %>: <% forecasts[day] %> Advice: <% advice %> % # end for

The above block, when compiled, translates into the following Python code, that is then executed to produce the final output:

<&|formatting.myt:code, syntaxtype="python" &><%text> # BEGIN BLOCK body m.write(''' ''') # test.myt Line 1 forecasts = { 'monday':'stormy', 'tuesday':'sunny', 'wednesday':'sunny', 'thursday':'humid', 'friday':'tornado' } # test.myt Line 11 m.write(''' ''') for day in ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']: # test.myt Line 13 m.write(''' ''') # test.myt Line 14 weather = forecasts[day] if weather == 'tornado': advice = "run for the hills !" elif weather == 'stormy': advice = "bring an umbrella" else: advice = "enjoy the day...." # test.myt Line 24 m.write(''' Weather for ''') m.write( day ) m.write(''': ''') m.write( forecasts[day] ) m.write(''' Advice: ''') # test.myt Line 26 m.write( advice ) m.write(''' ''') # test.myt Line 28 # end for # END BLOCK body myghty-1.1/doc/content/filtering.myt0000644000175000017500000004153310501064115016626 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="filtering", description="Filtering and Flushing (and Escaping)", escapedesc=True, header="Additional Features" &>

This section describes some options that can be applied to Myghty components which affect how the request object deals with their output. The request object supports the capturing of component output in a buffer which is flushed upon completion to provide simple and flexible component output behavior, or sending the output directly to the underlying output stream to provide faster perceived response time. This option is known as autoflush. Post-processing functions can be attached to the final output of a component at the top-level or callable component level, allowing user-defined text operations on the output, known as filtering. Autoflushing and filtering have some dependencies on each other, so they are described together.

As a convenient alternative to filtering, common text-escaping functions applicable to a web application environment, plus support for user-defined escape functions, are provided under the functionality of escapes, described at the end of the section.

<&|doclib.myt:item, name="autoflush", description="The Autoflush Option", escapedesc=True &>

The <&|formatting.myt:codeline&>autoflush option controls whether the output of a component is sent to the underlying output stream as it is produced, or if it is first captured in a buffer which is then sent in one chunk at the end of the request's lifecycle. This is significant in a web context, as it affects "perceived performance", which means the response time of an unbuffered page is nearly immediate, even though the time it takes to finish might be the same as that of a buffered page.

While the autoflush option can improve perceived performance, there are also increased complexities with an autoflushed, i.e. unbuffered page. HTTP headers, including hard redirects, must be sent before any content is written, else the response will have already begun and any new headers will simply be displayed with the page's content. A soft redirect, not being dependent on HTTP headers, will still function, but it's output also may be corrupted via any preceding output. Error messages also will appear inline with already existing content rather than on a page of their own.

The option can be set for an entire application, for a set of files or directories via autohandlers/inherited superpages, for an individual page, or even an individual method or subcomponent within a page. Within all of those scopes, the flag can be set at a more specific level and will override the more general level.

With no configuration, the parameter defaults to <&|formatting.myt:codeline&>False, meaning that component output is captured in a buffer, which is flushed at the end of the request or subcomponent's execution. This produces the simplest behavior and is fine for most applications.

But, here you are and you want to turn <&|formatting.myt:codeline&>autoflush on. To set it across an entire application, specify autoflush=True to the Interpreter or HTTPHandler being used. Or to configure via http.conf with mod_python:

<&|formatting.myt:code&> PythonOption MyghtyAutoflush True

When autoflush is true for an entire application, no buffer is placed between the output of a component and the underlying request output stream. The flag can be overridden back to <&|formatting.myt:codeline&>False within directories and pages via the use of the <&|formatting.myt:codeline&>autoflush flag within the %flags section of the page. To set the value of autoflush for a particular directory, place an <&|formatting.myt:codeline&>autohandler file at the base of the directory as such: <&|formatting.myt:code&><%text> <%flags>autoflush=False % m.call_next()

The Myghty request will run the autohandler which then calls the inherited page via the <&|formatting.myt:codeline&>m.call_next() call. The autoflush flag indicates that buffering should be enabled for the execution of this page, overriding the per-application setting of True.

The ultimate autoflush flag that is used for a page is the innermost occuring flag within the templates that comprise the wrapper chain. If no autoflush flag is present, the next enclosing template is used, and if no template contains an autoflush flag, the application setting is used. Even though an autohandler executes before the page that it encloses, the Myghty request figures out what the autoflush behavior should be before executing any components so that it takes the proper effect.

So any page can control its own autoflush behavior as follows:

<&|formatting.myt:code&><%text> <%flags>autoflush=False ... <&|doclib.myt:item, name="subcomponents", description="Setting Autoflush in Subcomponents" &>

Subcomponent or methods can determine their autoflush behavior as follows:

<&|formatting.myt:code&><%text> <%def mycomp> <%flags>autoflush=False ... stuff ...

There is one limitation to the autoflush flag when used in a subcomponent. If autoflush is set to True on a subcomponent, within a request that has autoflush set to False, the component will send its unfiltered data directly to the output stream of its parent; however since the overall request is not autoflushing and is instead capturing its content in a buffer of its own, the content is still stored in a buffer, which does not get sent to the client until the request is complete. Note that this limitation does not exist the other way around; if an overall request is autoflushing, and a subcomponent sets autoflush to False, that subcomponent's output will be buffered, until the subcomponent completes its execution. This is significant for a subcomponent whos output is being processed by a <% "<%filter>" %> section. More on this later.

<&|doclib.myt:item, name="autohandlers", description="Non-Buffered with Autohandlers", escapedesc=True &>

What happens when a page in an autoflush=True environment interacts with one or more autohandlers or inherited superpages? The superpage will execute first, output whatever content it has before it calls the subpage, and then will call <&|formatting.myt:codeline&>m.call_next() to pass control to the subpage. If the subpage then wants to play with HTTP headers and/or perform redirects, it's out of luck:

<&|formatting.myt:code, title="autohandler - autoflush enabled"&><%text> <%flags>autoflush=True % m.call_next() <&|formatting.myt:code, title="page.myt - wants to send a redirect"&><%text> <%python scope="init"> m.send_redirect("page2.myt", hard=True)

The above example will fail! Since the autohandler executes and prints the top HTML tags to the underlying output stream, by the time page.myt tries to send its redirect, the HTTP headers have already been written and you'll basically get a broken HTML page. What to do? Either page.myt needs to specify that it is not an autoflushing page, or it can detach itself from its inherited parent.

Solution One - Turn Autoflush On:

<&|formatting.myt:code, title="page.myt"&><%text> <%flags>autoflush=False <%python scope="init"> m.send_redirect("page2.myt", hard=True)

This is the most general-purpose method, which just disables autoflush for just that one page.

Solution Two - Inherit from None

<&|formatting.myt:code, title="page.myt"&><%text> <%flags>inherit=None <%python scope="init"> m.send_redirect("page2.myt", hard=True)

This method is appropriate for a page that never needs to output any content, i.e. it always will be performing a redirect. The autohandler here is not inherited, so never even gets called. This saves you the processing time of the autohandler setting up buffers and producing content. Of course, if there are autohandlers that are performing operations that this subpage depends upon, then you must be sure to inherit from those pages, possibly through the use of "alternate" autohandlers that inherit only from the desired pages. <&|doclib.myt:item, name="filtering", description="Filtering", escapedesc=True &>

Filtering means that the output stream of a component is sent through a text processing function which modifies the content before it is passed on to the ultimate output stream. Typical usages of text filtering are modifying entity references, trimming whitespace, converting plain-text to HTML, and lots of other things. For the general purpose per-component filter, Myghty supplies the <% "<%filter>" %> tag.

<&|doclib.myt:item, name="filter", description="The <%filter> Tag", escapedesc=True &>

%filter describes a filtering function that is applied to the final output of a component. This is more common on subcomponents or methods but works within the scope of any top-level component as well. The Python code within the %filter section provides the body of the to be used; the heading of the function is generated within the compiled component and not normally seen. The filter function is provided with one argument f which contains the content to be filtered; the function processes content and returns the new value.

Example:

<&|formatting.myt:code&><%text> <%filter> import re return re.sub(r"turkey", "penguin", f) dang those flyin turkeys !

will produce:

<&|formatting.myt:code&> dang those flyin penguins ! <&|doclib.myt:item, name="escapefilter", description="Escaping Content in a Filter Section", escapedesc=True &>

Myghty has some built in escaping functions, described in the next section <&formatting.myt:link, path="filtering_escaping"&>. While these functions are easy enough to use within substitutions or any other Python code, you can of course use them within a filter section as well.

This is a component that HTML escapes its output, i.e. replaces the special characters <% "&, <, and >" | h%> with entity reference encoding:

<&|formatting.myt:code&><%text> <%def bodytext> <%filter> return m.apply_escapes(f, 'h') # ... component output <&|doclib.myt:item, name="filterflush", description="Filtering Behavior with Autoflush Enabled", escapedesc=True &>

The usual case when a %filter section is used is that all <&|formatting.myt:codeline&>m.write() statements, both explicit and implicit, send their content to a buffer. Upon completion of the component's execution, the buffer's content is passed through the function defined in the %filter section. However when autoflush is enabled, the extra buffer is not used and the filtering function is attached directly to each call to <&|formatting.myt:codeline&>m.write(). For regular blocks of HTML, the entire block is sent inside of one big <&|formatting.myt:codeline&>write() statement, but each python substitution or other code call splits it up, resulting in another call to <&|formatting.myt:codeline&>write().

Since a non-autoflushing component is more predictable for filtering purposes than an autoflushing component, it is often useful to have autoflush disabled for a component that uses a %filter section. As stated in the autoflush section, autoflushing behavior can be changed per component, per page or for the whole application, and is the default value as well. There is also another configuration option can be overridden for all filter components by the setting "dont_auto_flush_filters" - see the section <&formatting.myt:link, path="parameters", &> for more information.

<&|doclib.myt:item, name="trim", description="Filtering Whitespace - the Trim Flag", escapedesc=True &>

A common need for components is to trim off the whitespace at the beginning and/or the end of a component output. It is convenient to define subcomponents and methods on multiple lines, but this inherently includes newlines in the output of that component, since Myghty sees the blank lines in the source. But lots of times the ultimate output of a component needs to not have any surrounding whitespace, such as a component that outputs a hyperlink. While a regular %filter section can be used for this, Myghty provides the <&|formatting.myt:codeline&>trim flag, as so:

<&|formatting.myt:code &><%text> for more information, click <&makelink, a="here", p=4.3, xg="foo" &>. <%def makelink trim="both"> <%args> p xg a <%doc>note this component has a lot of whitespace in it <% a %>

Output:

<&|formatting.myt:code &><%text> for more information, click here.

<&|formatting.myt:codeline&>trim supports the three options <&|formatting.myt:codeline&>left, <&|formatting.myt:codeline&>right, or <&|formatting.myt:codeline&>both, trimming whitespace on the left, right and both sides of component output, respectively.

<&|doclib.myt:item, name="escaping", description="Escaping Content", escapedesc=True &>

Escaping usually refers to a kind of filtering that converts the characters in a string into encoded values that can be safely passed through other character-parsing systems without them interfering.

Myghty provides built in support for HTML and URL escaping (also called URL encoding), and has plans for entity-reference escaping. User-defined escape functions can be added to an application as well.

<&|doclib.myt:item, name="substitutions", description="Escaping Substitutions", escapedesc=True &>

Substitutions, first introduced in <&formatting.myt:link, path="embedding_substitutions"&>, can include escape flags, specified by the following notation:

<&|formatting.myt:code &><%text> <% "i am an argument" | u %>

The above example will URL encode the embedded string. The two escape flags included with Myghty are "u" for url encoding, and "h" for HTML encoding. Entity reference encoding is in the works.

Multiple escape flags are separated by a comma:

<&|formatting.myt:code &><%text> <% "multiple escapes" | h, u %>

Which will HTML and URL escape the given string.

<&|doclib.myt:item, name="programmatic", description="Programmatic Escaping", escapedesc=True &>

The HTML and URL-encoded escaping functions described above are easy enough to use programmatically as well. The request object provides the escaping functions via the <&|formatting.myt:codeline&>apply_escapes() method for this purpose. This method uses the same flags as an escaped substitution, the defaults being "h" for HTML escaping and "u" for URL encoding.

In this example, a component receives the argument "text" and displays it in a textarea, where HTML escaping is required:

<&|formatting.myt:code&><%text> <%args> text <%python> # HTML escape the given text text = m.apply_escapes(text, 'h')

The first argument to <&|formatting.myt:codeline&>apply_escapes() is the text to be escaped, and the second is a list of escape flags. Since strings are lists in Python, multiple flags can be specified either in a single string as in <&|formatting.myt:codeline&>"hu" or as a regular list such as <&|formatting.myt:codeline&>['h', 'u']. <&|doclib.myt:item, name="custom", description="Custom Escape Flags", escapedesc=True &>

You can add your own escape flags to your application via the <&|formatting.myt:codeline&>escapes configuration parameter. <&|formatting.myt:codeline&>escapes is in the form of a dictionary, with the key names being the single-character escaping token and the values being function pointers to escaping functions.

In this example, an escape flag 'p' is added which provides the ability to convert the word "turkey" into "penguin": <&|formatting.myt:code&><%text> escapes = { 'p':re.sub('turkey', 'penguin', f) } <&|doclib.myt:item, name="custom", description="Default Escape Flags", escapedesc=True &>

Default escaping can be configured via the configuration parameter <&|formatting.myt:codeline&>default_escape_flags (PythonOption MyghtyDefaultEscapeFlags). The format of this parameter is a list of escape flag characters. This applies the given flags to all substitutions in an application.

When default escaping is used, the special flag "n" can be specified in the substitution to disable the default escape flags for that substitution.

myghty-1.1/doc/content/globals.myt0000644000175000017500000002100410501064115016255 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="globals", description="Standard Global Variables"&>

Myghty templates and components always have access to a set of global variables that are initialized on a per-request basis. The Myghty request object m is always available, as are the component arguments ARGS. When running with any HTTPHandler-based environment, the HTTP request object r is also available, which in mod_python is the actual mod_python request object, else it is a compatible emulating object. The Myghty session object, or mod_python's own session object, may be configured to be available as the variable s. Finally, any set of user-configured global variables can be defined as well; the value of these globals can be specified on a per-interpreter or per-request basis.

<&|doclib.myt:item, name="globalm", description="Your Best Friend: m"&>

m is known as the "request", which represents the runtime context of the template being executed. Its not the same as the HTTP-specific request object r and is mostly agnostic of HTTP. m includes methods for handling output and writing content, calling other components, as well as other services that a template will usually need. The full list of request methods is described in <&formatting.myt:link, path="request"&>.

<&|doclib.myt:item, name="globalargs", description="Your Other Best Friend: ARGS"&>

ARGS represents a dictionary of all arguments sent to the current component. While a component can specify arguments to be available in the component's namespace via the <% "<%args>" %> tag, the ARGS dictionary contains all arguments supplied to a component regardless of them being named in the <% "<%args>" %> section or not.

In the case of a top-level component called in an HTTP context, ARGS contains the full set of client request parameters. Each field is one of: a string, a list of strings, or for handling file upload objects, a Field object (from the FieldStorage API) or a list of Field objects.

For components called by other components, ARGS contains all the arguments sent by the calling component.

In all cases, the HTTP request arguments, or whatever arguments were originally sent to the request, are available via the request member <&formatting.myt:link, path="request_members", member="request_args" &>.

Component arguments are described in <&formatting.myt:link,path="components_args"&>.

<&|doclib.myt:item, name="globalr", description="Your Pal: r"&>

When running Myghty with any of the HTTPHandlers, i.e. ApacheHandler, CGIHandler, WSGIHandler, or HTTPServerHandler, variable r is a reference to either the mod python request object or a compatible emulation object. In the case of ApacheHandler, it is the actual mod_python request. In other cases, it attempts to provide a reasonably compatible interface, including the member variables headers_in, headers_out, err_headers_out, args, content_type, method, path_info, and filename (more can be added...just ask/submit patches). The request object is useful applications that need awareness of HTTP-specific concepts, such as headers and cookies, beyond what the more generic m object provides which attempts to be largely agnostic with regards to HTTP.

Under WSGIHandler, r also contains the member variables environ and start_response, so that an application may also have direct access to WSGI-specific constructs if needed.

<&|doclib.myt:item, name="globals", description="Your Fair Weather Friend: s"&>

s references the Myghty session object. It can also be configured to reference the mod_python session object when running with mod_python 3.1 To use s, you need to turn it on via <& formatting.myt:link, path="session_options", param="use_session" &>.

The Myghty session is still available even if s is not configured. See the section <&formatting.myt:link, path="session"&> for full information on the session object. <&|doclib.myt:item, name="globalcustom", description="Make your Own Friends"&>

Myghty supports the addition of any number of global variables that will be compiled into the namespace of all templates. The value of these variables can be specified on a per-application basis or a per-request basis. As of version 0.98, both scopes can be used simultaneously. Per-application globals can be specified via the initial interpreter configurational parameters, or within the httpd.conf file in a mod_python environment. Per-request globals require that the variables be initialized before the Myghty request begins, which requires programmatic "chaining" to the Interpreter via the methods described in <& formatting.myt:link, path="configuration_programmatic" &>.

The two configuration parameters to add global arguments are <&|formatting.myt:codeline&>allow_globals, which specifies a list of global variable names to compile into templates, and <&|formatting.myt:codeline&>global_args, which is a dictionary containing the names of the variables mapped to their desired values. A basic example of programmatic global variables looks like:

<&|formatting.myt:code, syntaxtype="python" &> import myghty.http.WSGIHandler def application(environ, start_response): handler = myghty.http.WSGIHandler.get_handler( allow_globals = ['myglobal'], component_root='/path/to/htdocs', data_dir='/path/to/datadirectory' ) return handler.handle( environ, start_response, global_args = {'myglobal' : 'hi'} )

Above, all Myghty components will have access to the global variable "myglobal" which has a per-request value of "hi". Note that the allow_globals parameter is only used on the first request, when constructing a new Interpreter object, whereas global_args may be specified for each request.

Another example, using Interpreter:

<&|formatting.myt:code, syntaxtype="python"&> interpreter = interp.Interpreter( allow_globals = ['myglobal'], data_dir = '/path/to/datadir', component_root = '/foo/components', ) interpreter.execute("file.myt", global_args = {'myglobal':MyGlobalThingy()} )

Here is an ApacheHandler example which specifies globals within both scopes:

<&|formatting.myt:code, syntaxtype="python"&> import myghty.http.ApacheHandler as ApacheHandler # create per-application global object appglobal = 'myappglobal' def handle(req): # create per-request global object myglob = MyGlobal(req) handler = ApacheHandler.get_handler( req, allow_globals = ['appglobal', 'myglobal'], global_args = {'appglobal' : appglobal} ) return handler.handle(req, global_args = {'myglobal':myglob})

Here is an application-scoped global added in a mod_python environment via the httpd.conf file:

<&|formatting.myt:code, syntaxtype="conf"&> # specify list of global variable names PythonOption MyghtyAllowGlobals ['myglobal'] # specify the value of the global variable PythonOption MyghtyGlobalArgs "{'myglobal': <% "\\" %> __import__('mystuff.util').util.MyGlobalThingy()}" <&|doclib.myt:item, name="assignment", description="Assignment to Request-Scoped Globals"&>

When the allow_globals configuration parameter specifies global variables to be compiled into all templates, if the variable is not present at request time, it is assigned the value of None (as of 0.98a). This is handy for global variables whos value is not determined until within a request.

To assign to a global variable, use m.global_args:

<&|formatting.myt:code&><%text> # assume the configuration parameter global_args = ['x','y','z'] is set <& hi &> % m.global_args['x'] = 'im x!' % m.global_args['y'] = 'im y!' <& hi &> <%def hi> x is '<% x %>' y is '<% y %>' z is '<% z %>'

this will produce:

<&|formatting.myt:code&> x is: '' y is: '' z is: '' x is: 'im x!' y is: 'im y!' z is: '' myghty-1.1/doc/content/inheritance.myt0000644000175000017500000002152010501064115017126 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="inheritance", description="Inheritance", &>

Inheritance allows a file-based component to be "wrapped" by another file-based component. This means the requested page executes as a method call inside of the inheriting page, which executes first. The basic idea of this is to allow a whole set of pages to have standardized behavior applied to them by an "inheriting" component, the application of which can be as simple as a standard header and footer, or something more complicated such as an authentication scheme, a content caching scheme, or a filtering scheme.

Inheritance also establishes a relationship between pages with regards to method calling as well as component attributes.

<&|doclib.myt:item, name="wrapper", description="The Wrapper Chain", &>

A set of pages that all inherit each other is sometimes called the "wrapper chain". The wrapper chain is an actual property of the request, and is determined dynamically for each new request based on what files are present in the filesystem and what inheritance pattern they specify. When a template X inherits from a template Y, and that template in turn inherits from template Z, the wrapper chain is created:

<&|formatting.myt:code&> Z -> Y -> X

The request above was called specifying component X as the requested component. X inherits from Y, either via an explicit flag or an autohandler configuration (more on that later), and Y inherits from Z. Therefore a wrapper chain with Z at the beginning and X at the end is created. When the request executes, control is passed to Z first. Z performs its component operations, then programmatically instructs the request to call the next component in the wrapper chain, which is Y. Y does the same thing and calls X, the originally requested component. When X is complete, control passes back to Y, and when Y is complete, control passes back to Z.

The flag used to specify that a component should inherit from another is called "inherit", and is specified in the <% "<%flags>" %> section of the component. The component to inherit from is specified by its component-root-relative URI. When an inherited parent component wants to call its inheriting child, it usually uses the <&formatting.myt:link, path="request_methods", method="call_next"&> method of the request object. The child is only executed if its inherited parent explicitly does so.

If no "inherit" flag is specified for a page, the page will attempt to inherit from a template in the nearest enclosing directory named "autohandler". Whereas the inherit flag allows a component to explicitly specify its inherited parent, the autohandler mechanism allows the configuration of implicitly inheriting parents. Autohandlers are described in the next section <&formatting.myt:link, path="specialtempl_autohandler", text="Autohandlers"&>.

<&|doclib.myt:item, name="example", description="Example - Basic Wrapping", &>

In this example, the requested page is called "index.myt", and its inherited parent is called "base.myt". base.myt supplies a standard HTML header and footer, and index.myt supplies the content in the middle of the <% "" | h %> tags.

<&|formatting.myt:code, title="index.myt, inherits from base.myt"&><%text> <%flags>inherit='/base.myt' I am index.myt <&|formatting.myt:code, title="base.myt, the parent template"&><%text>

example of content wrapping

# fetch the next component in the wrapper chain # and call it % m.call_next() The resulting document would be: <&|formatting.myt:code&><%text>

example of content wrapping

I am index.myt

While the <&formatting.myt:link, path="request_methods", method="call_next"&> method of request is the simplest way to call the next component in the wrapper chain, the method <&formatting.myt:link, path="request_methods", method="fetch_next"&> exists to pop the next component off the chain but not execute it, as well as <&formatting.myt:link, path="request_methods", method="fetch_all"&> which pops off and returns the entire list of components in the wrapper chain.

<&|doclib.myt:item, name="basecomp", description="The Base Component", &>

In the wrapper chain <% '"Z -> Y -> X"' | h%> described at the beginning of the section, the component X is known as the request component, and is accessible throughout the life of the request via the <&formatting.myt:link,path="request_members", member="request_component"&> member of the request. It also is established as the initial "base component" of the request. The base component is accessible via the <&formatting.myt:link,path="request_members", member="base_component"&> request member, and it is defined first as the lead requested component in a wrapper chain. When methods in other template files are called, the base component changes to be the file that the method appears in, throughout the life of that method's execution, and upon method completion returns to its previous value.

The base component is also referred to by the special component keyword 'SELF'. This keyword can be used directly via the <&formatting.myt:link, path="request_methods", method="fetch_component"&> method, but it is more commonly referenced in method component calls, as detailed in the next section. <&|doclib.myt:item, name="method", description="Method and Attribute Inheritance", &>

Methods, first described in <&formatting.myt:link, path="components_methods" &>, are normally called with the <% ":"|h%> syntax, where <% ""|h%> is the URI or special keyword identifier of a component, and <% ""|h%> is the name of the method to search for. This syntax enables the component to search not only its local method list for the requested method, it also will search its immediate parent for the method if not found, and that parent will continue the search up the wrapper chain.

It is for this reason that the base component and the SELF keyword is of particular value in fetch_component and method calls, since it indicates the innermost component in the current inheritance chain. An template at the end of a wrapper chain (i.e. template Z in the previous section) can specify the SELF keyword when calling a method, and the method will be located from the innermost template first (i.e. component X), and on up the wrapper chain until found.

Similarly, component attributes are located using an inheritance scheme as well. Attributes are referenced via the <&formatting.myt:link, path="components_members", member="attributes"&> member of the component object. The attributes dictionary, while it is a regular dictionary interface, will search for requested values in the parent of the component if not found locally.

<&|doclib.myt:item, name="methodexample", description="Method Inheritance Example", &>

Here is an example where the parent component is an autohandler, which because of its name, automatically becomes the inherited parent of the child component, as long as the child component does not explicitly specify otherwise.

Both in the parent component as well as the child component that inherits from it specify a method "title". The autohandler can render the title of the page via the "title" method, where it is guaranteed to exist at least in the autohandler's own version of the method, but can be overridden by the inheriting page. Additionally, the autohandler has an <% "<%attr>" %> section indicating the path to a file location. The child page will look in its own attribute dictionary for this location, where it will ultimately come from the inheriting parent.

<&|formatting.myt:code, title="autohandler - specifies root title method and attributes"&><%text> <%attr> # path to some files fileroot = '/docs/myfiles' # the "title" method is called here from SELF, # so the method will be searched in the base component first, # then traverse up the inheritance chain until found <& SELF:title &> # call the next component in the wrapper chain % m.call_next() # default "title" method implementation <%method title> Welcome to My Site <&|formatting.myt:code, title="pressrelease.myt - overrides title method"&><%text> <%python scope="init"> # locate the file root via a parent attribute pr = get_press_releases(fileroot = self.attributes['fileroot']) # specify a title method to override that of the parent's <%method title> My Site: Press Releases

Press Releases

% for release in pr: # ... print data ... % myghty-1.1/doc/content/installation.myt0000644000175000017500000001501110501064115017334 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="installation", description="Installation", header="Setting Up"&> <&|doclib.myt:item, name="requirements", description="Requirements", &>
  • Python 2.3.3 or greater. There is an issue with weakrefs (Python bug id 839548) prior to that version which impacts stability.
  • For production webserver usage, a server environment that supports one of: CGI, WSGI, or mod_python.
  • For mod_python usage, Apache 1.3/mod_python 2.7 (or greater) or Apache 2.0/mod_python 3.1 (or greater).
  • Tested platforms: Linux (RedHat 6.2, RedHat 9), Mac OSX, Solaris, Windows 2000. Prefork and worker MPMs tested on Apache 2.0.
  • Familiarity with the webserver being used and whatever plug-in architecture being used.
<&|doclib.myt:item, name="quick", description="Quick Start - Running the Demo Server", &>

Myghty includes an out-of-the-box instant-gratification examples/documentation server you can run directly from the distribution files, which listens on port 8000 by default. Extract the distribution from its .tar.gz file. Then run the server as follows:

<&|formatting.myt:code, syntaxtype="cmd" &> cd /path/to/Myghty-X.XX/ python ./tools/run_docs.py

Then point a webbrowser to the location http://localhost:8000/. An alternative port can be specified on the command line, e.g. python ./tools/run_docs.py 8100.

The demo server is a handy tool to browse through a release without having to perform an install. The underlying HTTP server implementation can also be handy for development and prototyping.

<&|doclib.myt:item, name="install", description="Installing the Library", &>

Myghty now installs with setuptools, which is the next generation of the standard Python distutils module. Its basic usage is the same as that of distutils. Switch to the user account under which Python is installed, and run: <&|formatting.myt:code, syntaxtype="cmd" &> cd /path/to/Myghty-X.XX/ python setup.py install As some systems may have multiple Python interpreters installed, be sure to use the Python executeable that corresponds to the Python being used by the webserver, such as the mod_python configuration, to insure the files go into the correct library path. setup.py will precompile the Myghty .py files and install them in the library path for the Python executeable you just ran.

This installer also installs Myghty in a version-specific location, such as "site-packages/Myghty-0.99-py2.3.egg/". If you have installed a version of Myghty prior to 0.99, the distutils-based installation, which lives in "site-packages/myghty", will produce a conflict message when running setup.py. After installation, you should delete the old install, either manually or via the command python ez_setup.py -D myghty. <&|doclib.myt:item, name="docgen", description="Generating the Documentation", &>

This documentation can also be generated into flat html files that are browseable directly from the filesystem. Myghty can create these files by running the genhtml.py utility:

<&|formatting.myt:code, syntaxtype="cmd" &> cd /path/to/Myghty-X.XX/doc python genhtml.py <&|doclib.myt:item, name="paste", description="Running Paste Templates", &>

Myghty now includes a few Python Paste templates, which are pre-built Myghty applications that can install themselves into a runnable application. Python 2.4 is required to run Python Paste.

Myghty's standard install procedure will automatically download and install all Python Paste components, which are also available at http://pythonpaste.org/download. Also required is WSGIUtils, which must be installed from a particular .egg file format, via:

<&|formatting.myt:code, syntaxtype="cmd" &> python ez_setup.py http://pylons.groovie.org/files/WSGIUtils-0.6-py2.4.egg

When Python Paste is installed, it will install the script "paster" into the standard Python scripts location. Insure that this location is in your path. Then to set up a Paste Template, just do:

<&|formatting.myt:code, syntaxtype="cmd" &> cd /target/directory paster create --template=myghty_simple myproject cd myproject paster serve server.conf

The myghty_simple template is a very small four-template configuration, which will by default listen on port 8080. Also included is myghty_modulecomponent, which shows off a more controller-oriented configuration, and myghty_routes, which builds a new Rails-like "Routes" configuration.

When a new Paste Template project is created, the file <%""|h%>/server.conf illustrates the general Paste options, such as the server listening port. Myghty-specific configuration information is in <%""|h%>/<%""|h%>/webconfig.py.

<&|doclib.myt:item, name="componentgen", description="Running Filesystem Components", &>

There is also a generic tool for running any Myghty template either to standard output or to a file. The command is as follows:

<&|formatting.myt:code, syntaxtype="cmd" &> python tools/gen.py --help usage: gen.py [options] files... options: -h, --help show this help message and exit --croot=COMPONENT_ROOT set component root (default: ./) --dest=DESTINATION set destination directory (default: ./) --stdout send output to stdout --datadir=DATADIR set data directory (default: dont use data directory) --ext=EXTENSION file extension for output files (default: .html) --source generate the source component to stdout myghty-1.1/doc/content/modulecomponents.myt0000644000175000017500000012721110501064115020234 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="modulecomponents", description="Module Components", &>

Module components build upon the component concept as introduced and explained in the previous section, <&formatting.myt:link, path="components"&>. They represent a way of writing Myghty components using regular Python code within importable .py modules. A module component can be used to handle requests, controlling the flow of an application across one or more templates; this role is known as a "controller". Module components can also be embedded as component calls within templates just like any other component. In both cases, Module Components are the appropriate place to place code-intensive control or view logic.

Module Components, or MC's, are not a new idea, and have analogies in other environments, such as the Java Servlet class, the mod_python handler() function, or the WSGI application() callable. To suit the preferences of the both the class-oriented and the function-oriented developer, MC's can be written either as classes that subclass a specific Myghty class (similar to subclassing the Java Servlet class) or just as a regular Python function or callable object (i.e., an object instance that has a __call__ method). Additionally, individual methods upon a class instance can also be called as MC's.

When a plain Python function, callable object, or arbitrary instance method (herein will be generically referred to as a "callable") is to be called as a Module Component, the resulting component is referred to as an implicit module component. In this case, the Myghty Interpreter is given the location of the callable either via a string pattern or via passing the callable itself, and is told to execute it as a component. The Interpreter then creates a "real" component behind the scenes which proxies the Myghty argument set to the callable. The developer need not be aware of this proxying component (though it is available).

The flipside to the implicit module component is the explicit module component. Here, the developer creates a class that is a subclass of myghty.component.ModuleComponent, and implements the method do_run_component(), which satisfies the execution of the component. Like the implicit module component, the Interpreter is given its location either via a string pattern or via the Class object itself, and is told to execute it. The Interpreter, upon receiving a Class object, instantiates the class which becomes directly the component to be executed; no "proxy" component is created.

The differences between implicit and explicit module components are mostly differences in style. The explicit form allows the developer more direct access to the flags, attributes, and argument lists of the component to be called, whereas the implicit form is more sparse, lightweight and flexible. Both styles can be mixed; essentially, the Intepreter receives the object and determines the calling style based on the type of object passed to it. In all cases, the rest of a Myghty application sees just more myghty.component.Component objects, which have the same properties as any other component.

An added layer of complexity to module components involves the configuration of URI resolution, which allows the mapping of arbitrary URI's to specific module component callables or classes based on different schemes. This is required for MC's that are to be called as the top-level, request-handling component. Myghty currently includes two methods of achieving this, which are described later in this section, as well as an API to allow any user-defined mapping algorithm (part of <&formatting.myt:link, path="resolver" &>).

<&|doclib.myt:item, name="example", description="Example Module Component", &>

Lets start with hello world. All we need to do is create a new .py file, called hello.py, and place the following function inside it:

<&|formatting.myt:code, syntaxtype='python' &> def helloworld(m): m.write("hello world !")

That is the extent of the module component itself. Since it is a plain function, this is an "implicit" module component.

To call the component, the request object is given a string starting with the prefix "MODULE:", followed by the module name and name of the callable or class, separated by a colon. In a template it is one of the following:

<&|formatting.myt:code, syntaxtype='myghty' &><%text> # inline <& MODULE:hello:helloworld &> # code <%python> m.comp("MODULE:hello:helloworld")

Alternatively (release 0.99b), the "MODULE:" prefix can also be an "at" sign as follows:

<&|formatting.myt:code, syntaxtype='myghty' &><%text> <& @hello:helloworld &> <%python> m.comp("@hello:helloworld")

Both the module name and the callable/class name can contain any number of periods to represent sub-modules or sub-properties of the module. Suppose helloworld was a property on an object:

<&|formatting.myt:code, syntaxtype='python' &> hello = object() def doit(m): m.write("hello world !") hello.helloworld = doit

This component can be called as:

<&|formatting.myt:code, syntaxtype='myghty' &><%text> <& MODULE:hello:hello.helloworld &>

or just:

<&|formatting.myt:code, syntaxtype='myghty' &><%text> <& @hello:hello.helloworld &>

The callable can have any argument list, and even the m variable is optional (though highly recommended). Myghty uses the inspect library to determine the desired arguments based on the callable's signature. (for the performance minded, this is done only once when the component is first loaded). Using the names present in the argument list, Myghty will determine which of the available global variables will be specified, as well as what required and optional component arguments this MC will require. The component arguments are configured in exactly the same way as a template-based component configures its <% "<%ARGS>" | h %> section.

So to our "hello world" lets add the current and optionally tomorrow's weather:

<&|formatting.myt:code, syntaxtype='python' &> def helloworld(m, today, tomorrow = None): m.write("hello world ! its a %s day today." % today) if tomorrow is None: m.write("Don't know what it is tomorrow, though.") else: m.write("But tomorrow, it'll be %s" % tomorrow)

This component can be called like these examples:

<&|formatting.myt:code, syntaxtype='myghty' &><%text> # inline shorthand <& @hello:helloworld, today='sunny' &> # inline longhand <& MODULE:hello:helloworld, today='sunny' &> # code <%python> m.comp("MODULE:hello:helloworld", today='cloudy', tomorrow='rainy')

The argument list of a module component may specify any of the standard and/or user-configured global arguments (globals were introduced in <& formatting.myt:link, path="globals" &>). All arguments are passed by name, so the ordering is not important:

<&|formatting.myt:code, syntaxtype='python' &> def handle_request(m, ARGS, r, s, **params): # ...

The above component specifies that it will receive the global variables m, ARGS, r, and s. It also defines **params, so all remaining component arguments will be in this dictionary. A callable that defines simply **params will receive all globals and component arguments within the dictionary.

<&|doclib.myt:item, name="flavor", description="Flavors of Module Component", &>

A summary of the various styles of MC are as follows. For each of the below examples, assume the callable exists within a module named mylib.hello:

<&|doclib.myt:item, name="function", description="Function", &> This is the most basic type of implicit module component. <&|formatting.myt:code, syntaxtype='python' &> def helloworld(m, **params): m.write("hello world!") Called as: <&|formatting.myt:code, syntaxtype='myghty' &><%text> <& MODULE:mylib.hello:helloworld &> <&|doclib.myt:item, name="callable", description="Callable Object", &>

A callable object must be instantiated, but to the outside world looks almost the same as a function:

<&|formatting.myt:code, syntaxtype='python' &> class HelloWorld: def __call__(self, m, **params): m.write("hello world!") # instantiate the class helloworld = HelloWorld() Called as: <&|formatting.myt:code, syntaxtype='myghty' &><%text> <& MODULE:mylib.hello:helloworld &> Note that this is identical to the previous function example. <&|doclib.myt:item, name="method", description="Object Method", &> The object method is set up similarly to a callable object, the only difference being that a specific named method is called, instead of the generic __call__ method. The difference lies in how it is specified when called. <&|formatting.myt:code, syntaxtype='python' &> class HelloWorld: def doit(self, m, **params): m.write("hello world!") # instantiate the class helloworld = HelloWorld() Called as: <&|formatting.myt:code, syntaxtype='myghty' &><%text> <& MODULE:mylib.hello:helloworld.doit &> One particular quirk about the object method style is that if an object instance contains multiple methods that are called as MC's, the interpreter creates a separate "proxy" component for each method.

With both callable object styles, the same object instance handles many requests, including multiple simultaneous requests among different threads, if the application is running in a threaded environment. A developer should take care to store request-scoped information as attributes upon the request object and not as instance variables of the object, as well as to appropriately synchronize access to instance variables and module attributes. <&|doclib.myt:item, name="explicit", description="Class / Explicit Module Component", &>

This style works differently from the previously mentioned styles, in that the developer is writing a class directly against the Myghty API. In this style, the developer directly defines the class of the component that is ultimately being accessed by the outside world. No "proxying" component is created; instead, the Interpreter instantiates the specified class the first time it is referenced. This also removes the need to explicitly instantiate the class: <&|formatting.myt:code, syntaxtype='python' &> import myghty.component class HelloWorld(myghty.component.ModuleComponent): def do_run_component(self, m, **params): m.write("hello world!") Called as: <&|formatting.myt:code, syntaxtype='myghty' &><%text> <& MODULE:mylib.hello:HelloWorld &>

Note that the scoping rules for this style of component are the same, i.e. the same component instance is called for many requests and must be aware of variable scope as well as concurrency issues.

The do_run_component method works similarly to the callable of an implicit component. Its argument list is dynamically inspected to determine the desired globals and component arguments, in the same way as that of implicit MC's. However, there also is the option to bypass this inspection process and name the arguments explicitly, as well as their desired scope. In previous versions of Myghty, this was the only way to define the arguments of an MC, but now its only optional. This method is described in the next section, as well as the configuration of flags and attributes for MC's. <&|doclib.myt:item, name="initialization", description="Module Component Initialization", &>

Explicit MCs as well as implicit MCs based on an object instance have an optional initialization step that is called the first time the object instance is referenced. The method is called on an explicit ModuleComponent subclass as:

def do_component_init(self)

And is called on an implicit callable object as:

def do_component_init(self, component)

In the second version, the argument component is an instance of myghty.component.FunctionComponent that is hosting the implicit module component's __call__ method or other method. It is important to note that an object instance with many callable methods will actually have many FunctionComponents created, each one hosting an individual methods...however, the do_component_init() method is only called once with whatever FunctionComponent was first associated with the object.

In previous versions of Myghty, the initialization phase was required for components with arguments, which had to be explicitly declared within this stage. As of version 0.98, explicit declaration of component arguments is optional, and the argument lists are by default determined from the signature of the explicit do_run_component method or the implicit callable.

Explicit specification of an MCs arguments in the init section are as follows:

<&|formatting.myt:code, syntaxtype='python'&><%text> import myghty.component as component class MyComponent(component.ModuleComponent): def do_component_init(self, **params): # component arguments self.args = ['arg1', 'arg2'] # required component arguments self.required_args = ['arg3', 'arg4'] # request-only arguments self.request_args = ['docid'] # required request-only arguments self.required_request_args = ['userid'] # subrequest or request arguments self.subrequest_args = ['foo'] # required subrequest or request arguments self.required_subrequest_args = ['bar']

A component as above would have the do_run_component signature as follows:

<&|formatting.myt:code, syntaxtype='python'&><%text> def do_run_component( self, m, userid, arg3, arg4, bar, docid = None, arg1 = None, arg2 = None, foo = None, **params ):

Note that if a module component defines any arguments explicitly in the do_component_init method, all arguments for the do_run_component method must be specified; no automatic introspection of function arguments will occur.

Similarly, <% "<%flags>" %> and <% "<%attr>" %> sections can be achieved like this:

<&|formatting.myt:code, syntaxtype='python'&><%text> def do_component_init(self, **params): # flags self.flags = { 'autoflush':True, 'trim': 'both' } # attributes self.attr = { 'style':'green', 'docid':5843 } <&|doclib.myt:item, name="controller", description="Using MCs as Controllers", &>

The two most prominent features of an MC used as a controller includes that URI resolution is configured so that one or more URIs result in the component being called directly from a request, and that the component typically uses subrequests to forward execution onto a template, which serves as the view.

Here are two versions of a controller component that is used to pull documents from a database based on the request arguments, and displays them via a template called "documents.myt".

Figure 1: Implicit Style, using a Callable Object

<&|formatting.myt:code, syntaxtype='python' &><%text> class DocumentManager: def do_component_init(self, component): # load some application-wide constants via Interpreter attributes. # the Interpreter is located via the hosting component. self.document_base = component.interpreter.attributes['document_base'] self.db_string = component.interpreter.attributes['db_connect_string'] def __call__(self, m, docid = None): # if no document id, return '404 - not found' if docid is None: m.abort(404) # access a hypothetical document database docdb = get_doc_db(self.document_base, self.db_string) # load document document = docdb.find_document(docid) # couldnt find document - return '404 - not found' if document is None: m.abort(404) # run template m.subexec('documents.myt', document = document, **params) documentmanager = DocumentManager()

Figure 2: Explicit Style, with Pre-Declared Arguments

<&|formatting.myt:code, syntaxtype='python' &><%text> import myghty.component as component class DocumentManager(component.ModuleComponent): def do_component_init(self, **params): # load some application-wide constants self.document_base = self.interpreter.attributes['document_base'] self.db_string = self.interpreter.attributes['db_connect_string'] # establish the argument names we want (optional as of 0.98) self.args = ['docid'] def do_run_component(self, m, ARGS, docid = None, **params): # if no document id, return '404 - not found' if docid is None: m.abort(404) # access a hypothetical document database docdb = get_doc_db(self.document_base, self.db_string) # load document document = docdb.find_document(docid) # couldnt find document - return '404 - not found' if document is None: m.abort(404) # run template m.subexec('documents.myt', document = document, **params)

The next section describes the two methods of configuring request URIs to execute "controller" components like this one.

<&|doclib.myt:item, name="resolution", description="Configuring Module Component Resolution", &>

Module components can be used anywhere within the request, either right at the beginning (i.e. a controller) or within the scope of other components already executing. A module component can be fetched and/or called based on its importable module name followed by a path to a callable object, or the name of a class that subclasses myghty.components.ModuleComponent. However, this doesn't work as a URL sent to a webserver. For request-level operation, MCs must be mapped to URIs. This resolution is achieved through two different configuration directives, module_components and module_root. A third option also exists which is to use the new routesresolver resolver object.

<&|doclib.myt:item, name="module_components", description="module_components", &> The module_components configuration parameter looks like this: <&|formatting.myt:code, syntaxtype="python" &> module_components = [ {r'myapplication/home/.*' : 'myapp.home:HomeHandler'}, {r'myapplication/login/.*' : 'myapp.login:LoginHandler'}, {r'.*/cart' : 'myapp.cart:process_cart'}, {r'.*' : 'myapp.home:HomeHandler'} ] Which in an apache configuration looks like: <&|formatting.myt:code, syntaxtype="conf" &> PythonOption MyghtyModuleComponents "[ <% "\\" %> {r'myapplication/home/.*' : 'myapp.home:HomeHandler'}, <% "\\" %> {r'myapplication/login/.*' : 'myapp.login:LoginHandler'}, <% "\\" %> {r'.*/cart' : 'myapp.cart:ShoppingCart'}, <% "\\" %> {r'.*' : 'myapp.home:HomeHandler'} <% "\\" %> ]"

Each entry in the module_components array is a single-member hash containing a regular expression to be matched against the incoming URI, and an expression that can be resolved into a Module Component. This expression can be any of the following forms:

<&|formatting.myt:code, syntaxtype="python" &> module_components = [ # a string in the form ':' {r'myapplication/home/.*' : 'myapp.home:HomeHandler'}, # a string in the form ':' {r'myapplication/login/.*' : 'myapp.login:do_login'}, # a string in the form ':..' {r'myapplication/status/.*' : 'myapp.status:handler.mcomp.get_status'}, # a Class {r'.*/cart' : MyClass}, # a function reference, or reference to a callable() object {r'.*/dostuff' : do_stuff}, # reference to a class instance method {r'.*' : mymodule.processor.main_process} ]

Generally, its better to use the string forms since they encompass all the information needed to load the class or callable object, which allows the Myghty interpreter to dynamically reload the object when the underlying module has changed.

For module components that resolve to a Class, the Class will be instantiated by the Interpreter, and is expected to be a subclass of myghty.component.ModuleComponent. This is also known as an explicit module component. For components that resolve to a function, object instance method, or callable object (i.e. any object that has a __call__ method), a "wrapper" module component will be created automatically which adapts to the callable's argument list. This is also known as an implicit module component.

<&|doclib.myt:item, name="additional", description="Passing Resolution Arguments", &>

The module_components configuration parameter also includes some methods of returning information about the actual resolution of the component at request time.

Since module_components uses regular expressions to match URIs to components, the re.Match object produced when a match occurs can be accessed via the m global variable. This example places capturing parenthesis in the regular expression to capture the additional path information:

<&|formatting.myt:code, syntaxtype="python", &> module_components = [ # place capturing parenthesis in the regexp {r'/catalog/(.*)' : 'store:Catalog'} ] The contents of the capturing parenthesis are available as: <&|formatting.myt:code, syntaxtype="python", &> m.resolution.match.group(1)

User-defined arguments can also be configured which will be passed to the request object at resolution time. To use this form, a dictionary is used instead of a single string to specify the module and callable/class name of the component, and the address of the component itself is placed under the key 'component':

<&|formatting.myt:code, syntaxtype="python", &> module_components = [ # supply the component with some arguments {r'/login/' : { 'component' : 'myapp:loginmanager', 'ldap_server' : 'ldap://localhost:5678' } } ] The contents of the ldap_server argument are available as: <&|formatting.myt:code, syntaxtype="python", &> m.resolution.args['ldap_server']

The corresponding Resolver class for module_components is ResolveModule.

<&|doclib.myt:item, name="module_root", description="module_root", &>

This parameter locates module components based on paths. It is similar to the <&formatting.myt:link, path="parameters", param="component_root" &> configuration parameter in that it defines one or more roots which will all be matched against the incoming URI. However, it not only traverses through directories, it also will traverse into a Python module where it attempts to locate a function or callable object instance. Currently, only the "implicit" style of module components can be used with module_root (but module_components, from the previous section, can be used with implicit or explicit MCs).

The entries in module_root take the following forms:

<&|formatting.myt:code, syntaxtype="python", &> module_root = [ # a regular file path '/web/lib/modules/', # the full file path of a Python module '/web/extra/modules/mycontroller.py', # the name of an importable Python module 'mylib.loginhandlers', ]

Using the first path '/web/lib/modules', heres an example. Start with the following file:

<&|formatting.myt:code, syntaxtype=None, &> /web/lib/modules/admin/login.py

This file contains the following code:

<&|formatting.myt:code, syntaxtype="python", &> def hello(m): m.write("hello world!") class _do_login: def __call__(self, m): m.write("please login") # .... def foo(self, m): m.write("this is foo") index = _do_login() _my_var = 12

Finally, lets have a module_root of:

<&|formatting.myt:code, syntaxtype="python", &> module_root = ['/web/lib/modules']

With this configuration, URLs will be resolved as follows:

<&|formatting.myt:code, syntaxtype=None &> http://mysite.com/admin/login/hello/ ---> "hello world!" (login.py:hello()) http://mysite.com/admin/login/ ---> "please login" (login.py:index.__call__()) http://mysite.com/admin/login/foo/ ---> "this is foo" (login.py:index.foo()) http://mysite.com/admin/login/lala/ ---> "please login" (login.py:index.__call__()) http://mysite.com/admin/login/_my_var/ ---> "please login" (_my_var is private and is skipped) http://mysite.com/admin/lala/ ---> 404 not found (no file named lala.py)

The spirit of this resolver is that of the mod_python Publisher handler. Path tokens are converted into the names of .py files on the filesystem. When a file is located and it is a valid Python module, the module is loaded and further path tokens are resolved as attributes within the module itself. Attributes that start with an underscore are skipped, which is the default way to flag "private" attributes. There is also a "public" attribute marker which can be used to mark public functions instead of marking private functions, explained in the options section below. If no matching attribute exists for a path token, it looks for the attribute name "index", else returns a ComponentNotFound exception.

<&|doclib.myt:item, name="options", description="Module Root Options", &>

The underlying Resolver object for the module_root parameter is called ResolvePathModule (resolver objects are described in <&formatting.myt:link, path="resolver" &>). This object also has configuration options of its own. These options may be specified to the ResolvePathModule constructor directly. They can be specified as regular Myghty configuration parameters as well as of version 0.99b:

  • module_root_adjust=None (release 0.99b) - a reference to a function which receives the incoming URI and returns a modified version of that URI with which to resolve. The modified URI is not propigated onto other rules in the resolution chain, so therefore it represents a "local" alternative to <&formatting.myt:link, path="parameters", param="path_translate"&>.

    This parameter is also called 'adjust' in all releases, but that name conflicts with the same name in the ResolveFile resolver, so should only be used when constructing individual ResolvePathModule objects and not as a global configuration parameter.

  • require_publish=False - when set to True, the callable object must have an attribute publish set to True in order to mark it as publically accessible; else it will not be resolved. This effectively reverses the "negative publishing" model established via the underscore "_" syntax into a "positive publishing" model. Given this configuration:

    <&|formatting.myt:code, syntaxtype="python", &> module_root = ['/web/lib/modules'] require_publish = True

    Marking functions and methods public looks like this: <&|formatting.myt:code, syntaxtype="python", &> class MyController(object): def __call__(self, m, **kwargs): # ... m.subexec('mytemplate.myt') __call__.public = True def do_stuff(self, m, **kwargs): # ... m.subexec('dostuff.myt') do_stuff.public = True def call_index(m,r, s, ARGS): # ... m.subexec('index.myt') call_index.public = True mycallable = MyController()

    The public attribute can also be set in Python 2.4 via decorator:

    <&|formatting.myt:code, syntaxtype="python", &> def mark_public(func): func.public = True return func @mark_public def mycontroller(m, **kwargs): # ...
  • path_moduletokens=['index'] - this is a list of special "default" token names when searching for module attributes. For example: If a given path "/foo/bar/bat" resolves to the module "bar.py" inside of directory "foo/", it will first look for an attribute named "bat" inside the module. If "bat" is not present, it will then look for all the names inside the moduletokens list, which defaults to the single entry "index". Therefore /foo/bar/bat not only looks for "bat", it also looks for "index". The tokens inside moduletokens are checked as defaults for every module token, so that a path such as "/foo/bar/bat" can also correspond to a module attribute "foo.index.bat", "index.bar.bat", etc.
  • path_stringtokens=[] - this is the path equivalent to the moduletokens parameter. It is a list of "default" path tokens when searching for files. In some ways, this parameter's functionality overlaps that of <&formatting.myt:link, path="parameters", param="dhandler_name"&>, except it allows multiple names and applies only to the module_root configuration. For example, if stringtokens is configured to ["default", "index"], and a given path "/foo/bar" resolves to the directory "foo/" but there is no file named "bar.py" inside the directory, it will then look for the files "default.py" and "index.py", respectively. Like moduletokens, stringtokens also checks these defaults for every path token, so a URL of "/foo/bar/bat" can also match "/default/bar/index", for example.

module_root can also be combined with dhandler resolution (described in <&formatting.myt:link, path='specialtempl_dhandler' &>), so that a file named dhandler.py in the current or parent directory serves as the "default" Python module for a path token that does not correspond to any other file. In the case of ResolvePathModule locating a dhandler.py file, the additional path tokens that normally reside within <& formatting.myt:link, path="request_members", member="dhandler_path"&> are also searched as attributes within the dhandler.py module.

The corresponding Resolver class for module_root is ResolvePathModule.

<&|doclib.myt:item, name="routesresolver", description="Routes Resolver", &>

The Routes Resolver provides Rails-like functionality, building upon the resolution architecture described in <&formatting.myt:link, path="resolver"&>. Since it is not part of the default set of resolver objects, it must be included in the resolution chain via the <&formatting.myt:link, path="parameters", param="resolver_strategy"&> configuration parameter.

A rudimentary example of the Routes resolver can be viewed by installing the myghty_routes Paste template. Instructions for installing Paste templates are in <& formatting.myt:link, path="installation_paste"&>. The routes resolver also serves as the core resolver for the Pylons web framework which is based on Myghty, and includes more comprehensive and up-to-date examples.

<&|doclib.myt:item, name="preprocessor", description="Module Component Pre and Post processing", &>

Request-handling module components can also be used to perform pre- and post-processing on a request. This means that the information about a request is modified before and/or after the main response is determined. The following two examples both wrap the main body of the request inside of a subrequest.

A module component that performs translations on incoming URI's, and then passes control onto the requested template, looks like this:

<&|formatting.myt:code, syntaxtype='python'&><%text> def translate_path(self, m, **params): path = m.get_request_path() path = re.sub('/sitedocs/john/', '/', path) m.subexec(path, **params)

A URI configuration for this component might look like:

<&|formatting.myt:code, syntaxtype="python"&><%text> module_components = [{r'/sitedocs/john/.*\.myt', 'mymodule:translate_path'}]

Path translation is also accomplished in Myghty via the <&formatting.myt:link, path="parameters", param="path_translate"&> parameter, and can be controlled in finer detail through the use of custom resolution rules, described in <&formatting.myt:link, path="resolver" &>.

An example post processing component, which filters the output of the subrequest before returning data:

<&|formatting.myt:code, syntaxtype='python' &><%text> import StringIO, re def filter(self, m, **params): # make a buffer buf = StringIO.StringIO() # create a subrequest using that buffer # our templates are located relative to the # request URI underneath '/components' subreq = m.create_subrequest('/components' + m.get_request_uri(), out_buffer = buf) # execute the subrequest ret = subreq.execute() # perform a meaningless filter operation on the content content = re.sub(r'foo', r'bar', buf.getvalue()) # write the content m.write(content)

This component might be configured as:

<&|formatting.myt:code, syntaxtype="python"&><%text> module_components = [{r'/sitedocs/john/.*\.myt', 'mymodule:filter'}]

Filtering is also built in to Myghty via the use of <% "<%filter>" %> section, described in <& formatting.myt:link, path="filtering" &>.

<&|doclib.myt:item, name="templates", description="Using Module Components within Templates", &>

MCs can also be called from within templates. If an MC is configured to be callable against a URI using module_components or module_root, that URI can also be used within a regular <% "<& &>" | h %>-style component call. Or, the MODULE: prefix style described previously can be used.

<&|doclib.myt:item, name="formexample", description="Example: Form Visitor", &>

Here is an example of a module component that is used to generate HTML forms from a data object, using the visitor pattern. Such an architecture can be used to have form information stored in a database or XML file which can then be used to automatically construct a corresponding form. This example is included in working form with the Myghty distribution.

Step 1: Data Model - The data model consists of a hierarchy of FormElement objects. Some FormElement objects can contain collections of other FormElements (the "composite" pattern):

<&|formatting.myt:code, title='form.py', syntaxtype='python'&><%text> class FormElement(object): """ abstract FormElement superclass.""" def __init__(self): pass def accept_visitor(self, visitor): """subclasses override accept_visitor to provide the appropriate type-based visitor method.""" pass class Form(FormElement): """represents a
tag.""" def __init__(self, name, action, elements): self.name = name self.action = action self.elements = elements def accept_visitor(self, visitor): visitor.visit_form(self) class Field(FormElement): """abstract base class for individual user-entry fields, each having a name, a description, and an optional value.""" def __init__(self, name, description, value = None): self.name = name self.description = description self.value = value class TextField(Field): """an field.""" def __init__(self, name, description, size, value = None): Field.__init__(self, name, description, value) self.size = size def accept_visitor(self, visitor): visitor.visit_textfield(self) class SelectField(Field): """a tag.""" def __init__(self, name, description, options, value = None): Field.__init__(self, name, description, value) self.options = options for o in self.options: o.parent = self if o.id == self.value: o.selected = True def accept_visitor(self, visitor): visitor.visit_selectfield(self) class OptionField(FormElement): """an tag. contains a parent attribute that points to a field.""" def __init__(self, value): self.value = value def accept_visitor(self, visitor): visitor.visit_submit(self)

Step 2: Visitor Interface - This class provides the layout of a visitor object:

<&|formatting.myt:code, syntaxtype='python', title='form.py' &><%text> class FormVisitor: def visit_form(self, form):pass def visit_textfield(self, textfield):pass def visit_selectfield(self, selectfield):pass def visit_option(self, option):pass def visit_submit(self, submit):pass

Step 3: HTML Rendering Components - a set of Myghty methods that will render the HTML of each form element:

<&|formatting.myt:code, title="formfields.myc"&><%text> <%doc> each method renders one particular HTML form element, as well as tags to provide basic layout for each. the "form" and "select" methods are component calls with content, to render the form elements within them. <%method form> <%args>form
<% m.content() %>
<%method textfield> <%args>textfield <% textfield.description %>: <%method submit> <%args>submit <%method select> <%args>select <% select.description %> <%method option> <%args>option

Step 4: Module Component - ties it all together. The FormGenerator serves as the traversal unit for the Form object, as well as the visitor implementation itself. The visit_form and visit_selectfield methods both involve contained formelements, and the corresponding myghty components are component calls with content; so for those, a content function is created to handle the embedded content.

<&|formatting.myt:code, syntaxtype='python', title='form.py'&><%text> def renderform(self, m, form, **params): form.accept_visitor(FormGenerator.RenderForm(m)) class RenderForm(FormVisitor): def __init__(self, m): self.m = m def visit_form(self, form): def formcontent(): for element in form.elements: element.accept_visitor(self) self.m.execute_component("formfields.myc:form", args = dict(form = form), content=formcontent) def visit_textfield(self, textfield): self.m.comp("formfields.myc:textfield", textfield = textfield) def visit_selectfield(self, selectfield): def selectcontent(): for element in selectfield.options: element.accept_visitor(self) self.m.execute_component("formfields.myc:select", args = dict(select = selectfield), content=selectcontent) def visit_option(self, option): self.m.comp("formfields.myc:option", option = option) def visit_submit(self, submit): self.m.comp("formfields.myc:submit", submit = submit)

Step 5: Fire it Up - this myghty template actually calls the renderform Module Component.

<&|formatting.myt:code, title="registration.myt"&><%text> <%doc> an example firstname/lastname/occupation registration form. <%python> import form registerform = form.Form('myform', 'register.myt', elements = [ form.TextField('firstname', 'First Name', size=50), form.TextField('lastname', 'Last Name', size=50), form.SelectField('occupation', 'Occupation', options = [ form.OptionField('skydiver', 'Sky Diver'), form.OptionField('programmer', 'Computer Programmer'), form.OptionField('', 'No Answer'), ] ), form.SubmitField('register') ] ) Welcome to the Skydivers or Computer Programmers Club ! <& MODULE:form:renderform, form = registerform &> myghty-1.1/doc/content/otherblocks.myt0000644000175000017500000001510210501064115017153 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="otherblocks", description="Other Blocks", &>

Myghty has some miscellaneous <% "<%xxxx>" %> blocks, whose contents are less than pure Python. The most important is the <% "<%flags>" %> section, since it controls several important behaviors of a component.

<&|doclib.myt:item, name="flags", description="<%flags>", escapedesc=True &>

%flags is used to specify special properties of the component, which are significant to the execution of the component in the Myghty environment. Some flags can be inherited from a parent supercomponent, and overridden as well. Current flags are:

  • <&|formatting.myt:codeline&>inherit = "/path/to/template" - used in a file-based component only, this flag indicates that this component should inherit from the given template file. This argument can also be None to indicate that this component should not inherit from anything, even the standard autohandler. This flag only applies to the component file that it appears in, and does not affect any super- or subclass components. See the section <&formatting.myt:link, path="inheritance", text="Inheritance"&> for details.

  • <&|formatting.myt:codeline&>autoflush = [True|False] - will override the configuration parameter <&|formatting.myt:codeline&>auto_flush for this individual component. This is mostly useful for a particularly large top-level component, or a filter component that requires the full output text in one chunk. When a comopnent has autoflush disabled, it essentially means that a buffer is placed between the component's output and the ultimate output stream. As a result, the delivery of any component within a buffered component is also going to be buffered - this includes when a superclass component calls <&|formatting.myt:codeline&>m.call_next() or for any subcomponent or method call.

    This flag is also inherited from a superclass component, such as the autohandler. See the section <&formatting.myt:link, path="filtering_autoflush", &> for information on auto_flush.

  • <&|formatting.myt:codeline&>trim = ["left"|"right"|"both"] - provides automatic left and/or right whitespace trimming for the output of a component. This is the equivalent of a <% "<%filter>" %> section utilizing <&|formatting.myt:codeline&>string.strip() combined with <&|formatting.myt:codeline&>autoflush=False. This allows component source code to be laid out on multiple lines without the whitespace surrounding the component output being presented in the final output. For information on trim, see the section <&formatting.myt:link, path="filtering_filtering_trim", &>.

  • <&|formatting.myt:codeline&>use_cache - Enables caching for the component. See <&formatting.myt:link, path="caching"&> for further cache options.

  • <&|formatting.myt:codeline&>encoding = ["utf-8" | "latin-1" | etc ] - Specifies the character encoding of the component's text file (such as utf-8, etc.). This encoding goes directly to the Python "magic comment" at the top of the generated Python component, so any character set is supported by Myghty templates.

  • <&|formatting.myt:codeline&>persistent = [True|False] - overrides the per-interpreter setting of <&formatting.myt:link, path="params", param="delete_modules"&> to specify that this file-based component should or should not specifically have it's module removed from sys.modules when it has fallen from scope. A file-based component that contains class definitions which may remain active after the component itself has fallen out of use may want to do this.

Flags may be specified via the <% "<%flags>" %> tag, or for subcomponents and methods may be specified as inline attributes within the <% "<%def> or <%method>" %> tags. The <% "<%flags>" %> tag may appear anywhere in a component, or even across multiple %flags sections, and will still take effect before a component is actually executed.

Example: the <% "<%flags>"%> tag:

<&|formatting.myt:code&><%text> <%flags> inherit = None autoflush = True <%python scope="init"> ...

Example: subcomponent flags as attributes:

<&|formatting.myt:code&><%text> <%method getlink trim="both"> <%init> lala <&|doclib.myt:item, name="attr", description="<%attr>", escapedesc=True &>

%attr simply sets some attributes on the component. These attributes are accessed via the <&formatting.myt:link, path="components_members", member="attributes"&> member of the component. Below, two attributes are accessed from a component to provide style information. One uses <& formatting.myt:link, path="request_members", member="current_component"&>, and the other uses <& formatting.myt:link, path="request_members", member="base_component"&>. The difference is the former always returns the very same component that is currently executing, whereas the latter corresponds to the component at the bottom of the inheritance chain:

<&|formatting.myt:code&><%text> <%attr> bgcolor = "#FF0000" fgcolor = "#FA456A" # ...

In both cases, if the attribute does not exist in the current component or within futher inheriting child components, it will then traverse upwards via parent components to locate the attribute. For more information on parent/child relationships between components, see the section <&formatting.myt:link,path="inheritance"&>.

<&|doclib.myt:item, name="text", description="<%text>", escapedesc=True &>

%text surrounds a block where Myghty will not parse for control lines, line comments, substitutions, or any other Myghty tag. This allows the placement of free text which is intended to print as is, even if it may contain characters that are normally significant to the Myghty lexer. The %text tag is also essential for writing documentation that illustrates examples of Myghty (or similar) code.

<&|doclib.myt:item, name="doc", description="<%doc>", escapedesc=True &>

%doc is simply a comment string - the content inside of a <% "<%doc>" %> section is discarded upon compilation. Comments can also be stated on a per-line basis using a '#' character as the very first character on a line, as described in <&formatting.myt:link, path="embedding_comment" &>.

myghty-1.1/doc/content/params.myt0000644000175000017500000007611010501064115016125 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="parameters", description="Index of Configuration Parameters", header="Appendix" &>

Configuration parameters in Myghty are basically a big dictionary of data that is initially passed to the constructor for myghty.interp.Interpreter, which represents the entry point for running a Myghty template. Upon construction, the Interpreter instantiates all the other objects it needs, such as the Compiler, the Resolver, and a prototype Request object (a prototype is an object that is used as a template to make copies of itself), passing in the same hash of configuration parameters. Each object takes what it needs out of the dictionary.

If you are writing your own interface to Interpreter, such as a standalone or simple CGI client, you can pass one or more of these values programmatically to the constructor of the Interpreter. If running from mod python, values can be specified in the http.conf file using the format Myghty<Name>, where Name is the parameter name in InitialCaps format (i.e., data_dir becomes DataDir).

Note also that there are configuration parameters specific to the caching mechanism, listed in <&formatting.myt:link, path="caching_options"&>, as well as session specific parameters listed in <&formatting.myt:link, path="session_options"&>.

<&|formatting.myt:paramtable&> <&|formatting.myt:param, name="allow_globals", classname="Compiler", type="list of strings", users="developers" &> A list of global variable names that will be compiled into Myghty templates, and will be expected to be provided upon template execution. The variable "r", representing the Apache Request, is delivered to templates via this parameter. To add your own variables, specify their names in this list, and also specify it as a key in the RequestImpl argument global_args to provide the values. See the section <&formatting.myt:link, path="globals_globalcustom", text="Make your Own Friends"&> for details. <&|formatting.myt:param, name="attributes", classname="Interpreter", type="dictionary", users="all" &> A dictionary of data that will be stored by the Interpreter used throughout a particular Myghty application. This is a way to set per-application global variables. The values are accessible via the interpreter.attributes dictionary.

<&|formatting.myt:code, syntaxtype="python"&><%text> i = interp.Interpreter(attributes = {'param': 'value'}) Accessing attributes: <&|formatting.myt:code, syntaxtype="python"&><%text> # call interpreter off request, # and get an attribute p = m.interpreter.attributes['param'] # call interpreter off component, # and set an attribute self.interpreter.attributes['param'] = 'value'

Interpreter attributes can be compared to component <&formatting.myt:link, path="components_members", member="attributes"&>, as well as request <&formatting.myt:link, path="request_members", member="attributes"&>.

<&|formatting.myt:param, name="auto_flush", classname="Request", type="boolean", default="False", users="developers" &>

Whether or not <&|formatting.myt:codeline&>m.write calls will be automatically flushed to the final output stream as they are called, or will be buffered until the end of the request. Autoflush being on makes things a little more tricky, as you can't do any kind of page redirects (neither external nor internal) once the content starts coming out. Error messages become messy as well since they are similar to an internal redirect.

Autoflush can be set in a variety of ways and is described fully in the section <&formatting.myt:link,path="filtering_autoflush"&>.

<&|formatting.myt:param, name="auto_handler_name", classname="Interpreter", type="string", default="autohandler", users="administrators" &> The name of the file used as a global page wrapper, defaults to 'autohandler'. See the section <&formatting.myt:link, path="specialtempl_autohandler", text="autohandlers" &> for information on autohandlers. <&|formatting.myt:param, name="cache_debug_file", classname="Cache", type="file object", version="0.94", default="None", users="optimizers" &> If pointing to a Python file object, container operations performed by the caching system will be logged, allowing the viewing of how often data is being refreshed as well as how many concurrent threads and processes are hitting various containers. When running with ApacheHandler or CGIHandler, this parameter can be set to the standard Apache log via the parameter <&|formatting.myt:codeline&>log_cache_operations. <&|formatting.myt:param, name="code_cache_size", classname="Interpreter", type="integer", default="16777216", users="optimizers" &> Size of the cache used by Interpreter to cache loaded component objects, measured in bytes. The cache works on a "least recently used" scheme. This value has a great impact on performance, as too large a value can use up lots of memory for a very large site, and too small results in excessive "swapping" of components in and out of memory. Cache operations can be logged via <&formatting.myt:link, param="debug_file" &>. <&|formatting.myt:param, name="compiler", classname="Interpreter", type="object", default="myghty.compiler.Compiler", users="hackers" &> An instance of <&|formatting.myt:codeline&>myghty.compiler.Compiler used to generate object files. Not much reason to play with this unless you are seriously hacking your own engine. The default Compiler object will receive its optional parameters from the argument list sent to the constructor for <&|formatting.myt:codeline&>Interpreter. <&|formatting.myt:param, name="component_root", classname="Resolver", type="string or list", default="None", users="all" &>

This parameter is almost always required; it is the filesystem location, or list of locations, with which to locate Myghty component files.

The two formats of component_root are as follows:

Single string

<&|formatting.myt:code, syntaxtype="python"&>component_root = "/web/sites/mysite/htdocs"

List of hashes

<&|formatting.myt:code, syntaxtype="python"&>component_root = [ {'main':"/web/sites/mysite/htdocs"}, {'components':"/web/sites/mysite/components"}, {'alt':"/usr/local/lib/special-comp"} ]

The single string specifies one root for all component calls, which will be assigned the identifying key "main". The list of hashes specifies the keys and paths for a collection of locations which will be searched in order for a component. This allows you to have one file tree that is web-server accessible files, and another file tree that contains files which can only be used as embedded components, and can't be accessed by themselves. The key names are used to uniquely identify a component by both its location and its name, such as "main:/docs/index.myt".

For examples of component_root configuration, see the section <& formatting.myt:link, path="configuration" &>. For advanced options on file-based resolution, see <& formatting.myt:link, path="resolver" &>.

<&|formatting.myt:param, name="data_dir", classname="Interpreter", type="string", default="None", users="all" &> Directory to be used to store compiled object files (.py files and .pyc files), lock files used for synchronization, as well as container files which store data for the component caching system and the session object. A non-None value implies True for the "use_object_files" setting. Compiled object files are stored in the obj directory, and cache/session files are stored underneath container_XXX directories. <&|formatting.myt:param, name="debug_elements", classname="Interpreter", type="list", version="0.97alpha3", default="[]", users="optimizers" &>

A list of string names that refer to various elements that can send debug information to the Interpreter's debug output. An example with all allowed names:

<&|formatting.myt:code, syntaxtype="python"&> debug_elements = [ # all resolution of components 'resolution', # inspection of component objects and modules loaded into memory and later garbage-collected 'codecache', # inspection of the generation and compilation of components 'classloading', # cache operations, reloading of cached data 'cache', ] <&|formatting.myt:param, name="debug_file", classname="Interpreter", type="file object", version="0.93b", default="stderr", users="optimizers" &>

References a Python file object; if non-None, the Interpreter will send debugging information to this file. Note that line breaks are not sent by default; use code such as myghty.buffer.LinePrinter(sys.stderr) to wrap the file in a line-based formatter.

When running the Interpreter in an HTTP context, various implementations of HTTPHandler supply their own stream for debugging information, such as the ApacheHandler which supplies the Apache error log as a filehandle.

As of version 0.97alpha3, to actually enable debug logging requires the <&formatting.myt:link, param="debug_elements"&> parameter to be set up.

<&|formatting.myt:param, name="default_escape_flags", classname="Compiler", type="list", default="None", users="developers" &> This is a list of escape flags that will be automatically applied to all substitution calls, i.e. <%text><% string %>. See <&formatting.myt:link, path="filtering_escaping"&> for more information on content escaping. <&|formatting.myt:param, name="dhandler_name", classname="Request", type="string", default="dhandler", users="administrators" &> The name of the file used as a directory handler, defaults to 'dhandler'. See the section <&formatting.myt:link, path="specialtempl_dhandler", text="dhandlers" &> for information on directory handlers. <&|formatting.myt:param, name="disable_unicode", classname="Request, Compiler", type="boolean", default="False", users="developers", version="1.1" &> Disable the new unicode support features. If set, all strings are assumed to be strs in the system default encoding (usually ASCII). See the section on <&formatting.myt:link, path="unicode"&> for further details. <&|formatting.myt:param, name="dont_auto_flush_filters", classname="Request", type="boolean", default="False", users="developers" &> If auto_flush is turned on, this option will keep auto_flush turned off for components that use <% "<%filter>" | h %> sections. This is useful if you have filters that rely upon receiving the entire block of text at once. See the section <&formatting.myt:link, path="filtering_filtering", text=m.apply_escapes("<%filter>", ['h'])&> for more information on filters. <&|formatting.myt:param, name="encoding_errors", classname="Request", type="string", default="strict", users="developers", version="1.1" &>

Sets the initial value for the encoding_errors of requests, which, in turn, determines how character set encoding errors are handled.

Some choices are:

strict
Raise an exception in case of an encoding error.
replace
Replace malformed data with a suitable replacement marker, such as "?".
xmlcharrefreplace
Replace with the appropriate XML character reference.
htmlentityreplace
Replace with the appropriate HTML character entity reference, if there is one; otherwise replace with a numeric character reference. (This is not a standard python encoding error handler. It is provided by the mighty.escapes module.)
backslashreplace
Replace with backslashed escape sequence.
ignore
Ignore malformed data and continue without further notice.

See also the section on <&formatting.myt:link, path="unicode"&>, and the Python codecs documentation.

<&|formatting.myt:param, name="error_handler", classname="RequestImpl", type="function", default="None", users="developers" &>

A function object that will be passed all exceptions and given the chance to handle them before regular processing occurs. The format of the function is:

<&|formatting.myt:code, syntaxtype="python"&> def handle_error(e, m, **params): # custom error handling goes here # ... # return False to continue handling error, True to disregard return False

Where 'e' is a myghty.exception.Error object and m is the request object. The global variable 'r' will also be passed if running in an HTTP context. The function should return True to stop all further error handling, or False to continue error handling as normal.

The request object, while it is the same instance used to handle the request initially, no longer will have any buffered content and will also not have a current component instance. It is safe to call subrequests from it, allowing the construction of custom error pages.

See also <&formatting.myt:link, param="raise_error"&> to simply raise an error outside the Request and bypass all internal and custom error handling.

<&|formatting.myt:param, name="escapes", classname="Interpreter", type="dict", default="{'h':html_escape, 'u':url_escape}", users="developers" &>

Use this parameter to define your own custom escaping functions that are available within substitutions, as well as <&|formatting.myt:codeline&>m.apply_escapes(). Within a dictionary key/value pair, the key is the identifying character, and the value is a reference to a single-argument function that will be called with the string with which to apply the text escape. The function should return the filtered text.

Escaping is described fully at <& formatting.myt:link, path="filtering_escaping" &>.

<&|formatting.myt:param, name="generator", classname="Compiler", type="object", default="myghty.objgen.PythonGenerator", users="hackers" &> An instance of <&|formatting.myt:codeline&>myghty.objgen.ObjectGenerator that is used to generate object files. Again, for the brave hacker, you can write your own generator and output object files in whatever language you like. <&|formatting.myt:param, name="global_args", classname="RequestImpl", type="dictionary", users="developers" &> A list of global argument names and values that will be available in all template calls. See the section <&formatting.myt:link, path="globals_globalcustom", text="Make your Own Friends"&> for details. <&|formatting.myt:param, name="interpreter_name", classname="HTTPHandler", type="string", users="administrators" &>

Specifies the name of the Myghty interpreter that should be used. All HTTP handlers modules maintain a dictionary of HTTPHandler instances, each of which references a Myghty interpreter, keyed off of a name. When this name is explicitly specified, that particular handler will be created if it doesnt exist and then used.

This option is currently most useful for use within multiple Apache directives, so that different sets of configuration parameters can be used for different directives, without requiring the use mod_python's multiple Python interpreters feature. See the example in <&formatting.myt:link, path="configuration_mod_python_multiple" &>.

<&|formatting.myt:param, name="lexer", classname="Compiler", type="object", default="myghty.lexer.Lexer", users="hackers" &> An instance of <&|formatting.myt:codeline&>myghty.request.Lexer, used to parse the text of template files. <&|formatting.myt:param, name="log_cache_operations", classname="ApacheHandler or CGIHandler", version="0.93b", type="boolean", default="False", users="optimizers" &> Deprecated; use <&formatting.myt:link, param="debug_elements" &> instead. <&|formatting.myt:param, name="log_component_loading", classname="ApacheHandler or CGIHandler", version="0.93b", type="boolean", default="False", users="optimizers" &> Deprecated; use <&formatting.myt:link, param="debug_elements" &> instead. <&|formatting.myt:param, name="log_errors", classname="HTTPHandler", type="boolean", default="False", users="administrators" &> Specifies whether or not component errors should receive a full stack trace in the Apache error log, standard error output of CGIHandler, or other HTTP-specific logging system. If false, component errors still produce a single-line description of the error in the log. See also <&formatting.myt:link, param="output_errors"&>. If <&formatting.myt:link, param="raise_error"&> is set to true, no logging of errors occurs. <&|formatting.myt:param, name="max_recursion", classname="Request", type="integer", users="optimizers", default="32" &> The highest level of component recursion that can occur, i.e. as in recursive calls to a subcomponent. <&|formatting.myt:param, name="module_components", classname="ResolveModule", type="array of hashes", users="all" &> This parameter is used for resolving <& formatting.myt:link, path="modulecomponents" &> using regular expressions which are mapped to function, class or class instance objects. See <&formatting.myt:link, path="modulecomponents_resolution_module_components"&> for examples.

<&|formatting.myt:param, name="module_root", classname="ResolvePathModule", type="array of hashes", users="all" &> This parameter is used for resolving <& formatting.myt:link, path="modulecomponents" &> using file paths which map to the locations of python files and the attributes within. See <&formatting.myt:link, path="modulecomponents_resolution_module_root"&> for examples.

<&|formatting.myt:param, name="module_root_adjust", classname="ResolvePathModule", type="callable", users="all", version="0.99b" &> This parameter is used in combination with <& formatting.myt:link, param="module_root" &> to specify a callable that will translate an incoming URI before being resolved to a path-based module. See <&formatting.myt:link, path="modulecomponents_resolution_module_root"&> for more detail.

<&|formatting.myt:param, name="out_buffer", classname="DefaultRequestImpl", type="object", users="developers" &> A Python file object, or similar, with which to send component output. See the <&formatting.myt:link, path="configuration", text="Configuration"&> section for examples. <&|formatting.myt:param, name="output_encoding", classname="Request", type="string", default="sys.getdefaultencoding()", users="developers", version="1.1" &> Sets the initial value for the output_encoding of requests. The default value is python's system default encoding (usually ASCII.) See the section on <&formatting.myt:link, path="unicode"&> for further details. <&|formatting.myt:param, name="output_errors", classname="ApacheHandler or CGIHandler", version="0.93b", type="boolean", default="True", users="administrators" &> Specifies whether or not component errors with full stack trace should be reported to the client application. If false, component errors will produce a 500 Server Error. See also <&formatting.myt:link, param="log_errors"&>. If <&formatting.myt:link, param="raise_error"&> is set to True, this parameter is overridden and client will receive a 500 server error (unless the error is caught by an external handler). <&|formatting.myt:param, name="parent_request", classname="Request", type="object", users="hackers" &> This parameter specifies the parent request when making subrequests, and is automatically provided. For more information see <&formatting.myt:link, path="components_programmatic", text="Programmatic Interface"&>. <&|formatting.myt:param, name="path_moduletokens", classname="ResolvePathModule", type="list of strings", default="['index']", users="all", version="0.99b" &>

Used by <&formatting.myt:link, param="module_root"&> to specify default path tokens that should be searched in module attribute paths. See <&formatting.myt:link, path="modulecomponents_resolution_module_root_options"&> for details. <&|formatting.myt:param, name="path_stringtokens", classname="ResolvePathModule", type="list of strings", default="[]", users="all" , version="0.99b" &>

Used by <&formatting.myt:link, param="module_root"&> to specify default path tokens that should be searched in file paths. See <&formatting.myt:link, path="modulecomponents_resolution_module_root_options"&> for details. <&|formatting.myt:param, name="path_translate", classname="Resolver", type="list of tuples, or a callable", users="administrators" &> A list of regular-expression translations that will be performed on paths before they are used to locate a component and/or module component. This can be useful for complicated web server configurations where aliases point to component roots, or specifying a default document for path requests. It looks as follows: <&|formatting.myt:code, syntaxtype="python"&><%text> path_translate = [ # convert /store/* to /shop/* (r'^/store/(.*)', r'/shop/\1'), # convert /documents/* to /docs/* (r'^/documents/', '/docs/'), # convert /foo/bar/ to /foo/bar/index.myt (r'/$', '/index.myt'), ]

As of revision 0.99b, the argument to path_translate can alternatively be specified as a callable, which converts its given URI to the translated value:

<&|formatting.myt:code, syntaxtype="python"&><%text> def my_translate(uri): return "/doc/" + uri path_translate = my_translate

Note that the <&formatting.myt:link, path="request_members", member="request_path"&> member of the request object will reference the original path before translation, whereas the <&formatting.myt:link, path="components_members", member="path" &> of the component served will contain the translated path (for file-based components).

More detail about path translation can be found in <&formatting.myt:link, path="resolver" &>.

<&|formatting.myt:param, name="python_post_processor", classname="Compiler", type="function", users="hackers" &> References a function that will be invoked with the full text of each individual unit of Python code created during component generation (i.e., creation of .py files). The returned string will be used as the Python code that is actually placed within the generated .py file. Also see <&formatting.myt:link, param="text_post_processor" &>. <&|formatting.myt:param, name="python_pre_processor", classname="Compiler", type="function", users="hackers" &> References a function that will be invoked with the full text of a template's source file before it is parsed. The returned string will then be used as the source to be parsed. <&|formatting.myt:param, name="raise_error", classname="Request", version="0.97a", type="boolean", default="False", users="developers" &>

Indicates if errors should be raised when they occur, or be handled by error handling functionality. If set to True, no error logging, friendly client response, or custom internal error handler function will happen. Instead, an external error handler can be used. This parameter overrides invalidates the functionality of <&formatting.myt:link, param="log_errors"&>, <&formatting.myt:link, param="output_errors"&> and <&formatting.myt:link, param="error_handler"&>.

This parameter allows the entire interpreter.execute() or handler.handle() call to be wrapped in a try: / except: block, where the error can be handled externally to all Myghty functionality.

<&|formatting.myt:param, name="request", classname="Interpreter", type="object", default="myghty.request.Request", users="hackers" &> An instance of <&|formatting.myt:codeline&>myghty.request.Request used as a prototype to generate new requests. Its context-specific behavior is supplied by a separate instance of <&|formatting.myt:codeline&>myghty.request.RequestImpl, so there is not much reason to change this either. <&|formatting.myt:param, name="request_impl", classname="Request", type="object", default="myghty.request.DefaultRequestImpl", users="hackers" &> An instance of <&|formatting.myt:codeline&>myghty.request.RequestImpl that will be used either as a prototype to create new RequestImpls per request, or can be passed per individual interpreter execution. RequestImpl tells <&|formatting.myt:codeline&>Request how it should interact with its environment. Take a look at <&|formatting.myt:codeline&>ApacheHandler.ApacheRequestImpl, <&|formatting.myt:codeline&>CGIHandler.CGIRequestImpl, and <&|formatting.myt:codeline&>myghty.request.DefaultRequestImpl for examples. <&|formatting.myt:param, name="request_path", classname="Request", type="string", default="myghty.request.Request", users="developers" &> Sets the initial request path for this request. In most cases, this is set automatically by simply calling a request with a string-based component name, or with a file-based component which can return its originating path. However, in the case of a request being called with a memory or module-based component, the path defaults to None and must be set by the calling function if it is to be referenced by components. <&|formatting.myt:param, name="require_publish", classname="ResolvePathModule", type="boolean", default="False", users="all", version="0.99b" &>

Indicates that callables located via <&formatting.myt:link, param="module_root"&> resolution require an attribute named 'public' set to True attached to them, in order to allow access. See <&formatting.myt:link, path="modulecomponents_resolution_module_root_options"&> for details. <&|formatting.myt:param, name="resolver", classname="Interpreter", type="object", default="myghty.resolver.FileResolver", users="hackers" &> An instance of <&|formatting.myt:codeline&>myghty.resolver.Resolver that is used to locate component files. Both ApacheHandler and CGIHandler have their own instances of <&|formatting.myt:codeline&>Resolver but don't yet do anything special. If you wanted some kind of special file resolution behavior, you can swap in your own subclass of <&|formatting.myt:codeline&>Resolver. <&|formatting.myt:param, name="resolver_strategy", classname="Resolver", type="list", users="developers"&> Allows configuration of the rules used in component resolution. See the section <&formatting.myt:link, path="resolver" &> for details. <&|formatting.myt:param, name="source_cache_size", classname="Interpreter", type="integer", default="1000", users="optimizers" &> Size of the cache used by Interpreter to cache the source of components. See <&formatting.myt:link, param="use_static_source"&> for a description of source caching, and see code_cache_size for a description of the LRU caching scheme. <&|formatting.myt:param, name="text_post_processor", classname="Compiler", type="function", users="hackers" &> References a function that will be invoked with the full text of each individual unit of output text created during component generation (i.e., creation of .py files). The returned string will be used as the output text that is actually placed within the generated .py file. Also see <& formatting.myt:link, param="python_post_processor"&>. <&|formatting.myt:param, name="use_auto_handlers", classname="Interpreter", type="boolean", default="True", users="developers" &> Whether or not to use autohandlers. See the section <&formatting.myt:link, path="specialtempl_autohandler", text="autohandlers" &> for more information. <&|formatting.myt:param, name="use_dhandlers", classname="Request", type="boolean", default="True", users="developers" &> Whether or not to use directory handlers. See the section <&formatting.myt:link, path="specialtempl_dhandler", text="dhandlers" &> for more information. <&|formatting.myt:param, name="use_object_files", classname="Interpreter", type="boolean", default="True if data_dir is not null", users="optimizers" &> Indicates whether or not components should be compiled into module files on the filesystem, or into strings held in memory. A value of None for the <&formatting.myt:link, param="data_dir"&> parameter will have the same effect. There is no advantage whatsoever to having components compiled in memory, and startup performance will be degraded for repeated calls to the same component. It might be useful if you are running the interpreter in a "one shot" fashion where there is no need to have compiled object files lying around. <&|formatting.myt:param, name="use_session", classname="ApacheHandler", type="boolean", users="developers" &> The additional global variable s to reference the Myghty session, or alternatively the mod_python 3.1 session, is turned on when this option is set to True. See the section <&formatting.myt:link, path="session" &> for details. <&|formatting.myt:param, name="use_source_line_numbers", classname="Compiler", type="boolean", users="hackers" &> Whether or not to put source line numbers in compiled object files. This is used to generate friendly stack traces upon errors (when that feature is complete). <&|formatting.myt:param, name="use_static_source", classname="Interpreter, Resolver", type="boolean", default="False", users="optimizers" &> This parameter, when set to True:

  • Enables URI caching within the resolver, and/or turns on all configured URICache() resolution rules, so that visiting a URI a second time does not produce any filesystem checks, within the currently cached set of URIs.
  • Disables repeated last-modification time checks on all template files and module-component files
  • Disables last-modification time checks on compiled template object files against the template they were generated from, which in effect disables re-compilation of templates even across server restarts, unless the object file is removed from the obj directory underneath the configured <& formatting.myt:link, param="data_dir"&> directory
Use use_static_source on production servers where the site's structure is not being changed, to prevent auto-recompilation of modified templates, and to prevent reloads of modified module component modules. There is both a performance increase due to fewer filesystem checks, and also a stability increase, as no Python modules are dynamically reloaded within a running server. Dynamic reloads in Python are somewhat error-prone, particularly if a module with a base class changes, and a corresponding subclass in a different, non-reloaded module attempts to instantiate itself. myghty-1.1/doc/content/programmatic.myt0000644000175000017500000003403410501064115017326 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="configuration_programmatic", description="Programmatic Configuration", &>

This section illustrates how to link the Myghty interpreter to a Python application. If you already have a basic configuration running and want to jump into programming templates, skip ahead to <& formatting.myt:link, path="embedding" &>.

The central request-handling unit within Myghty is called the Interpreter. When an application is set up to handle a request and pass control onto the Interpreter, it is referred to as an external controller. In the model-view-controller paradigm, the application is the controller, the template called by the Interpreter is the view, and whatever data is passed from the controller to the template is the model.

Currently, all Myghty installations, except for mod_python, require at least a rudimentary external controller, which serves as the place for configuration directives to be set up and specified to the Myghty environment. Since a lot of configuration directives reference datastructures or functions themselves, a Python module is the most natural place for this to happen.

A more elaborate external controller may be the point at which an application passes control from its application code and business logic onto a view layer, served by a Myghty Interpreter and corresponding template files. This may be appropriate for an application that is already handling the details of its HTTP environment, if any, or any application that just needs basic template support.

In contrast to the "external controller" concept is the internal controller, which is Python code that executes after the Interpreter begins handling the request, forwarding data onto template-driven components at the end of its request cycle. Myghty provides a construct for this kind of controller described in <& formatting.myt:link, path="modulecomponents" &>. It is recommended that an application that aims to be designed in a full MVC (model-view-controller) style, particularly one that runs in an HTTP environment, place its controller code into Module Components, and only application configuration code and externally-dependent code into a single external controller module. That way the majority of an application's controller code is written in an environment-neutral fashion, and also has the full benefits of the Myghty resolution and request environment available to it.

The two general categories of external controller are:

  • <& formatting.myt:link, path="configuration_programmatic_httphandler" &>
  • <& formatting.myt:link, path="configuration_programmatic_interpreter" &>
<&|doclib.myt:item, name="httphandler", description="Chaining to HTTPHandler", &>

For applications running in an HTTP environment, chaining to HTTPHandler is the usual method. An HTTPHandler object has awareness of HTTP requests for any of four different environments, which are WSGI, CGI, mod_python, and the standalone handler. It can construct the appropriate HTTP-aware environment to be delivered to the Interpreter and ultimately onto templates, which then receive an implementation-neutral interface to that environment via the r global object.

All HTTP-based Myghty configurations utilize a subclass of myghty.http.HTTPHandler.HTTPHandler to serve requests. Each HTTP environment has its own module: myghty.http.ApacheHandler, myghty.http.CGIHandler, myghty.http.HTTPServerHandler and myghty.http.WSGIHandler, containing the classes ApacheHandler, CGIHandler, HSHandler, and WSGIHandler, respectively.

As of version 0.98, the recommended way to chain to an HTTPHandler is to first get an instance to a handler via the function get_handler(), and then execute a request on that handler via the method handle(). In previous versions, a single function handle() is used which combines the argument sets of both functions; this function is still available.

The get_handler function retrieves a handler from a registry based on the given interpreter_name, which defaults to 'main' or in the case of Apache uses the http.conf variable "MyghtyInterpreterName". Application-scoped configuration variables are sent to this method which are used to create the initial HTTPHandler object. Once created, subsequent calls with the same interpreter_name will return the same HTTPHandler instance.

HTTPHandler then supplies the method handle() to handle requests, which accepts configurational parameters on a per request basis. Common per-request parameters include the component path or object to serve, the request arguments, and out_buffer to capture component output.

Each handler module has a slightly different calling signature, illustrated below.

<&|doclib.myt:item, name="ApacheHandler", description="ApacheHandler", &>

The ApacheHandler handles mod_python requests. It retrieves configuration via directives found in the host's httpd.conf file in the manner detailed in <&formatting.myt:link, path="configuration_mod_python"&>, and by default serves the component referenced by the request.uri data member. Configuration parameters sent programmatically override those found in the Apache configuration.

In the example below, a file "myghty_handler.py" is created, which contains a very simple mod_python handler that "chains" to the myghty ApacheHandler.

<&|formatting.myt:code, syntaxtype="python", title="myghty_handler.py" &> import myghty.http.ApacheHandler as ApacheHandler from mod_python import apache def handle(request): handler = ApacheHandler.get_handler(request) return handler.handle(request)

A configuration for the above file is similar to a straight Apache configuration. Since the ApacheHandler created in the myghty_handler.py file contains no configuration at all, all of its options will come from the httpd.conf directives:

<&|formatting.myt:code, syntaxtype="conf" &> AddHandler mod_python .myt PythonHandler myghty_handler::handle PythonPath "sys.path+[r'/path/to/my/libraries']" PythonOption MyghtyComponentRoot r"/path/to/htdocs" PythonOption MyghtyDataDir r"/path/to/writeable/data/directory/"

When we take the above handler file and add configuration directives programmatically, they will override those named in the httpd.conf file:

<&|formatting.myt:code, syntaxtype="python", title="myghty_handler.py" &> import myghty.http.ApacheHandler as ApacheHandler from mod_python import apache def handle(request): handler = ApacheHandler.get_handler(request, data_dir='/usr/local/web/data', component_root=[ {'components':'/usr/local/web/components'}, {'templates':'/usr/local/web/templates'} ]) return handler.handle(request)

Another example, overriding the application's data directory, and also the request's component path and request arguments:

<&|formatting.myt:code, syntaxtype="python" &> import myghty.http.ApacheHandler as ApacheHandler from mod_python import apache def handle(request): handler = ApacheHandler.get_handler( request, interpreter_name = 'mainhandler', data_dir = '/data' ) return handler.handle(request, component = 'mypage.myt', request_args = {'foo' : 'bar'} )

The above example also specifies the <&formatting.myt:link, path="parameters", param="interpreter_name"&> configuration parameter which identifies which ApacheHandler is returned by get_handler. If this parameter is not specified, it defaults to "main".

<&|doclib.myt:item, name="CGIHandler", description="CGIHandler", &>

The CGI handler retreives its environment information via the cgi.FieldStorage() method as well as os.environ. Configuration parameters sent programmatically override those found in the CGI environment. By default, CGIHandler serves the component indicated by the environment variable PATH_INFO.

<&|formatting.myt:code, syntaxtype="python", title="CGI application chaining to CGIHandler.handle() function" &> #!/usr/local/bin/python import myghty.http.CGIHandler as CGIHandler # serve the component based on PATH_INFO CGIHandler.get_handler( component_root='/path/to/htdocs', data_dir='/path/to/datadirectory' ).handle() <&|doclib.myt:item, name="WSGIHandler", description="WSGIHandler", &>

WSGIHandler works similarly to CGIHandler. Its r object maintains a reference to both the <&|formatting.myt:codeline&>environ and <&|formatting.myt:codeline&>start_response members. These members are used to extract the core data members of the r object, such as <&|formatting.myt:codeline&>headers_in, <&|formatting.myt:codeline&>method, etc.

When running under WSGI, the environ and start_response variables are available via: <&|formatting.myt:code, syntaxtype="python"&> # WSGI environment variables r.environ r.start_response

<&|formatting.myt:code, syntaxtype="python", title="WSGI application chaining to WSGIHandler.handle() method" &> import myghty.http.WSGIHandler as WSGIHandler def application(environ, start_response): return WSGIHandler.get_handler( component_root='/path/to/htdocs', data_dir='/path/to/datadirectory').handle(environ, start_response)

Also supported with WSGI is the standard application(environ, start_response) function, which takes in all application and request-scoped configuration parameters via the environ argument:

<&|formatting.myt:code, syntaxtype="python", title="WSGI application via application()" &> import myghty.http.WSGIHandler as WSGIHandler params = dict( interpreter_name='main_wsgi', component_root='/path/to/htdocs', data_dir='/path/to/datadirectory' ) def application(environ, start_response): environ['myghty.application'] = params environ['myghty.request'] = {'component' : 'mycomponent.myt'} return WSGIHandler.application(environ, start_response) <&|doclib.myt:item, name="interpreter", description="Chaining to Interpreter", &>

The Myghty Interpreter object is the underlying engine that creates Myghty requests and executes them, supplying the necessary services each request needs. The Interpreter can be programmatically instantiated with a full set of configuration parameters and used directly.

While the advantage of using an HTTPHandler in an application is that Myghty components are aware of HTTP-specific information, such as the mod_python request object, HTTP headers, the httpd.conf configuration, etc., an application can also bypass all this by chaining directly to the Interpreter, if templates do not need HTTP awareness and the configuration can be programatically specified.

<&|formatting.myt:code, syntaxtype="python", title="mod_python handler chaining to Interpreter" &> import myghty.interp as interp from mod_python import apache # set up a singleton instance of Interpreter interpreter = interp.Interpreter( data_dir = './cache', component_root = './doc/components', ) def handle(request): # set up some data to send to the template data = { 'foo': 'bar', 'info' : get_info() } # call a template interpreter.execute('mytemplate.myt', request_args = data, out_buffer = request)

In the above approach, Myghty components are unaware of the HTTP environment, meaning there is no r global variable, and also can't make HTTP return calls or location redirects. A greater amount of responsibility is placed within the controller.

<&|doclib.myt:item, name="standalone", description="Configuring a Standalone Application", &>

Chaining to the Interpreter directly also allows Myghty to run outside of a web context completely, within any Python application. It doesnt really need much configuration to run in "barebones" mode; the two most common features are the component root, which specifies one or more roots to search for input files, and the optional data directory, where it will store compiled python modules. Without the data directory, the compiled python modules are created in memory only.

A standlone application to run Myghty templates looks like this:

<&|formatting.myt:code, syntaxtype="python" &> #!/usr/local/bin/python import myghty.interp as interp import sys interpreter = interp.Interpreter( data_dir = './cache', component_root = './doc/components', out_buffer = sys.stdout ) # component name is relative to component_root interpreter.execute('/index.myt')

The <&|formatting.myt:codeline&>execute method of Intepreter takes optional parameters and sends them off to a newly created <&|formatting.myt:codeline&>Request object. You can specify any of the constructor arguments used by <&|formatting.myt:codeline&>Request or its embedded helper object <&|formatting.myt:codeline&>RequestImpl in the <&|formatting.myt:codeline&>execute call of Interpreter, which will override the values given in the constructor to Interpreter. The below example sends a buffer to each request with which to capture output, via the <&formatting.myt:link, path="parameters", param="out_buffer" &> parameter:

<&|formatting.myt:code, syntaxtype="python" &> # write .myt file to a corresponding .html file file = open('index.html', 'w') interpreter.execute('/index.myt', out_buffer = file) file.close() myghty-1.1/doc/content/request.myt0000644000175000017500000005707310501064115016341 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="request", description="The Request"&>

The Request, available in all Myghty templates as the global variable m, is the central processor of a component and all the calls it makes to other components. Following are all the useful methods and members of a request.

Note: many request methods have been converted to straight data members as of version 0.96, such as get_base_component(), get_request_args(), get_dhandler_arg(), etc. These methods are still present for existing applications but their use is deprecated. Their functionality can be located in the Request Members section.

<&|doclib.myt:item, name="methods", description="Request Methods"&> <&|formatting.myt:paramtable&> <&|formatting.myt:function_doc, name="abort", arglist=['status_code = None']&>

Halts the currently executing component, and optionally sends the specified HTTP status code. Any content that is still in the output buffer is discarded. If the status code is None, the request simply ends with no further output, and the HTTP code 200:OK is returned as a normal request.

Abort can be used to send any HTTP status code desired to the client. However, to send a soft redirect via a subrequest, or a hard redirect via the "Location:" header along with 302:Moved, its easier to use m.send_redirect(), described below.

<&|formatting.myt:function_doc, name="apply_escapes", arglist=['text', 'flags'] &> programmatically applies escape flags to the given text, the same as they would be used inside of a substitution. text is a string to be processed, and flags is an array of single-character escape flags. The two built in flags are "u" for url escaping and "h" for HTML escaping, and user-defined flags are supported as well. See <&formatting.myt:link, path="filtering_escaping", &> for more information on escape flags. <&|formatting.myt:function_doc, name="cache", alt="get_cache", arglist=['component=None', 'cache_type=None', 'cache_container_class=None', "**params"] &>

Returns the cache for the current component, or if called with an optional component argument, returns the cache for the specified component. If the cache does not yet exist within the scope of the current request, it will be created using the given list of arguments, else the arguments are disregarded and the previous request-scoped cache interface is returned (note that multiple instances of a cache interface may all reference the same underlying data store, if they are of the same type). The arguments here override those set in the global configuration for the interpreter, but will usually not override the arguments specified in the %flags section of the component.

The cache type defaults to 'dbm' if a data_dir is in use, else uses 'memory'. Additional arguments can be added that are specific to MemoryContainer, DBMContainer, or other custom types of containers. See the section <&formatting.myt:link, path="caching" &>. <&|formatting.myt:function_doc, name="cache_self", arglist=['component=None', 'key=None', 'retval=None', 'cache_type=None', 'cache_container_class=None', "**params"] &>

Caches the current component (or the specified component's) output and return value. All arguments are optional. The component argument is a component argument specifying a component other than the currently executing component. The key argument is the key to store the information under, which defaults to the string "_self". The key can be modified if you want to cache the comopnent's output based on some kind of conditional information such as component arguments. The retval argument is a value object which can be used to receive the return value of the component. The rest of the parameters are the same as those for the cache/get_cache method.

The function returns True indicating that the retval has been populated and the component's output has been flushed to the output, or False indicating that the value is not yet cached (or needs refreshing) and execution should continue on into the rest of the component. See <&formatting.myt:link, path="caching" &> for an example.

<&|formatting.myt:function_doc, name="callers", arglist=["index = None"] &>

Returns a single component from the call stack object indicated by the given index, or if None returns the full list of components stored within the current call stack.

<&|formatting.myt:function_doc, name="caller_args", arglist=["index = None"] &>

Returns a single dictionary of component arguments from the call stack object indicated by the given index, or if None returns the full list of argument dictionaries sent to each component stored within the current call stack.

<&|formatting.myt:function_doc, name="call_content" &>

Similar to the content() method used within a component call with content, except does not push a new buffer onto the buffer stack. When combined with programmatic pushing and popping buffers onto the request via push_buffer() and pop_buffer(), it can be used for complicated content-grabbing schemes. For advanced usage.

<&|formatting.myt:function_doc, name="call_next", arglist=["**params"] &> Within a chain of inheriting pages, calls the next component in the inheritance chain, i.e. the "wrapped" component. See <&formatting.myt:link, path="inheritance", &> for more information on the inheritance chain. Optional **params specify arguments that will override the subclass-component's default argument list on a parameter-by-parameter basis. <&|formatting.myt:function_doc, name="call_stack", arglist=["index = None"] &>

Provides access to the current call stack. The call stack is a list of StackFrame objects, which are internal representations of components calling each other. Each StackFrame object contains the fields "component", "args", "base_component", "content", "is_call_self", and "filter". The given index refers to what index within the stack to return; if it is None, the entire call stack is returned.

<&|formatting.myt:function_doc, name="comp", arglist=['component', '**params'] &> calls a component, subcomponent, or method. See <&formatting.myt:link, path="components_programmatic_comp", &>. <&|formatting.myt:function_doc, name="component_exists", arglist=['path'] &> returns True if the specified file-based component, identified by its URI, can be located. Performs the full filesystem check even if the requested component is already loaded, therefore repeated calls to this method can get expensive. <&|formatting.myt:function_doc, name="content", arglist=[] &> returns the content of an embedded component call in the context of a component call with content. See <&formatting.myt:link, path="components_callwithcontent", &>. <&|formatting.myt:function_doc, name="create_subrequest", arglist=['component', "resolver_context='subrequest'", '**params'] &> Creates a subrequest, which is a child request to this one. The request can then serve a new component call, which will be serviced within the same output stream as this one. By default, the subrequest is configured in much the same way as the originating request. The **params dictionary can be used to override individual configuration parameters; any per-request parameter listed in <&formatting.myt:link, path="parameters", &> is relevant.

Users will usually prefer to use the methods make_subrequest() or subexec() since they are more compact for typical component-call scenarios. See <&formatting.myt:link, path="components_programmatic_subrequests", &>.

Also see <&formatting.myt:link, path="resolver_context"&> for information on the resolver_context parameter.

<&|formatting.myt:function_doc, name="decline"&> Used by a dhandler to forego processing a directory request, and to bump the interpreter up to the enclosing directory, where it will search again for a dhandler. See <&formatting.myt:link, path="specialtempl_dhandler"&>. <&|formatting.myt:function_doc, name="execute"&> Executes this request. Chances are you are already inside the execute call, which can only be called once on any given request object. However, if you make yourself a subrequest, then you can call execute on that object. See <&formatting.myt:link, path="components_programmatic_subrequests", &>. <&|formatting.myt:function_doc, name="execute_component", arglist=["component", "args = {}", "base_component = None", "content = None", "store = None", "is_call_self = False"] &> The base component-calling method, at the root of all the other component calling methods (i.e. execute(), comp(), subexec(), etc.). The richer argument set of this method is useful for calling components with an embedded content call, and also for supplying a custom output buffer to capture the component output directly. The parameters are:
  • component - the string name/URI of the component, or an actual component argument
  • args - dictionary of arguments sent to the component. this is supplied to the component via ARGS as well as via the component-scoped <% "<%args>" %> tag.
  • base_component - the base component for the component call. This is best left as None, where it will be determined dynamically.
  • content - reference to a function that will be attached to the m.content() method inside the component call. this is used to programmatically call a "component call with content". The function takes no arguments and is normally a "closure" within the span of the code calling the enclosing component.
  • store - reference to a buffer, such as a file, StringIO, or myghty.buffer object, where the component's output will be sent.
  • is_call_self - used internally by the m.call_self() method to mark a stack frame as the original "self caller". Just leave it as False.

Example: component call with content:

<&|formatting.myt:code&><%text> <%def testcall> this is testcall. embedded content: [ <% m.content() %> ] <%python> def mycontent(): m.write("this is the embedded content.") m.execute_component('testcall', args = {}, content = mycontent)

This will produce:

<&|formatting.myt:code&><%text> this is testcall. embedded content: [ this is the embedded content. ] <&|formatting.myt:function_doc, name="fetch_all" &> Returns a list of the full "wrapper chain" of components inheriting each other, beginning with the current inheriting component and going down to the innermost component. Each element is popped of the request's internal wrapper stack. See <&formatting.myt:link, path="inheritance"&> for details on component inheritance. <&|formatting.myt:function_doc, name="fetch_component", arglist=['path', "resolver_context='component'", 'raise_error=True']&>

Returns a component object corresponding to the specified component path. This path may be:

  • a URI path relative to one of the application's configured component roots, with an optional clause ":methodname" indicating a method to be called from the resulting file-based component.
  • one of the special names SELF, PARENT, REQUEST, with an optional clause ":methodname" indicating a method to be called from the resulting file-based component, or the current subcomponent's owner in the case of a subcomponent that calls SELF.
  • the name of a method component stated in the form "METHOD:modulename:classname".
When the component is located, it will be compiled and/or loaded into the current Python environment if it has not been already. In this way it is the Myghty equivalent of "import".

If the component cannot be located, it raises a <&|formatting.myt:codeline&>myghty.exceptions.ComponentNotFound error, unless raise_error is False, in which case the method returns None.

More details on how to identify components can be found in <&formatting.myt:link, path="components"&>.

Also see <&formatting.myt:link, path="resolver_context"&> for information on the resolver_context parameter.

<&|formatting.myt:function_doc, name="fetch_lead_component", arglist=['path']&>

Fetches a component similarly to fetch_component(), but also resolves directory requests and requests for nonexistent components to dhandlers.

If the component cannot be located, or a useable dhandler cannot be located because either no dhandler was found or all valid dhandlers have already sent decline() within the span of this request, a <&|formatting.myt:codeline&>myghty.exceptions.ComponentNotFound error is raised.

<&|formatting.myt:function_doc, name="fetch_module_component", arglist=['moduleorpath', 'classname', 'raise_error=True']&>

Fetches a module component. The value of "moduleorpath" is either a string in the form "modulename:classname", or it is a reference to a Python module containing the desired component. In the latter case, the argument "classname" must also be sent indicating the name of the desired class to load.

Using this method with a string is the same as using the fetch_component() method using the syntax "METHOD:modulename:classname".

If the module component cannot be located, a <&|formatting.myt:codeline&>myghty.exceptions.ComponentNotFound error is raised, unless raise_error is False, in which case the method returns None.

<&|formatting.myt:function_doc, name="fetch_next" &> Within a chain of inheriting pages, this method returns the next component in the inheritance chain (also called the "wrapper" chain), popping it off the stack. The component can then be executed via the <&|formatting.myt:codeline&>m.comp() or similar method. See the section <&formatting.myt:link, path="inheritance", &>. <&|formatting.myt:function_doc, name="get_session", arglist=['**params']&>

Creates or returns the current Myghty session object (or optionally the mod_python Session object). If the session has not been created, the given **params are used to override defaults and interpreter-wide configuration settings during initialization.

Details on the Session are described in <&formatting.myt:link,path="session"&>.

<&|formatting.myt:function_doc, name="has_content"&> Inside of a subcomponent or method call, returns whether or not the component was a component call with content. See the section <&formatting.myt:link, path="components_callwithcontent", &>. <&|formatting.myt:function_doc, name="has_current_component"&> Returns True if this request has a currently executing component. This becomes true once a request's execute() method has been called. <&|formatting.myt:function_doc, name="instance"&> This static method returns the request corresponding to the current process and thread. You can access the current request in a globally scoped block via this method. See <&formatting.myt:link, path="scopedpython_global"&>. <&|formatting.myt:code&><%text> <%python scope="global"> req = request.instance() <&|formatting.myt:function_doc, name="is_subrequest"&> Returns True if the currently executing request is a subrequest. See the section <&formatting.myt:link, path="components_programmatic_subrequests", &>. <&|formatting.myt:function_doc, name="log", arglist=['message'] &> Logs a message. The actual method of logging is specific to the underlying request implementation; for standalone and CGI it is the standard error stream, for mod_python it is a [notice] in the Apache error log. <&|formatting.myt:function_doc, name="make_subrequest", arglist=['component', '**params'] &> Creates a new subrequest with the given component object or string path component and the request arguments **params. The returned request object can then be executed via the execute() method. See <&formatting.myt:link, path="components_programmatic_subrequests", &>. <&|formatting.myt:function_doc, name="scomp", arglist=['component', '**params']&> Executes a component and returns its content as a string. See <&formatting.myt:link, path="components_programmatic_scomp", &>. <&|formatting.myt:function_doc, name="send_redirect", arglist=['path', 'hard=True']&> Sends a hard or soft redirect to the specified path. A hard redirect is when the http server returns the "Location:" header and instructs the browser to go to a new URL via the 302 - MOVED_TEMPORARILY return code. A soft redirect means the new page will be executed within the same execution context as the current component. In both cases, the current component is aborted, and any buffered output is cleared. For this reason, if auto_flush is enabled (which it is not by default), you would want to call this method, particular the soft redirect, only before any content has been output, most ideally in an %init section. If auto_flush is disabled, you can call either version of this method anywhere in a component and it will work fine. <&|formatting.myt:function_doc, name="set_output_encoding", arglist=['encoding', 'errors="strict"']&>

Change the output_encoding, and, optionally, the encoding error handling strategy.

Generally, you will not want to change the output encdoing after you have written any output (as then your output will be in two different encodings --- probably not what you wanted unless, for example, you are generating Mime multipart output.)

See the section on <&formatting.myt:link, path="unicode"&> for further details.

<&|formatting.myt:function_doc, name='subexec', arglist=['component', '**params']&> All-in-one version of <&|formatting.myt:codeline&>make_subrequest and <&|formatting.myt:codeline&>m.execute(). See <&formatting.myt:link, path="components_programmatic_subrequests", &>. <&|formatting.myt:function_doc, name='write', args=['text'], alt='out'&> Writes text to the current output buffer. If auto_flush is enabled, will flush the text to the final output stream as well. <&|doclib.myt:item,name="members", description="Request Members" &> <&|formatting.myt:paramtable&> <&|formatting.myt:member_doc, name="attributes", &>

A dictionary where request-local information can be stored. Also can be referenced by the member <&formatting.myt:link, member="notes"&>. If the request is a subrequest, this dictionary inherits information from its parent request.

<&|formatting.myt:member_doc, name="base_component" &> Represents the "effective" file-based component being serviced, from which methods and attributes may be accessed. When autohandlers or other inherited components are executing, this member always points to the file-based component at the bottom of the inheritance chain. This allows an inherited component to access methods or attributes that may be "dynamically overridden" further down the inheritance chain. It is equivalent to the special path SELF. When a method or def component is called via the path of another file-based component, base_component will change to reflect that file-based component for the life of the subcomponent call. <&|formatting.myt:member_doc, name="current_component" &> The component currently being executed. Within the body of a component, this is equivalent to self. <&|formatting.myt:member_doc, name="dhandler_path", &> In the case of a dhandler call, the request path adjusted to the current dhandler.

For information on dhandlers see <&formatting.myt:link, path="specialtempl_dhandler"&>.

<&|formatting.myt:member_doc, name="encoding_errors", &>

The current output encoding error handling strategy. This is a read-only attribute, though you may change it using the <&formatting.myt:link, path="request_methods", method="set_output_encoding" &> method.

See the section on <&formatting.myt:link, path="unicode"&> for further details.

<&|formatting.myt:member_doc, name="global_args", &>

A dictionary of custom-configured global variables to be used throughout the request. Global variables can be initialized or re-assigned here, and the new value will become available to all further component calls. Each key in this dictionary must already correspond to a value in the allow_globals configuration parameter. See <&formatting.myt:link, path="globals_globalcustom" &> for full examples.

<&|formatting.myt:member_doc, name="interpreter", &>

The Interpreter object that created this Request object.

<&|formatting.myt:member_doc, name="notes", &> A synonym for <& formatting.myt:link, member="attributes"&>. <&|formatting.myt:member_doc, name="output_encoding", &>

The current output encoding. This is a read-only attribute, though you may change it using the <&formatting.myt:link, path="request_methods", method="set_output_encoding" &> method.

See the section on <&formatting.myt:link, path="unicode"&> for further details.

<&|formatting.myt:member_doc, name="parent_request", &> In a subrequest call, refers to the immediate parent request of this request, else defaults to None. <&|formatting.myt:member_doc, name="request_args" &>

A dictionary containing the initial request arguments sent when this request was created. In an HTTP context, this usually refers to the request arguments sent by the client browser.

In the case of a subrequest (described in <&formatting.myt:link, path="components_programmatic_subrequests" &>), this member refers to the request arguments sent to the local subrequest. To get the original request arguments, use the root_request_args parameter.

For detailed description of component and request arguments, see <&formatting.myt:link, path="components_args"&>.

<&|formatting.myt:member_doc, name="request_component" &> Returns the top-level component corresponding to the current request. <&|formatting.myt:member_doc, name="request_path", &>

The original path sent to this request for resolution. In the case of a subrequest, the path sent to the subrequest.

In the case of a new request that is called with a pre-existing component object, such as in a custom application interfacing to the Myghty interpreter, the request_path defaults to None, but can also be set programmatically via the <&formatting.myt:link, path="parameters", param="request_path"&> configuration argument.

<&|formatting.myt:member_doc, name="root_request" &> Refers to the ultimate originating request in a string of subrequests. If this request is not a subrequest, this member references the current request. <&|formatting.myt:member_doc, name="root_request_args" &>

Used in subrequests, returns the request arguments sent to the ultimate root request in a chain of subrequests. For a regular request with no parent, this dictionary is synonymous with request_args.

For detailed description of component and request arguments, see <&formatting.myt:link, path="components_args"&>.

<&|formatting.myt:member_doc, name="root_request_path", &>

The request path for the original root request, in a chain of subrequests.

myghty-1.1/doc/content/resolver.myt0000644000175000017500000005624010501064115016505 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="resolver", description="Advanced Resolver Configuration"&> <&|doclib.myt:item, name="intro", description="Introduction" &>

With version 0.97 comes a new and highly configurable URI resolution engine. This engine is responsible for converting the URI address of a component into a ComponentSource object, from which an actual Component is created. Originally, components were only retrieved from one or more file-based component roots, configured by <& formatting.myt:link, path="parameters", param="component_root" &>. Special logic existed for locating <& formatting.myt:link, path="specialtempl_dhandler" &> and <& formatting.myt:link, path="specialtempl_autohandler" &> components as well, and the resolution of URIs could be cached (i.e. <& formatting.myt:link, path="parameters", param="use_static_source" &>). Myghty added the ability to load module-based components based on regular expressions (<& formatting.myt:link, path="parameters", param="module_components" &>), and also to translate incoming URIs via <& formatting.myt:link, path="parameters", param="path_translate"&>.

With the new resolution engine, it is now possible to:

  • Change the order of all resolution steps, or remove unneeded resolution steps. The steps that can be controlled are component root lookup, module component lookup, path translation, dhandler resolution, autohandler resolution (also called 'upwards resolution'), and URI caching (static source).
  • Create conditional resolution rules or rule chains. Built-in conditional rules allow matching the URI with a regular expression, or matching the "resolution context" of the incoming request. Built-in resolution contexts include request, subrequest, component, and inherit. User-defined resolution contexts can be created as well and specified in component lookup calls and also as the default context to be used in a subrequest.
  • Control the point at which the caching of URIs occurs, by placing a special rule above only those rules whose results should be reused, or to enable caching of just a single rule.
  • Inspect the full series of steps involved in a URI resolution via logging, regardless of whether or not it succeeded. This is crucial for developing custom rule-chains.
  • Add special behavior to file-based resolution, so that the conversion from a URI to a filesystem path can be more than just a straight path concatenation of the component root and URI.
  • Retrieve an re.Match object that is created when a URI matches the regular expression used by a module component. Capturing parenthesis can also be added to the regular expressions, whose results will also be available in the resulting Match object.
  • Create custom resolution rules using small and simple classes that can easily coexist with the built-in rules.
<&|doclib.myt:item, name="step1", description="Basic Example"&> Lets start with some component code that wants to locate another component: <&|formatting.myt:code, syntaxtype="python"&> comp = m.fetch_component("/training/index.myt")

In the above line, the code is using the request to fetch the component referenced by the uri "/training/index.myt". The process of looking up a component corresponding to a uri is called resolution.

When the resolver attempts to locate the uri, it executes a series of resolver rules specified within the configuration option resolver_strategy, which is represented as a list. This list contains instances of one or more myghty.resolver.ResolverRule subclasses, each of which represents a single rule along with configurational arguments for that rule. A list of ResolverRule objects to be executed in order is also referred to as a chain. A resolver strategy for the above search might look like:

<&|formatting.myt:code, syntaxtype="python"&> from myghty.resolver import * resolver_strategy = [ ResolveFile( {'htdocs' : '/web/htdocs'} ) ]

Above, the resolver strategy has only one rule, an instance of ResolveFile. This rule searches through one or more component roots given as constructor arguments and concatenates the incoming URI to each root, until one of them matches an actual file located on the filesystem. When a match is found, the rule cancels any further rule processing, and returns a result object from which the component can be loaded. If no match is found, the rule passes control onto the next rule, or if no further rules exist, the resolver determines that the component cannot be located. Since in the above example we have only one rule, if the file /web/htdocs/training/index.myt doesn't exist, the fetch_component call will raise a ComponentNotFound exception.

If no arguments are given to the constructor, ResolveFile instead uses the value of <&formatting.myt:link, path="parameters", param="component_root"&> as its list of roots. From this one can see that when a Myghty installation specifies the component_root configuration parameter, this rule is implicitly activated behind the scenes.

The rule classes themselves require the symbols from the resolver package to be imported, as in from myghty.resolver import *. These symbols are also available in mod_python configuration within PythonOption directives, as of version 0.97a or the current CVS version.

A few more rules can be added above, which allow more options for how this uri can be resolved:

<&|formatting.myt:code, syntaxtype="python"&> from myghty.resolver import * resolver_strategy = [ ResolveModule( {r'/user/.*' : 'myapp.UserManager'}, {r'/admin/.*' : 'myapp.Administration'}, ), PathTranslate( (r'/training/' , r'/newmembers/'), (r'/$', '/index.myt'), ), ResolveFile( {'htdocs' : '/web/htdocs'} ) ]

Two more rules have been added. ResolveModule will be the first to attempt resolving the uri, and it will attempt to match the uri against a series of regular expressions that each refer to a module component. Module components are described in the section <&formatting.myt:link, path="modulecomponents"&>. As our uri does not match either regular expression, the ResolveModule rule will give up, and pass control onto the next rule in the chain. The ResolveModule rule, if instantiated with no arguments, will instead use the value of <&formatting.myt:link, path="parameters", param="module_components"&> as its list. As with ResolveFile, when a Myghty installation specifies the module_components configuration parameter, this rule is also implicitly activated.

Next is the PathTranslate rule. This rule is a uri processing rule which itself will never return a result, but rather changes the incoming uri and then passes the modified uri onto the remaining rules in the chain. PathTranslate executes a series of regular expression search-and-replace operations on the uri before passing it on, which are specified as a list of two-element tuples. PathTranslate, when given no arguments, will use the value of <&formatting.myt:link, path="parameters", param="path_translate"&> as its list.

We have specified a path translation rule that all uris starting with "/training/" should be changed to reference the same file within "/newmembers/" instead. Secondly, we specify that any uri which is a directory request, i.e. uris that end with a slash, should have the default file "index.myt" appended to it. Our uri does match the first regular expression, so upon matching it will be converted into /newmembers/index.myt. We then arrive back at our ResolveFile rule, where either the existing file /web/htdocs/newmembers/index.myt will be used as the source of the resolved component, or if no such file exists a ComponentNotFound exception will be raised.

Behind the scenes, the myghty.resolver.Resolver object is invoked to resolve the incoming uri, and when located it returns an instance of myghty.resolver.Resolution, which in turn contains an instance of myghty.csource.ComponentSource. Whereas the Resolution instance describes just one of potentially many resolution paths the same component, the ComponentSource instance is the only object that describes location information for the component exactly.

<&|doclib.myt:item, name="realthing", description="The Real Set of Rules"&>

The previous section illustrated the three basic ResolverRules that are behind the configuration parameters component_root, module_components, and path_translate. In fact, these three rules also have an ordering that is used by default, and there are also several more rules installed by default which accomplish some other important tasks. The default strategy is:

<&|formatting.myt:code, syntaxtype="python"&> from myghty.resolver import * resolver_strategy = [ PathTranslate(), ResolveDhandler(), URICache(), ResolveUpwards(), ResolveModule(), ResolvePathModule(), ResolveFile() ]

Our familiar rules PathTranslate, ResolveModule and ResolveFile are all present, and as they are specified with no constructor arguments they will use the values of their correspoinding configuration parameters. Also one can see that path translation happens at the very front of everything before other resolution starts. ResolvePathModule is also a new rule which corresponds to the configuration parameter <& formatting.myt:link, path="modulecomponents_resolution_module_root" &>.

Three new rules are introduced in this chain, ResolveDhandler, URICache, and ResolveUpwards. All have two things in common which are different from the first three rules: they all are conditional rules that do not necessarily get activated, and also they are rollup rules which cannot resolve a component on their own, but rather rely upon the full list of rules that occur directly below them in order to retrieve results.

The ResolveDhandler rule is activated when the request looks for a top level component, with the option to serve a not-found component or directory request as a dhandler. After not matching any resolution rules or file lookups for the given URI, it looks instead for /path/dhandler, and searches "upwards" through successive parent paths to resolve a dhandler. The rules below are executed repeatedly with each new URI until a match is made, or no more path tokens exist. Additionally, any resolved dhandlers that have been declined by the current request are also bypassed.

The ResolveUpwards rule is similar to the dhandler rule, except it is activated when a component searches for its inherited autohandler. This rule also searches upwards through successive parent paths to locate the module or file. In fact this rule can be used for any kind of upwards search but normally is used for autohandler only.

The URICache rule caches the results of all rules below it, keyed against the incoming URI. If the component is located, the resulting ComponentSource object is cached. If it is not located, the resulting ComponentNotFound condition is cached. Whether or not this rule is enabled is based on the value of <&formatting.myt:link, path="parameters", param="use_static_source" &>. URI caching has the effect of disabling repeated resolution/filesystem lookups for components that have already been located, as well as components that were not found when searched. URICache also takes an optional constructor parameter source_cache_size, which indicates that this URICache should use its own cache of the given size, separately from the per-interpreter source cache. Through custom configuration of URICache rules, parts of a site can be configured as static source and other parts can be configured as being more dynamically alterable.

Since URICache caches data based only on the URI, it will complain if you try to put a dhandler rule below it. This is because the dhandler rule does not necessarily return the same result for the same URI each time, as its upwards logic is conditionally enabled. In theory, ResolveUpwards should have this effect as well, but since normal usage will use ResolveUpwards for all autohandlers and nothing else, it by default will not complain about a ResolveUpwards rule. If you construct ResolveUpwards using ResolveUpwards(enable_caching = False), then the URICache rule will complain about it.

URICache also has an additional parameter which allows it to cache the results of just a single rule, instead of all rules below it:

<&|formatting.myt:code, syntaxtype="python"&> from myghty.resolver import * resolver_strategy = [ URICache(rule = ResolveModule()), ResolveFile() ]

Above, only the results of ResolveModule will be cached. ResolveFile will be executed every time it is called.

<&|doclib.myt:item, name="conditionals", description="Conditionals, Groups and ConditionalGroups"&>

Here is an example of conditional and group-based rules:

<&|formatting.myt:code, syntaxtype="python"&> from myghty.resolver import * resolver_strategy = [ ConditionalGroup(context = 'subrequest', rules = [ ResolveDhandler(), ResolveUpwards(), ResolveFile(), NotFound() ]), Conditional(regexp = '/login/.*', rule = ResolveModule({'.*' : 'myapp:LoginHandler'})), AdjustedResolveFile( adjust = [('/docs/', '/')], ('main' : '/web/htdocs'), ('comp' : '/web/comp') ) ]

The above example routes all subrequests into a rule subchain, which resolves files only, including optional dhandler and autohandler resolution. The subchain is terminated by the rule NotFound, which always results in a ComponentNotFound error. If NotFound is not included, the resolution would fall through back into the main rule chain. Continuing on, all non-subrequest component requests will first check for the URI '/login/' which matches to a specific module component, and then onto the bottom where it does a special file resolution rule.

That last rule above is AdjustedResolveFile(), which is a subclass of ResolveFile() that performs path translation on the incoming URI before concatenating it to each of its file paths. In contrast to using PathTranslate(), the result of this translation is not propigated forward onto new requests nor is it used in the resulting component source; it is only used in the actual path concatenation.

<&|doclib.myt:item, name="context", description="Resolver Contexts"&>

The Resolver is used for all resolution of URIs into components, which is not just the URI used by a client browser, but also URIs used for all components called by a template, the inheriting component of a template, and subrequests. Custom resolver strategies allow different rules to execute based on this context, using the Conditional and ConditionalGroup rules.

A common use for a conditional rule based on resolver context is to have module components be invoked only for request-based URIs, and all internal subreqests, component calls, and templates use file-based components:

<&|formatting.myt:code, syntaxtype="python"&> from myghty.resolver import * resolver_strategy = [ ConditionalGroup(context = 'request', rules = [ ResolveModule(), # optional - dont allow requests to "fall through" # into file-based resolution NotFound() ]), ResolveDhandler(), URICache(), ResolveUpwards(), ResolveFile() ]

Above, all requests from the outside world must be resolved by module components, as the conditional is terminated by a NotFound() rule. If the NotFound() rule is omitted, incoming uris that do not match a module component path will "fall through" into the file-based rules.

The resolver context is also customizable programmatically. The request methods <&formatting.myt:link, path="request_methods", method="create_subrequest" &> and <&formatting.myt:link, path="request_methods", method="fetch_component" &> take the optional parameter resolver_context which can be any user-defined string, or one of the standard names request, subrequest, component or inherit. Any Conditional or ConditionalGroup rule which specifies the name as the value of context will be activated by this name.

<&|doclib.myt:item, name="logging", description="Resolution Logging"&> Details about a component resolution are attached to ComponentNotFound exceptions, when a page or component is not found. Additionally, if you add the configuration parameter debug_elements = ['resolution'] to your Interpreter config, the resolution of all components will be logged. This log details each step within a resolution with an identifying keyword, such as "resolvemodule:", "resolvefile:", or "dhandler". See <&formatting.myt:link, path="parameters", param="debug_elements"&> for information on debug_elements. <&|doclib.myt:item, name="custom", description="Custom Rules"&>

The basic idea of a custom rule is to subclass the ResolverRule class, overriding at least the do method, which is tasked with returning a Resolution object, which in turn contains a ComponentSource object.

Example 1 - File-based Custom Rule

<&|formatting.myt:code, syntaxtype="python"&> from myghty.resolver import * from myghty.csource import * import os class MyFileResolver(ResolverRule): """a user-defined resolver rule that looks up file-based components""" def __init__(self, file_root): """builds a new MyFileResolver.""" self.file_root = file_root def do_init_resolver(self, resolver, remaining_rules, **kwargs): """called when the MyFileResolver is established in the rule chain. 'resolver' is the instance of Resolver being used to run the rules. 'remaining_rules' is a list of all ResolverRules that occur below this rule. **kwargs receives the full dictionary of Myghty configuration parameters.""" pass def do(self, uri, remaining, resolution_detail, **kwargs): """attempts to resolve the given uri into a Resolution object. 'resolution_detail' is a list of log messages that will be appended to the debug log when resolution is being logged, or to ComponentNotFound exceptions. **kwargs contains extra resolution arguments, such as "resolver_context" and "enable_dhandler". """ if resolution_detail is not None: resolution_detail.append("MyFileResolver:" + uri) # a simple resolution. file = self.file_root + uri if os.access(file, os.F_OK): # file exists, so return a Resolution/FileComponentSource return Resolution( FileComponentSource( file_path = file, # file modification time - this is not required as of 0.99b last_modified = os.stat(srcfile)[stat.ST_MTIME], path = uri, # key to identify the 'compilation root' for the component path_id = 'myfileresolve', # unique key to identify the component id = "%s|%s" % (key, path), ), resolution_detail ) else: # file doesnt exist, so call the next resolver in the chain return remaining.next().do(uri, remaining, resolution_detail, **params)

Example 2 - Module Component Custom Rule

<&|formatting.myt:code, syntaxtype="python"&> from myghty.resolver import * from myghty.csource import * import sys, re class MyModuleResolver(ResolverRule): """a user-defined resolver rule that looks up module-based components""" def __init__(self, path): """constructs a new MyModuleResover with the given module path.""" self.path = path def do_init_resolver(self, resolver, remaining_rules, **kwargs): """initializes the MyModuleResolver.""" pass def do(self, uri, remaining, resolution_detail, context = None, **params): """resolves the given URI to a specific callable.""" if resolution_detail is not None: resolution_detail.append("MyModuleResolver: " + uri) # here we illustrate a variety of constructor arguments # for Resolution and ModuleComponentSource. if uri eq '/shopping/': # return a ModuleComponentSource with a callable inside of it # the "cache_arg" flag, new in version 0.99b, indicates that the # given argument is not changing its type (i.e. function, object, class) and can be # cached with regards to inspecting its type, parent module, etc. return Resolution( ModuleComponentSource(shopping, cache_arg=True), resolution_detail ) elif uri eq '/checkout/': # or with the standard "package.module:callable" string return Resolution( ModuleComponentSource("mypackage.mymodule:checkout", cache_arg=True), resolution_detail ) elif uri eq '/inspector/': # the Resolution object will also attach any additional **kwargs as attributes # which other components can then retreive via m.resolution return Resolution( ModuleComponentSource(inspector, cache_arg=True), resolution_detail param1 = 'inspect', param2 = 3 ) else: # no component matches, return next resolver in the chain return remaining.next().do(uri, remaining, resolution_detail, **params) <&|doclib.myt:item, name="installing", description="Installing the Rule"&>

The new rule is then installed by specifying it within resolver_strategy, as in this example which first resolves via MyModuleResolver, and then resolves file components via MyFileResolver, plugged into the standard URICache/dhandler/autohandler chain:

<&|formatting.myt:code, syntaxtype="python"&> from myghty.resolver import * resolver_strategy = [ MyModuleResolver('/usr/local/mymodules'), ResolveDHandler(), URICache(), ResolveUpwards(), MyFileResolver('/web/htdocs') ] myghty-1.1/doc/content/scopedpython.myt0000644000175000017500000003446310501064115017366 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="scopedpython", description="Scoped <%python> Blocks", &>

The <% "<%python>" %> tag is also capable of specifying Python code that occurs within a specific scope of a template's execution, using the <&|formatting.myt:codeline&>scope attribute. Scopes are provided that specify Python code as always the first thing executed within a component, always the last thing executed, or executes once per request, once per application thread, or once per application process.

Originally, these scopes had their own specific tag names, which are still available. These names include all those that are familiar to HTML::Mason users, as well as some new names. All synonyms are noted in each section.

<&|doclib.myt:item, name="component", description="Component Scope", escapedesc=True &>

Usage: <% '<%python scope="component">' %>

Also called: <% "<%python>" %>

A component-scoped block is a regular block of Python that executes within the body of a template, as illustrated in the previous section <&formatting.myt:link, path="embedding_blocks"&> . Many component-scoped blocks may exist in a template and they will all be executed in the place that they occur. Component scope has two variants, init and cleanup scope, which while still being component scope, execute at specific times in a component's execution regardless of their position in a template. They are described later in this section.

<&|doclib.myt:item, name="global", description="Global Scope", escapedesc=True &>

Usage: <% '<%python scope="global">' %>

Also called: <% "<%global>" %>, <% "<%once>" %>

A global-scoped block refers to a section of Python code that is executed exactly once for a component, within the scope of its process. In reality, this usually means code that is executed each time the module is newly loaded or reloaded in the current Python process, as it is called as part of the module import process. Variables that are declared in a global-scoped block are compiled as global variables at the top of the generated module, and thus can be seen from within the bodies of all components defined within the module. Assignment to these variables follows the Python scoping rules, meaning that they will magically be copied into local variables once assigned to unless they are pre-declared in the local block with the Python keyword <&|formatting.myt:codeline&>global.

Global-scoped Python executes only once when a component module is first loaded, as part of its import. As such, it is called slightly outside of the normal request call-chain and does not have the usual access to the the built-in request-scoped variables m, r, ARGS, etc. However, you can access the Request that is corresponding to this very first execution via the static call <&|formatting.myt:codeline&>request.instance(), which will give you the current request instance that is servicing the global-scoped block.

<&|formatting.myt:code&> <%text> <%python scope="global"> # declare global variables, accessible # across this component's generated module message1 = "this is message one." message2 = "this is message two." message3 = "doh, im message three." <%python> # reference the global variables m.write("message one: " + message1) m.write("message two: " + message2) # we want to assign to message3, # so declare "global" first global message3 message3 = "this is message three." m.write("message three: " + message3)

Use a global-scoped Python block to declare constants and resources which are shareable amongst all components within a template file. Note that for a threaded environment, global-scoped section applies to all threads within a process. This may not be appropriate for certain kinds of non-threadsafe resources, such as database handles, certain dbm modules, etc. For thread-local global variable declaration, see the section <&formatting.myt:link, path="scopedpython_thread"&>, or use the Myghty <&|formatting.myt:codeline&>ThreadLocal() object described in <&formatting.myt:link, path="scopedpython_thread_threadlocal"&>. Similarly, request-scoped operation is provided as well, described in the section <&formatting.myt:link, path="scopedpython_request"&>.

A global-scoped block can only be placed in a top-level component, i.e. cannot be used within %def or %method.

<&|doclib.myt:item, name="init", description="Init Scope", escapedesc=True &>

Usage: <% '<%python scope="init">' %>

Also called: <% "<%init>" %>

Init-scoped Python is python code that is executed once per component execution, before any other local python code or text is processed. It really is just a variant of component scope, since it is still technically within component scope, just executed before any other component-scoped code executes. One handy thing about init scope is that you can stick it at the bottom of a big HTML file, or in any other weird place, and it will still execute before all the other local code. Its recommended as the place for setting up HTTP headers which can only be set before any content and/or whitespace is written (although if autoflush is disabled, this is less of an issue; see <&formatting.myt:link, path="filtering_autoflush"&> for more details).

In this example, a login function is queried to detect if the current browser is a logged in user. If not, the component wants to redirect to a login page. Since a redirect should occur before any output is generated, the login function and redirect occurs within an init-scoped Python block:

<&|formatting.myt:code&><%text> <%python scope="init"> # check that the user is logged in, else # redirect them to a login page if not user_logged_in(): m.send_redirect("/login.myt", hard = True) <%doc>rest of page follows.... .... <&|doclib.myt:item, name="cleanup", description="Cleanup Scope", escapedesc=True &>

Usage: <% '<%python scope="cleanup">' %>

Also called: <% "<%cleanup>" %>

Cleanup-scoped Python is Python code executed at the end of everything else within a component's execution. It is executed within the scope of a <&|formatting.myt:codeline&>try..finally construct so its guaranteed to execute even in the case of an error condition.

In this example, a hypothetical LDAP database is accessed to get user information. Since the database connection is opened within the scope of the component inside its init-scoped block, it is closed within the cleanup-scoped block:

<&|formatting.myt:code&><%text> <%args> userid <%python scope="init"> # open a connection to an expensive resource ldap = Ldap.connect() userrec = ldap.lookup(userid) name: <% userrec['name'] %>
address: <% userrec['address'] %>
email: <% userrec['email'] %>
<%python scope="cleanup"> # insure the expensive resource is closed if ldap is not None: ldap.close() <&|doclib.myt:item, name="request", description="Request Scope", escapedesc=True &>

Usage: <% '<%python scope="request">' %>

Also called: <% "<%requestlocal>, <%requestonce> or <%shared>" %>

A request-scoped Python block has similarities to a global-scoped block, except instead of executing at the top of the generated component's module, it is executed within the context of a function definition that is executed once per request. Within this function definition, all the rest of the component's functions and variable namespaces are declared, so that when variables, functions and objects declared within this function are referenced, they are effectively unique to the individual request.

<&|formatting.myt:code&><%text> <%python scope="request"> context = {} % context['start'] = True <& dosomething &> <%def dosomething> % if context.has_key('start'): hi % else: bye

The good news is, the regular Myghty variables m, ARGS, r etc. are all available within a request-scoped block. Although since a request-scoped block executes within a unique place in the call-chain, the full functionality of m, such as component calling, is not currently supported within such a block.

Request-scoped sections can only be used in top-level components, i.e. cannot be used within %def or %method.

<&|doclib.myt:item, name="value", description="Using Pass-By-Value", escapedesc=True &>

While the net result of a request-scoped Python block looks similar to a global-scoped block, there are differences with how declared variables are referenced. Since they are not module-level variables, they can't be used with the Python <&|formatting.myt:codeline&>global keyword. There actually is no way to directly change what a request-scoped variable points to; however this can be easily worked around through the use of pass-by-value variables. A pass-by-value effect can be achieved with an array, a dictionary, a user-defined object whose value can change, or the automatically imported Myghty datatype <&|formatting.myt:codeline&>Value(), which represents an object whose contents you can change:

<&|formatting.myt:code&><%text> <%python scope="request"> status = Value("initial status") <%python> if some_status_changed(): status.assign("new status") the status is <% status() %> <&|doclib.myt:item, name="alternative", description="Alternative - Request Attributes", escapedesc=True &>

An alternative to using request-scoped blocks is to assign attributes to the request object, which can then be accessed by any other component within that request. This is achieved via the member <&formatting.myt:link, path="request_members", member="attributes" &>:

<&|formatting.myt:code&><%text> <%python> m.attributes['whatmawsaw'] = 'i just saw a flyin turkey!' # .... somewhere in some other part of the template JIMMY: Hey maw, what'd ya see ? MAW: <% m.attributes['whatmawsaw'] %>

Produces:

<&|formatting.myt:code&> JIMMY: Hey maw, what'd ya see ? MAW: i just saw a flyin turkey!

Also see <&formatting.myt:link, path="request" &> which lists all request methods, including those used for attributes.

<&|doclib.myt:item, name="thread", description="Thread Scope", escapedesc=True &>

Usage: <% '<%python scope="thread">' %>

Also called: <% "<%threadlocal>" %>, <% "<%threadonce>" %>

A thread-scoped Python block is nearly the same as a <&formatting.myt:link, path="scopedpython_request"&> block, except its defining function is executed once per thread of execution, rather than once per request. In a non-threaded environment, this amounts to once per process. The standard global variables m, r etc. are still available, but their usefulness is limited, as they only represent the one particular request that happens to be the first request to execute within the current thread. Also, like request-scope, variables declared in a thread-scoped block cannot be changed except with pass-by-value techniques, described in <&formatting.myt:link, path="scopedpython_request_value"&>

In this example, a <&|formatting.myt:codeline&>gdbm file-based database is accessed to retreive weather information keyed off of a zipcode. Since gdbm uses the <&|formatting.myt:codeline&>flock() system call, it's a good idea to keep the reference to a gdbm handle local to a particular thread (at least, the thread that opens the database must be the one that closes it). The reference to gdbm is created and initialized within a thread-scoped block to insure this behavior:

<&|formatting.myt:code&><%text> <%python scope="thread"> # use GNU dbm, which definitely doesnt work in # multiple threads (unless you specify 'u') import gdbm db = gdbm.open("weather.dbm", 'r') <%args> zipcode temperature in your zip for today is: <% db[zipcode] %>

Use a thread-scoped block to declare global resources which are not thread-safe. A big candidate for this is a database connection, or an object that contains a database-connection reference, for applications that are not trying too hard to separate their presentation code from their business logic (which, of course, they should be).

Thread-scoped sections can only be used in top-level components, i.e. cannot be used within %def or %method.

<&|doclib.myt:item, name="threadlocal", description="Alternative to Thread Scope: ThreadLocal()", escapedesc=True &>

A possibly higher-performing alternative to a thread-scoped section is to declare thread-local variables via the automatically imported class <&|formatting.myt:codeline&>ThreadLocal(). The <&|formatting.myt:codeline&>ThreadLocal() class works similarly to a <&|formatting.myt:codeline&>Value() object, except that assigning and retrieving its value attaches the data internally to a dictionary, keyed off of an identifier for the current thread. In this way each value can only be accessed by the thread which assigned the value, and other threads are left to assign their own value:

<&|formatting.myt:code&><%text> <%python scope="global"> x = ThreadLocal() <%python> import time x.assign("the current time is " + repr(time.time())) value of x: <% x() %>

<&|formatting.myt:codeline&>ThreadLocal() also supports automatic creation of its value per thread, by supplying a pointer to a function to the parameter <&|formatting.myt:codeline&>creator. Here is the above gdbm example with <&|formatting.myt:codeline&>ThreadLocal(), using an anonymous (lambda) creation function to automatically allocate a new <&|formatting.myt:codeline&>gdbm handle per thread:

<&|formatting.myt:code&><%text> <%python scope="global"> import gdbm db = ThreadLocal(creator = lambda: gdbm.open("weather.dbm", 'r')) <%args> zipcode temperature in your zip for today is: <% db()[zipcode] %> myghty-1.1/doc/content/session.myt0000644000175000017500000003115510501064115016325 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="session", description="Session", &>

Myghty now has its own session storage class. This class offers some advantages over the mod python session, including:

  • It is independent of mod_python, so it works with either version of Apache, other web connectors, and in CGI mode
  • The file-based version keeps each user's session in separate DBM files, so no synchronization issues are present between disparate sessions, session files are small, and old sessions can be cleaned up via an external file-deletion process.
  • The session object loads its data in immediately from the persistent store and unlocks, so that a page containing multiple session-enabled requests (i.e., such as a page with IFRAMEs or image delivery servlets) can allow access to all of its sub-elements with a minimum of waiting between the concurrent requests.
  • The package can also run directly with the mod_python request, independently of Myghty.
<&|doclib.myt:item, name="usage", description="Basic Usage", &>

The session is retrieved from the request object via the get_session() method, operated upon like a dictionary, and then can have its save() method called to write its data to persistent storage:

<&|formatting.myt:code&><%text> <%python scope="init"> # get the session session = m.get_session() # add data session['key1'] = 'foo' # get data if session.has_key('user'): user = session['user'] else: user = User() session['user'] = user # save new information session.save()

The session handles generation of session IDs automatically as well as storing and retrieving them from cookies. Options exist to pass in custom session IDs, to not use cookies, to use "signed" session IDs, and to change the cookie-based session key (defaulting to myghty_session_id). It loads its data in fully when instantiated and then unlocks, so no programmatic locking or unlocking is necessary (but lock methods are available if you want the session to stay locked throughout a request).

<&|doclib.myt:item, name="options", description="Session Options", &>

Session options are specified as Myghty configuration parameters in the form <&|formatting.myt:codeline&>session_XXXX, to identify them as options being sent to the Session object. When calling the m.get_session() method, parameters may be specified with or without the "session_" prefix; they are stripped off.

The get_session method can take any of the configuration parameters that are identified below as used directly by the Session object or by the underlying Namespace objects.

<&|formatting.myt:paramtable&> <&|formatting.myt:param, name="session_cookie_expires", classname="Session", type="boolean, datetime, timedelta", users=None, default="True" &> The expiration time to use on the session cookie. Defaults to "True" which means, dont specify any expiration time (the cookie will expire when the browser is closed). A value of "False" means, never expire (specifies the maximum date that can be stored in a datetime object and uses that). The value can also be a datetime.timedelta() object which will be added to the current date and time, or a datetime.datetime() object. <&|formatting.myt:param, name="session_data_dir", classname="Session", type="string", users=None &> The data directory where sessions will be stored. If this argument is not present, the regular <&formatting.myt:link, path="parameters", param="data_dir"&> parameter is used, with the path "./sessions" appended to it. <&|formatting.myt:param, name="session_dbmmodule", classname="DBMNamespace", type="dbm module", users=None, default='anydbm' &> When dbm is used as the session type, this parameter points to a module to use for DBM support, such as gdbm, dbhash, etc. <&|formatting.myt:param, name="session_id", classname="Session", type="String", users=None &> Session id for this session. When using sessions with cookies, this parameter is not needed as the session automatically creates, writes and retrieves the value from the request. When using a URL-based method for the session, the id should be retreived from the <&formatting.myt:link, path="session_members", member="id"&> data member when the session is first created, and then used in writing new URLs. <&|formatting.myt:param, name="session_invalidate_corrupt", classname="Session", type="boolean", default="False", users=None &>

If there are any exceptions upon loading the session, the entire session will be invalidated and started clean. When object interfaces change in an application, old versions of those objects might still be present in existing session files, and exceptions will be raised when the session object tries to deserialize them into memory. Setting this to True allows those sessions to be cleaned out and started from scratch again.

This parameter should be used carefully since it can conceal real application errors in certain situations.

<&|formatting.myt:param, name="session_key", classname="Session", type="string", default="myghty_session_id", users=None &> The key that will be used as a cookie key to identify sessions. Changing this could allow several different applications to have different sessions underneath the same hostname. <&|formatting.myt:param, name="session_log_file", classname="Session", type="file", users=None &> A file or buffer object where debugging information will be sent. <&|formatting.myt:param, name="session_namespace_class", classname="Session", type="class", users=None &> A class that will be used to create the underlying NamespaceManager used by this Session, when a custom NamespaceManager implementation is being used. By default, the implementation is determined among the built-in NamespaceManagers by the <&formatting.myt:link, param="session_type"&> parameter. <&|formatting.myt:param, name="session_type", classname="Session", type="string", default="dbm", users=None &>

Type of storage used for the session, current types are "dbm" (also called "file"), and "memory". The storage uses the Container API that is also used by the cache system.

When using dbm files, each user's session is stored in its own dbm file, via the class myghty.container.DBMNamespaceManager class. To get the dbm filename used by a session, use session.namespace.file.path, or to retrieve a list of the actual files created by the particular dbm instance, use session.namespace.file.get_filenames().

<&|formatting.myt:param, name="session_secret", classname="Session", type="string", users=None &> Secret key to enable encrypted session ids. When non-None, the session ids are generated with an MD5-signature created against this value. <&|formatting.myt:param, name="session_timeout", classname="Session", type="integer", users= None &> Time in seconds before the session times out. A timeout occurs when the session has not been loaded for more than timeout seconds. <&|formatting.myt:param, name="session_use_cookies", classname="Session", type="boolean", default="True", users=None &> Whether or not to store and retrieve the session ID from the cookies present in the request. If False, the session ID must be present in the argument list to retrieve an existing session. <&|formatting.myt:param, name="use_modpython_session", classname="ApacheHandler", type="boolean", users=None &> Instructs the get_session() method, or the global variable s if configured, to return an instance of the mod_python session object instead of the Myghty session object. If this is configured, only the <&formatting.myt:link, param='session_timeout'&> parameter is supported. <&|formatting.myt:param, name="use_session", classname="ApacheHandler,CGIHandler", type="boolean", users=None &> Establishes the global variable s as a reference to the Session object. This means all requests will automatically have the session initialized and loaded. If an application has a lot of templates that dont have use for the session, this could add unnecessary overhead. <&|doclib.myt:item, name="methods", description="Session Methods", &> <&|formatting.myt:paramtable&> <&|formatting.myt:function_doc, name="delete", args=[] &> deletes the persistent storage for this session, but the session remains valid. When save() is called, the new data will be written. <&|formatting.myt:function_doc, name="invalidate", args=[] &> invalidates this session, creates a new session id, returns to the is_new state <&|formatting.myt:function_doc, name="load", args=[] &> Loads the data from this session from persistent storage and updates the last modified time of the session. This method is called automatically upon session object construction and does not need to be called explicitly. If the session's persistant storage does not exist, it will be created. If the session has not been accessed since the timeout period, the invalidate() method will be called, and the session will return to the is_new state, as well as was_invalidated. <&|formatting.myt:function_doc, name="lock", args=[] &> Locks this session against other accesses. This method is called automatically by the load() and save() methods. However, this method can be called to keep the session locked persistently until explicitly unlocked by the unlock() method. <&|formatting.myt:function_doc, name="unlock", args=[] &> Unlocks this session against other accesses. This method is called automatically by the load() and save() methods. However, this method can be called to unlock a persistent lock set up by the lock() method. <&|formatting.myt:function_doc, name="save", args=[] &> Saves the data for this session to persistent storage. This should be called whenever you know the session has been modified. <&|doclib.myt:item, name="members", description="Session Members", &> <&|formatting.myt:paramtable&> <&|formatting.myt:member_doc, name="accessed", &> The last time this session was accessed. <&|formatting.myt:member_doc, name="created", &> The time this session was created. <&|formatting.myt:member_doc, name="id", &>

The id for this session. When using cookies, this is retrieved and set within the cookie referenced by the <&formatting.myt:link, param="key"&> string. The id is automatically created when a new session is instantiated.

When regular cookies are used (the default), this value is the same value sent in the session cookie to the client. When signed cookies are enabled via the <&formatting.myt:link, path="session_options", param="session_secret"&> parameter, this id is MD5 signed against the secret to form the client cookie value.

<&|formatting.myt:member_doc, name="is_new", &> True if this session was newly created. This can be because no previous session existed, or the session existed but was invalidated, usually due to a timeout. <&|formatting.myt:member_doc, name="key", &> The key used in cookies to set this session's id. <&|formatting.myt:member_doc, name="secret", &> <&|formatting.myt:member_doc, name="timeout", &> The timeout span of this session. <&|formatting.myt:member_doc, name="was_invalidated", &> True if this session was invalidated upon opening, usually due to a timeout. <&formatting.myt:link, member='is_new'&> will always be True as well. <&|doclib.myt:item, name="object", description="Using the Session Object Standalone", &>

The session object is actually functionally independent of the rest of Myghty, and is compatible with the mod python request object directly, as well as the request emulator used by CGIHandler. To instantiate it, simply use its constructor as follows:

<&|formatting.myt:code, syntaxtype="python"&><%text> from mod_python import apache from myghty.session import Session def handle(req): session = Session(req, data_dir='/path/to/session_dir', key='user_session_id')

The full constructor signature for the Session object is as follows:

<&|formatting.myt:code, syntaxtype="python"&><%text> Session(request, id = None, use_cookies = True, invalidate_corrupt = False, type = None, data_dir = None, key = 'myghty_session_id', timeout = None, secret = None, log_file = None, **params)

Note that the parameters are the same as the configuration arguments with the prefix "session_" removed.

myghty-1.1/doc/content/specialtempl.myt0000644000175000017500000001416510501064115017326 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="specialtempl", description="Special Templates", &> <&|doclib.myt:item, name="autohandler", description="autohandler", &>

an autohandler is an optional file, by default named <&|formatting.myt:codeline&>autohandler, that will serve as the base inheriting template for all requests within a directory and its subdirectories. Inheritance is discussed in more detail in the section <&formatting.myt:link, path="inheritance", text="Inheritance"&>. The basic idea is that the autohandler template executes first; it runs some code and/or outputs some HTML, and then calls a special method to deliver the content of the requested template. When the embedded template has completed its execution, the autohandler can then output some more HTML at the bottom and/or execute cleanup code:

<&|formatting.myt:code&><%text> autohandler demo % m.call_next()

Autohandlers are searched for first in the current directory, then upwards in the enclosing directories, until the component root is reached. If more than one autohandler is found, they will all be executed within each other, with the most directory-specific autohandler executed at the innermost level. Any page or autohandler can deny the execution of an enclosing autohandler by setting the "inherit" flag to be None.

Autohandlers are ideal for standardized HTML enclosing schemes as above. There are also many more creative uses. An autohandler that automatically protects a whole directory based on a custom login scheme would look something like this:

<&|formatting.myt:code&><%text> # autohandler <%python scope="init"> # look in the apache request for some kind of # login token user = login_manager.check_login(r) if not user: # redirect out of here, the rest of the content # in this page will not be sent m.send_redirect("/login.myt", hard=True) else: # otherwise, they are ok, deliver content m.call_next() <&|doclib.myt:item, name="dhandler", description="dhandler", &>

A dhandler, otherwise known as a directory handler, is a file, by default named <&|formatting.myt:codeline&>dhandler, that serves requests for which the requested Myghty template can not be located, or for a request that names a directory by itself without a file. dhandlers are searched for similarly to autohandlers, i.e. in the innermost enclosing directory first, then upwards towards the component root. However, only one dhandler is executed at a time. The code within the dhandler has access to the request member <&formatting.myt:link, path="request_members", member="dhandler_path" &> which refers to the path information for the requested (but unlocated) component, relative to the path of the current dhandler. It also can call <&formatting.myt:link, path="request_methods", method="decline"&> which will abort the current dhandler and search up the directory tree for the next enclosing dhandler.

Dhandlers are good for special path-based requests used in places such as news sites who want to have clean URLs that have no query strings, for writing components that process the contents of a directory dynamically, such as image or filesystem browsers, or custom per-directory "file not found" handlers.

Example: content management system. A lot of news sites have fancy URLs with dates and article keywords (sometimes called slugs) specified within them. These URLs sometimes are resolved into database parameters, and the actual content is retrieved from some source that is not the same as a local file with that path scheme. This example extracts tokens from a URI and uses them as parameters to retrieve content.

<&|formatting.myt:code&><%text> # Hypothetical URL: # http://foonews.com/news/2004/10/23/aapl.myt # dhandler, inside of the web directory /news/ <%python scope="init"> import re # get path path = m.dhandler_path # get arguments from the path match = re.match(r"(\d+)\/(\d+)\/(\d+)\/(\w+)\.myt", path) if match: (year, month, day, slug) = match.groups() # look up a news article in a # hypothetical content-management database # based on this parameters from the path article = db.lookup(year, month, day, slug) else: article = None if article is None: # improper URL, or no article found m.send_redirect("article_not_found.myt", hard=False)

<% article.get_headline() %>

<% article.get_text() %>

The tricky part about a dhandler in conjunction with Apache is that the URL used to access the dhandler has to be identified by apache as a Myghty request. For a basic setup that associates *.myt with Myghty files, the URL used to access the dhandler would have to end with the string ".myt". To call dhandlers more flexibly, you would have to insure that Apache is configured to send all requests for a particular directory to Myghty for processing, using a directive like DirectoryMatch or FilesMatch.

<&|doclib.myt:item, name="modulecomponents", description="Using Module Components for autohandler/dhandler", &>

First described in <&formatting.myt:link, path="modulecomponents"&>, these components can also be used as autohandlers or dhandlers. Simply configure the Myghty environment to recognize paths with "autohandler" and/or "dhandler" as module component paths:

<&|formatting.myt:code, syntaxtype="python"&><%text> module_components = [ # configure the root autohandler to resolve to Autohandler class {r'/autohandler$' : 'modcomp:Autohandler'}, # configure all dhandlers to resolve to Dhandler class {r'.*/dhandler$' : 'modcomp:Dhandler'}, ]

In particular, code-intensive autohandlers and dhandlers such as content delivery mechanisms, translation components, or authentication controllers would be suitable as module components. Also see the section <& formatting.myt:link, path="resolver" &> for more information on the resolution of autohandlers and dhandlers.

myghty-1.1/doc/content/technical.myt0000644000175000017500000004156710501064115016604 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="technical", description="Myghty Guts (tech description)", &>

This section refers to the inner workings of Myghty and how it relates to HTML::Mason.

<&|doclib.myt:item, name="goals", description="Design Goals", &>

The main goal in developing Myghty is, port HTML::Mason to Python ! Beyond that, it is important that it retain all the central features of HTML::Mason, to make it easy for people already familiar with Mason to use Myghty as well.

But even though the language should be similar to Mason's, it also should be altered as needed to appropriately fit the Python language style, which while having some notable similarities to Perl, is still a different beast. The design of the engine should model itself after that of Mason's at a high level, but also should take advantage of the opportunities the "clean start" porting to Python gives, including code streamlining, introduction of new design patterns, and putting to use the unique programming capabilities Python affords (since after all, I wouldnt be porting to Python if I didn't think Python had some unique things to offer).

Going forward, the design and featureset of Myghty is expected to diverge from that of HTML::Mason, which is seen as more of a "starting point" rather than a continuous benchmark to follow. Since it is intended for a different audience than that of Mason, i.e. the Python developmental community, I am hoping that its development can be steered into being a useful and "first choice" enterprise web development tool specifically for Python enthusiasts.

<&|doclib.myt:item, name="templates", description="Approaches to Templating", &>

Myghty is a compiled page system, as opposed to a directly interpreted system. A compiled page system means that templates are used to generate source code files consisting a programming language that is different from that of the actual template, typically the same language as that of the language compiler itself. Whereas a directly interpreted language provides its own language interpreter for templates. Compiled page systems include Java Server Pages, Embperl, HTML::Mason, Python Server Pages. Directly interpreted systems include PHP, Webmacro, and FreeMarker (whose early development history bears the name of Myghty's author).

Each approach has its own advantages and disadvantages. A directly interpreted system has the burden of providing a full programming language written from scratch, which can result in a language that is somewhat rigid and single-purposed, but can potentially deliver extremely fast performance. A compiled page system is much simpler to write, and typically allows full blocks of the host language to be dropped into templates. This allows great flexibility, but is also known to result in templates that are more complicated than is appropriate, depending on the developer's code practices. For developers of Python code, Python is probably an elegant and powerful enough language that it deserves to live within templates unadulterated, as it does within Myghty.

<&|doclib.myt:item, name="generation", description="Generating Python Modules", &>

A Myghty template file, which might have an extension such as ".myt", is parsed by the engine and converted into a python module, which contains a subclass of the class Component. This new python module, which also gets compiled by python into a .pyc file, then forms the basis for generating the template's final output, and is only changed whenever the original template changes.

The majority of the template is encoded as Python statements inside of a class method called do_run_component(). Straight text from the template file gets distilled into write statements, lines and blocks of Python code get inserted inline as they are (with their whitespace adjusted to the appropriate context), and calls to other parts of the template or other templates get translated into calls that look like m.execute_component(). A pre-determined namespace of one or more helper variables is provided to statements within the template so that your template can attend to its daily chores, such as looking at the apache request, sending headers, ordering takeout, what have you. Other details to be dealt with by Component include handling argument lists being passed around as well as properly specifying the scoping of variables and custom methods.

<&|doclib.myt:item, name="request", description="Handling a Request", &>

A request starts first with the module that is handling the host environment. This may be the ApacheHandler, the CGIHandler, a standalone handler, or any developer-custom handler (they're pretty easy to write).

The handler then is responsible for creating an instance of <&|formatting.myt:codeline&>myghty.interp.Interpreter with the proper arguments corresponding to the execution environment, and then calling the <&|formatting.myt:codeline&>execute() method on that interpreter with request-specific arguments, which will begin the process of compiling, loading, and running a Myghty template.

The Interpreter then creates an instance of <&|formatting.myt:codeline&>myghty.request.Request which represents the template and arguments we are actually going to serve, and optionally a <&|formatting.myt:codeline&>myghty.request.RequestImpl object which provides additional environment-specific behavior to the Request object, including the request arguments and the output buffer where content will be written.

Request is responsible for looking up the requested component and also other referenced components, which may be inherit components, methods in other component files, etc. The individual lookup operations are sent back to the hosting Interpreter object, which will coordinate amongst an instance of <&|formatting.myt:codeline&>myghty.resolver.Resolver to locate files, a cache of <&|formatting.myt:codeline&>myghty.resolver.ComponentSource instances representing located template files, and a thread-local instance of <&|formatting.myt:codeline&>myghty.compiler.Compiler which is responsible for parsing in new template files and producing compiled component files from them, from which Interpreter can receive new <&|formatting.myt:codeline&>myghty.component.Component objects. <&|formatting.myt:codeline&>myghty.compiler.Compiler makes use of the <&|formatting.myt:codeline&>Lexer and <&|formatting.myt:codeline&>ObjectGenerator objects to compile templates.

The Request object executes the components it locates, passing itself as the primary namespace variable m to components, manages a call-stack of components, and manages a stack of <&|formatting.myt:codeline&>myghty.buffer.AbstractBuffer objects which capture component output. Exceptions are also handled here and processed so that they contain clean stack traces.

Request then cleans up and finishes, Interpreter finishes, and its back up to the handler to send return codes and exit its handle method.

<&|doclib.myt:item, name="modules", description="Modules", &>

To gain a deeper understanding of Myghty, heres a quick rundown of the modules and what their purpose is.

<&|doclib.myt:item, name="request", description="request", &>

The request package is home to the Request object. Request is an object that is instantiated by Interpreter to service a top-level component exactly once. It is available within a component's namespace by the special variable m. It is responsible for locating all components and inherited components referenced by its top-level component, including autohandlers and dhandlers, calling them in the appropriate order, buffering their output, and handling exceptions. It interacts with the outside world via an embedded RequestImpl object, such as DefaultRequestImpl (also included in the request package) for dealing with a command-line environment, ApacheRequestImpl for dealing with mod_python, and CGIRequestImpl which handles a CGI environment. <&|doclib.myt:item, name="interp", description="interp", &>

interp is home to the Interpreter object. Interpreter represents a Myghty application environment and its configuration parameters, and is the primary API used for executing requests within that environment. Interpreter contains logic for loading and compiling component objects at a high level, and dispatching those compiled component objects to new requests. It also deals with the filesystem structure where component files, lock files, and cache files are stored.

<&|doclib.myt:item, name="lexer", description="lexer", &>

The lexer package contains the Lexer object, which is responsible for parsing component files or in-memory strings and sending the resulting tokens to an instance of Compiler. Lexer relies heavily on the regular expression package to produce its results.

<&|doclib.myt:item, name="compiler", description="compiler", &>

Compiler represents a location for Lexer to send its parsing events, and aggregates a compiled parse tree structure for a particular compilation. Once complete it calls an instance of ObjectGenerator with the new parse tree to produce the source code of a Myghty component. The parse tree itself is an instance of Compiler.Compiled, which stores the various code blocks and supports visitor-based traversal.

<&|doclib.myt:item, name="objgen", description="objgen", &>

The home to ObjectGenerator, PythonGenerator, and PythonPrinter. ObjectGenerator is a generic interface for receiving visitor events from a compiled structure. The PythonGenerator extends ObjectGenerator to produce source code strings/files from that compiled structure. PythonPrinter handles printing of python code lines while keeping track of the indentation level, to allow all kinds of Myghty blocks to aggregate into one coherent Python source file.

<&|doclib.myt:item, name="component", description="component", &>

component contains the hierarchy of Component, FileComponent and SubComponent, which serve as the base classes for user-defined Myghty components. FileComponent contains initialization logic for handling requestlocal and threadlocal sections, as well as the initial determination of inheritance.

<&|doclib.myt:item, name="resolver", description="resolver", &>

contains the Resolver object and the various built in ResolverRule objects. The Resolver is called by the Interpreter.

<&|doclib.myt:item, name="escapes", description="escapes", &>

Contains code for handling substitution escaping, which includes URL, HTML and XML escaping.

<&|doclib.myt:item, name="buffer", description="buffer", &>

Provides interfaces for Python file objects which allow structures of buffers to be created. This is used to direct the output of components, subcomponents, filter sections, etc. to its final destination according to autoflush rules.

<&|doclib.myt:item, name="HTTPHandler", description="HTTPHandler", &>

HTTPHandler is the base class for the the HTTP-oriented handlers, which includes ApacheHandler, CGIHandler, WSGIHandler, and HTTPServerHandler.

<&|doclib.myt:item, name="util", description="util", &>

Util contains the data structure objects Value, ThreadLocal, OrderedDict as well as the cloning helper object ConstructorClone.

<&|doclib.myt:item, name="synchronizer", description="synchronizer", &>

synchronizer contains code for synchronizing read and write operations on data structures against thread mutexes and file-based mutexes.

<&|doclib.myt:item, name="cache", description="cache", &>

The cache package provides the primary API for component output caching and data structure caching.

<&|doclib.myt:item, name="container", description="container", &>

The container package provides a system of storing namespaces of key/value pairs in memory or filesystem based storage units, with additional support for DBM and memcached. It is used as the common storage engine for the cache system as well as the session object.

<&|doclib.myt:item, name="exception", description="exception", &>

The exception package defines a hierarchy of errors used across Myghty, as well as stack-trace formatting code used for logging and printing exceptions.

<&|doclib.myt:item, name="differences", description="Differences from HTML::Mason", &>

Myghty is not the same as Mason. Mason, which has established itself as a fast and efficient methodology for producing high capacity websites, has provided the initial starting point, but should the direction of Myghty go in a divergent direction, that is entirely OK. When porting Mason to Myghty, opportunities arose to rework a lot of sections in new ways, and also to take advantage of the unique features of Python. Browsing the source code of both applications can reveal a lot about this process.

As far as the user experience as well as the internal architecture, beyond the obvious "its python, not perl" as well as the python indentation scheme, the differences between Myghty and Mason continue to mount, with some significant syntactical differences as well as executional differences:

  • %python tag contains the "scope" attribute, which effectively replaces %shared, %once, %init, %cleanup.
  • %args tag contains optional "scope" attribute, to specify component or request-scoped arguments
  • %shared, %once, %init, %cleanup still exist, and %shared is also synonymous with %requestlocal and %requestonce, %once is synonymous with %global
  • thread-scoped python was added, referenced by scope="thread" or alternatively %threadlocal, %threadonce
  • the %flags section can also be specified inline inside the %method and %def tags as attributes
  • filter sections and filter functions need to return a new instance of string with the filtered content since python strings are immutable. Mason filters modify content in place.
  • the %cleanup section is guaranteed to execute even in the case of components that raise an error, as the section is executed in a <&|formatting.myt:codeline&>finally block.
  • %cleanup is also available within methods and subcomponents
  • additional component flags: <&|formatting.myt:codeline&>autoflush for fine-grained control of buffering, <&|formatting.myt:codeline&>trim for whitespace trimming of component output. full cache functionality is available via the <&|formatting.myt:codeline&>use_cache flag as well as all the options as <&|formatting.myt:codeline&>cache_XXXX.
  • the request object and component object interfaces were changed to be more pythonic, using properties as much as possible, as well as having different method names in some cases. the execution model is a little different as well.
  • module components architecture added, provides servlet-like objects to applications
  • The m and r objects are available in request-scoped (%shared) and thread-scoped blocks
  • ApacheHandler and CGIHandler were rewritten entirely. They give their behavior to the Request object via their own implementations of RequestImpl.
  • Caching API was rewritten entirely, and utilizes a genericized "container" API. It includes a mutex-based "busy lock" feature. The file storage system is DBM based. Component caching can also be configured via the %flags section of any component to avoid the more complicated programmatic interface.
  • Building off of the container API, a Session object is included in the base distribution which can also be configured as a global variable.
  • The Mason strategy of <&|formatting.myt:codeline&>Class::Container has been removed, and some of its functionality replaced with a small helper object called <&|formatting.myt:codeline&>myghty.util.ConstructorClone that is used to create clones of objects.
  • The <&|formatting.myt:codeline&>buffer object was entirely rewritten as a hierarchy of classes that resemble Python's built-in file objects, and the attachment of buffers to each other is acheived through a "decorator" pattern.
  • Object generation was completely rewritten using a "visitor" paradigm and is more decoupled from the Compiler.
  • The Request object's context-specific behavior is provided by an internally-referenced RequestImpl object, which can be replaced with different implementations without the class of Request having to change.

myghty-1.1/doc/content/unicode.myt0000644000175000017500000001647110501064115016274 0ustar malexmalex# encoding: latin1 <%flags>inherit='document_base.myt' <&|doclib.myt:item, name="unicode", description="Unicode Support"&>

Since version 1.1, Myghty provides support for writing unicode strings, and for including non-ASCII characters within component source files.

<&|doclib.myt:item, name="unicode_writes", description="What You Can Give to m.write() (and m.apply_escapes())"&>

When unicode support is enabled, you may pass either <&|formatting.myt:codeline&>unicode or plain <&|formatting.myt:codeline&>strs to <&formatting.myt:link, path="request_methods", method="write", text="m.write()"&>. <&|formatting.myt:codeline&>Strs will be interpreted according the Python's system default encoding (as returned by <&|formatting.myt:codeline&>sys.getdefaultencoding(). You may also write any other object, in which case the object will be coerced to unicode by calling <&|formatting.myt:codeline&>unicode() before it is output. There is one exception to this rule: writing a <&|formatting.myt:codeline&>None generates no output.

<&|doclib.myt:item, name="magic_comment", description="The Magic Encoding Comment"&>

If a myghty component source file contains contains characters other than those in the python system default encoding (as reported by <&|formatting.myt:codeline&>sys.getdefaultencoding() --- usually ASCII), you may so indicate this by placing a magic encoding comment at the top of the file. The exact syntax of the magic comment is essentially the same as that used by python, with the added restriction that the '#' which introduces the magic comment must start at the beginning of a line (without leading whitespace.)

The magic encoding comment has affects the interpretation of any plain text in the component source file, and the contents of any python unicode string literals. It does not have any effect on the interpretation of bytes within python plain str literals. In particular, the following is likely to generate a UnicodeDecodeError:

<&|formatting.myt:code&><%text> # encoding: latin1 # This is fine: Français % m.write(u"Français") # This is fine, too % m.write("Français") # BAD! => UnicodeDecodeError <&|doclib.myt:item, name="output_encoding", description="Controlling the Output Encoding"&>

The output encoding, and output encoding error handling strategy can be specified using the <&formatting.myt:link, path="parameters", param="output_encoding"&> and <&formatting.myt:link, path="parameters", param="encoding_errors"&> configuration parameters. It can also be changed for a specific request (or portion thereof) by calling the <&formatting.myt:link, path="request_methods", method="set_output_encoding"&> method.

Choices for the value of <&formatting.myt:link, path="parameters", param="encoding_errors"&> include:

<&|formatting.myt:codeline&>strict
Raise an exception in case of an encoding error.
<&|formatting.myt:codeline&>replace
Replace malformed data with a suitable replacement marker, such as <&|formatting.myt:codeline&>"?".
<&|formatting.myt:codeline&>xmlcharrefreplace
Replace with the appropriate XML character reference.
<&|formatting.myt:codeline&>htmlentityreplace
Replace with the appropriate HTML character entity reference, if there is one; otherwise replace with a numeric character reference. (This is not a standard python encoding error handler. It is provided by the <&|formatting.myt:codeline&>mighty.escapes module.)
<&|formatting.myt:codeline&>backslashreplace
Replace with backslashed escape sequence.
<&|formatting.myt:codeline&>ignore
Ignore malformed data and continue without further notice.

See the Python codecs documentation for more information on how encoding error handlers work, and on how you can define your own.

Generally, for components generating HTML output, it sufficient to set <&formatting.myt:link, path="parameters", param="output_encoding"&> to <&|formatting.myt:codeline&>'latin1' (or even 'ascii'), and <&formatting.myt:link, path="parameters", param="encoding_errors"&> to <&|formatting.myt:codeline&>'htmlentityreplace'. (Latin1 is the default encoding for HTML, as specified in RFC 2616.) The <&|formatting.myt:codeline&>'htmlentityreplace' error handler replaces any characters which can't be encoded by an HTML named character reference (or a numeric character reference, if that is not possible) so this setting can correctly handle the output of any unicode character to HTML.

<&|doclib.myt:item, name="details", description="Other Details"&>

With unicode support enable the return value from <&formatting.myt:link, path="request_methods", method="scomp", text="m.scomp()"&> will be either a <&|formatting.myt:codeline&>unicode or a <&|formatting.myt:codeline&>str in the system default encoding.

Similarly, the input passed to any <&formatting.myt:link, path="filtering_filtering", text="component output filters"&> will also be either a <&|formatting.myt:codeline&>unicode or a <&|formatting.myt:codeline&>str. The filter may return any object which is coercable to a <&|formatting.myt:codeline&>unicode.

Output passed to the <&|formatting.myt:codeline&>.write() method of component capture buffers (specified using the <&|formatting.myt:codeline&>store argument of <&formatting.myt:link, path="request_methods", method="execute_component"&>) will be either a <&|formatting.myt:codeline&>unicode or a plain <&|formatting.myt:codeline&>str. (Using a <&|formatting.myt:codeline&>StringIO.StringIO buffer should just work. Using a <&|formatting.myt:codeline&>cStringIO.StringIO buffer will probably not work, as they don't accept unicode input.)

Output passed to the <&|formatting.myt:codeline&>.write() method of subrequest capture buffers (specified using the <&|formatting.myt:codeline&>out_buffer argument of <&formatting.myt:link, path="request_methods", method="create_subrequest"&>) will be encoded <&|formatting.myt:codeline&>strs. The encoding and error strategy, by default, will be the system default encoding and <&|formatting.myt:codeline&>'strict' respectively, irrespective of the <&formatting.myt:link, path="request_members", member="output_encoding"&> of the parent request. These can be changed using the <&|formatting.myt:codeline&>output_encoding and <&|formatting.myt:codeline&>encoding_errors arguments of <&formatting.myt:link, path="request_methods", method="create_subrequest"&> (or by calling <&formatting.myt:link, path="request_methods", method="set_output_encoding"&> on the subrequest.)

<&|doclib.myt:item, name="disabling", description="Disabling Unicode Support"&>

Myghty's unicode support may be disabled by setting the <&formatting.myt:link, path="parameters", param="disable_unicode"&> configuration parameter.

myghty-1.1/doc/content/whatsitdo.myt0000644000175000017500000002722010501064115016646 0ustar malexmalex<%flags>inherit='document_base.myt' <&|doclib.myt:item, name="whatdoesitdo", description="What does Myghty Do?", header="Introduction" &>

Heres a rundown of what Myghty is about, touching upon the basic features one would expect in a template system, as well as the unique features Myghty provides.

<&|doclib.myt:item, name="psp", description="High Performance Python Server Pages (PSP)"&>

Myghty's primary feature is it's Python Server Page (PSP) system. Individual templates are generated into pure Python modules which become the method of serving the content. Myghty creates real Python modules from templates, which are regular .py files with corresponding .pyc bytecode files. These modules can also be generated in memory only, if desired.

Myghty templates allow full Python syntax to be embedded amongst HTML or other markup, and keeps intact the full syntax and indentation scheme of Python, even within very markup-interactive sections of code. Myghty also lets you organize embedded Python code in ways that minimize its intrusion upon markup. Python embedded within markup is clearly denoted via <% "<%python>" %> tags or the more interactive "%" syntax:

<&|formatting.myt:code &> <%text> <%python> def somefunc(): return True % if somefunc():

hello!

% # end for

Read more about Myghty syntax in <& formatting.myt:link, path="embedding" &>.

Python sections can optionally have scope attributes specified, with values such as "global", "request", "init" and "cleanup", which cause the contained python code to execute at specific points in the template's execution, regardless of where they are placed in the template. This allows flexible organization of template code and a very distinct separation of Python and markup. Read more about Myghty code scope in <& formatting.myt:link, path="scopedpython" &>.

<&|doclib.myt:item, name="components", description="Componentized Development"&>

Myghty allows you to organize Python code and markup into smaller sub-units of a page called Components. Consider a page like this:

                        +--------------------+
                        |    |    toolbar    |
                        |    +---------------|
                        |  header            |
                        |--------------------|
                        |        |           |
                        | left   |           |
                        | nav    | content   |
                        |        |           |
                        |        |           |
                        |--------------------|
                        |        footer      |
                        +--------------------+

Each subsection of this page is a likely candidate to be its own component. The overall template is referred to as the top-level component, and contains any number of subcomponents, which are effectively semi-autonomous units of code within a larger template file. They have their own namespaces, passed-in argument lists, buffering and caching attributes, etc. Components normally send their output to the current output stream, but can also be called as Python functions with return values, or their output content can be grabbed into a string as the return value.

All components are capable of calling any other component within the same template, and also of calling another template to be executed inline, or even the subcomponents contained within other templates, which are known as methods. With these functions, the page above can be organized into any variety of HTML/python code-snippets either within a single template file or across any combination of template and/or method library files:

           components.myt
        +-----------------+                                      
        |                 |                              page.myt
        |                 |       header.myt        +---------------+
        |  +-----------+  |    +---------------+    |   **********  |
        |  | toolbar   |--------------->*****  |------> **********  |
        |  +-----------+  |    |    header     |    |               |
        |                 |    +---------------+    | **            |
        |    +------+     |                         | **    page    |
        |    |      |     |                         | **   content  |
        |    | left | ------------------------------->**            |
        |    | nav  |     |                         | **            |
        |    |      |     |                         |               |
        |    +------+     |                         | +-----------+ |
        |                 |                         | |  footer   | |
        +-----------------+                         | +-----------+ |
                                                    +---------------+
                                       
                                    

Components are called via the <% "<& &>" | h%> tag construct, and also support "open-tag/close-tag" behavior via the <% "<&| &>" | h%> syntax, known as a component call with content. The content within the tags is enclosed into its own Python function that is callable by the component code, allowing custom tags with full control of execution to be created.

Read more about Myghty components in <& formatting.myt:link, path="components" &>.

<&|doclib.myt:item, name="modulecomponents", description="Module Components"&>

Myghty introduces a convenient environment-agnostic way to mix regular Python modules with template code known as Module Components. The same component model that applies to templates can be applied to regular Python objects and functions, either by explicitly subclassing the ModuleComponent class in a manner similar to a Servlet, or by configuring Myghty to implicitly resolve any regular Python function, callable object, or plain object instance into a FunctionComponent (version 0.98).

Module Components serve primarily as the "controller" stage in a request, the initial entry point for a request that handles data loading and state changes, and then passes control onto a template for display. As they are also fully capable component objects, they can also just as easily be embedded within templates, either by themselves or wrapped around further sub-content, to allow the creation of module-based tag libraries.

A big advantage to using Module Components for controller code is that the application remains completely portable to any environment, including mod_python, any WSGI environment, or non-web oriented environments.

Read more about Myghty Module Components in <& formatting.myt:link, path="modulecomponents" &>.

<&|doclib.myt:item, name="inheritance", description="Page Inheritance"&>

Any top level component can also be inherited by another top level component. This means the execution of pages can be implicitly or explicitly "wrapped" by an enclosing template, which can control the execution and content embedding of its "subtemplate" at any point in its execution. In the diagram below, a content-based HTML file is enclosed by a file providing a standardized layout, which is enclosed by another file that provides session authentication code and management:

        /lib/authenticate.myt
        +------------------+
        |% authenticate()  |
        |-------------------             /autohandler
        |                  |         +-----------------+                
        |                            |      header     |    /foo/content.myt
        |                            |-----------------|      +--------+
        |    m.call_next() --->      |                        |        |
        |                            |   m.call_next() --->   |content |
        |                            |                        |        |
        |                  |         |-----------------|      +--------+
        |------------------|         |      footer     |                
        |% cleanup()       |         +-----------------+                
        +------------------+

The methods of a parent template are also inherited by the child and can also be overridden, allowing a sub-template to change the behavior of its parent. Layout and behavior of individual groups of templates, directories, or entire sites can be managed through a concise and centralized group of inheritable templates.

Read more about Inheritance in <& formatting.myt:link, path="inheritance" &>.

<&|doclib.myt:item, name="performance", description="Performance"&>

Myghty is written with fast performance and highly concurrent service in mind. A flexible cache API, supporting in-memory, file, DBM and Memcached backends allows quick re-delivery of complicated pages. Buffering can be completely disabled, for an entire site or just individual pages, to send template output to directly to the client. Expensive cache and compilation operations are process- and thread-synchronized to prevent data corruption and redundant computation. A configurable least-recently-used cache holds only the most heavily used components in memory, deferring less used ones to be loaded again from .pyc files. Filesystem checks can be disabled as well, allowing complete in-memory operation. Large chunks of plain text are distilled into large, multi-line write() statements to minimize method call overhead for large and mostly static pages.

<&|doclib.myt:item, name="other", description="Other Features"&>
  • Session object support - can write session data into memory, plain or DBM files, or Memcached.
  • Direct connectors for mod_python, CGI, WSGI, Python Paste, SimpleHTTPServer. As the Interpreter object is a lightweight object with no external dependencies whatsoever, any Python application or application server can invoke any series of Myghty components with just one line of code.
  • A super-configurable ruleset driven URI resolution architecture allowing many options for resolving URI's both externally and within templates. Allows any combination of resolution directly to templates, Module Components, or any plain Python function or object instance. Special rules exist to route non-existent URI's to specific components, to cache the results of URI resolution for higher performance, and to execute conditionally based on contextual information.
  • Cache API and implementation, can cache component output and any other data structure in memory, in plain files, DBM files, or Memcached. Includes a "busy lock" feature that allows a slow re-generation method to execute while the old data continues to be returned to other threads and processes. New cache implementations can be added fairly easily.
  • Flexible global namespaces allow components to have any number of custom "global" variables.
  • Special code blocks allow the construction of code that is local to the current request, or local to the current thread. A ThreadLocal object is supplied as well for safe management of thread-sensitive resources such as databases.
  • Fine grained control of buffering - the buffering of textual output as it is delivered to the client can controlled at the application, page, or component level.
  • Custom filtering functions can be defined for the output any component, within the source of the component via the <% "<%filter>" %> tag.
  • Full featured error handling and reporting. errors can be logged to the Apache logs or standard error, caught by application code, and/or reported to the browser screen. stack traces are delivered showing original template line numbers.
myghty-1.1/doc/genhtml.py0000644000175000017500000000044610501064115014444 0ustar malexmalex#!/usr/bin/env python component_root = [ {'components':'./components'}, {'content':'./content'}, ] doccomp = ['document_base.myt'] output ='./html' import sys,re,os.path sys.path = ['../lib/', './lib/'] + sys.path import documentgen documentgen.genall(doccomp, component_root, output) myghty-1.1/doc/html/0000755000175000017500000000000010501065764013407 5ustar malexmalexmyghty-1.1/doc/html/docs.css0000644000175000017500000000442010501064114015035 0ustar malexmalex/* documentation section styles */ .doccontainer { } .panecontainer { } .sidebar { background-color: #EEEEFB; border: 1px solid; padding: 5px 5px 5px 5px; margin: 0px 5px 5px 0px; width:120px; float:left; } .sectionnavblock { } .sectionnav { background-color: #EEEEFB; border: 1px solid; padding: 10px 10px 10px 10px; margin: 35px 0px 15px 5px; float:right; } .topnav { background-color: #EEEEFB; border: 1px solid; padding:10px 10px 0px 10px; margin:0px 0px 10px 0px; } .tipbox { background-color: #EEEEFB; border:1px solid; padding:10px; margin: 5px; } /* optional margin to add to topnav */ .topnavmargin { margin:10px; } .topnavsectionlink { padding: 0px 0px 0px 0px; margin: 0px 0px 0px 0px; } .topnavcontrol { float:right; } .topnavmain { margin: 25px 5px 15px 5px; } .topnavheader { font-weight: bold; font-size: 16px; margin: 0px 0px 0px 0px; padding:0px 0px 15px 0px; } .topnavitems { margin: 0px 0px 0px 40px; } .prevnext { padding: 5px 0px 0px 0px; } .code { font-family:courier, serif; font-size:12px; background-color: #E2E2EB; padding:2px 2px 2px 10px; margin: 5px 5px 5px 5px; } .codetitle { font-family: verdana, sans-serif; font-size: 12px; font-weight: bold; text-decoration:underline; padding:5px; } .codeline { font-family:courier, serif; font-size:12px; } .content { border: 1px solid; padding: 0px 10px 20px 0px; } .sectioncontent { border: 1px solid; padding: 0px 10px 20px 0px; } .onepagecontent { border: 1px solid; margin: 0px 0px 0px 0px; } .docheadertext { font-size: 16px; font-weight: bold; } .docheader { margin: 0px 0px 10px 0px; } .subsection { /* this style is dynamically modified by the indentation */ margin:15px 0px 0px 0px; clear:right; } .section { padding: 20px 0px 0px 0px; } .sectionheadertext { font-weight: bold; font-size: 16px; } .sectiontext { font-size: 12px; margin: 5px 0px 0px 0px; } .maintoc { background-color: #EEEEFB; border: 1px solid; padding: 10px 10px 10px 10px; margin: 0px 0px 10px 0px; } .toclinkcontainer { padding:0px 0px 0px 8px; /*border:1px solid;*/ } .tocsection { padding:2px 2px 2px 8px; } .toclink { font-size: 12px; padding:0px 0px 3px 8px; /*border:1px solid;*/ } .smalltoclink { font-size: 11px; padding:0px 0px 3px 0px; } myghty-1.1/doc/html/style.css0000644000175000017500000000231310501064114015244 0ustar malexmalexbody, td { font-family: verdana, sans-serif; font-size: 12px; } body { background-color: #FDFBFC; margin:20px 20px 20px 20px; } p { margin-top:10px; margin-bottom:10px; } a {font-weight:normal; text-decoration:underline;} a:link {color:#0000FF;} a:visited {color:#0000FF;} a:active {color:#0000FF;} a:hover {color:#700000;} .toc { background-color: #EEEEFB; border: 1px solid; /*padding:10px 8px 10px 15px;*/ padding: 10px 10px 10px 10px; margin: 5px; } .light { background-color: #FFFFFF; } .dark { background-color: #D2D2D2; } .logo { float:left; padding: 24px 15px 0px 10px; height:276px; } .smalllogo { float:left; } .headerbar { padding-bottom:60px; } .header { font-weight: bold; font-size: 20px; } .smallheader { font-weight: bold; font-size: 16px; } .filelist { font-size:14px; margin: 10px 10px 10px 70px; line-height:25px; } .dirheader { font-size:13px; margin: 10px 50px 10px 20px } .toolbar { text-align:right; margin: 0px 10px 0px 10px } .copyright { padding-top: 30px; text-align:center; font-size:9px; color: #5F5F5F; } .small { font-size:9px; } .sforgelogo { text-align:right; height: 40px; } .source { border:1px solid; padding:10px; width:auto; } myghty-1.1/doc/html/syntaxhighlight.css0000644000175000017500000000103510501064114017322 0ustar malexmalex .substitution, .compcall { color: #DF2020; } .controlline { color: #10109E; } .doctag_text, .python_comment, .doctag { color: #109010; } .argstag_text { color: #10109E; } .blocktag, .python_keyword, .deftag, .argstag { #color: #1010FF; color: #0908CE; } .blocktag_text { color: #10109E; } .python_literal, .python_number { color: #804049; } .text { color: #807079; } .python_operator { color: #EF0005; } .python_enclosure { color: #0000FF; } .compname { color: #272767; } .python_name, name { color: #070707; } myghty-1.1/doc/lib/0000755000175000017500000000000010501065764013211 5ustar malexmalexmyghty-1.1/doc/lib/documentgen.py0000644000175000017500000000233510501064114016062 0ustar malexmaleximport sys, re, os import myghty.interp import myghty.exception as exception # document generation library def genall(comps, component_root, output_dir): interp = myghty.interp.Interpreter( component_root = component_root) try: for comp in comps: gendoc(comp, interp, output_dir = output_dir) except exception.Error, e: sys.stderr.write(e.textformat()) def gendoc(doccomp, interp, output_dir): component = interp.load(doccomp) files = component.get_attribute('files') index = component.get_attribute('index') onepage = component.get_attribute('onepage') genfile(index + ".myt", interp, output_dir) for file in files: file += '.myt' genfile(file, interp, output_dir) genfile(index + ".myt", interp, output_dir, outfile = onepage + ".html", args = {'paged':'no'}) def genfile(file, interp, output_dir, outfile = None, args = {}): if outfile is None: outfile = re.sub(r"\..+$", "%s" % '.html', file) outfile = os.path.join(output_dir, outfile) print "%s -> %s" % (file, outfile) outbuf = open(outfile, "w") interp.execute(file, out_buffer = outbuf, request_args = args, raise_error = True) outbuf.close() myghty-1.1/doc/lib/highlight.py0000644000175000017500000003025510501064114015523 0ustar malexmalex# $Id: highlight.py 2022 2006-01-11 02:40:49Z zzzeek $ # highlight.py - syntax highlighting functions for Myghty # Copyright (C) 2004 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 re, StringIO, sys, string, os import token, tokenize, keyword # Highlighter - highlights Myghty and Python source code __all__ = ['highlight', 'PythonHighlighter', 'MyghtyHighlighter'] pystyles = { token.ENDMARKER : 'python_operator' , token.NAME : 'python_name' , token.NUMBER : 'python_number' , token.STRING : 'python_literal' , token.NEWLINE : 'python_operator' , token.INDENT : 'python_operator' , token.DEDENT : 'python_operator' , token.LPAR : 'python_enclosure' , token.RPAR : 'python_enclosure' , token.LSQB : 'python_enclosure' , token.RSQB : 'python_enclosure' , token.COLON : 'python_operator' , token.COMMA : 'python_operator' , token.SEMI : 'python_operator' , token.PLUS : 'python_operator' , token.MINUS : 'python_operator' , token.STAR : 'python_operator' , token.SLASH : 'python_operator' , token.VBAR : 'python_operator' , token.AMPER : 'python_operator' , token.LESS : 'python_operator' , token.GREATER : 'python_operator' , token.EQUAL : 'python_operator' , token.DOT : 'python_operator' , token.PERCENT : 'python_operator' , token.BACKQUOTE : 'python_operator' , token.LBRACE : 'python_enclosure', token.RBRACE : 'python_enclosure' , token.EQEQUAL : 'python_operator' , token.NOTEQUAL : 'python_operator' , token.LESSEQUAL : 'python_operator' , token.GREATEREQUAL : 'python_operator' , token.TILDE : 'python_operator' , token.CIRCUMFLEX : 'python_operator' , token.LEFTSHIFT : 'python_operator' , token.RIGHTSHIFT : 'python_operator' , token.DOUBLESTAR : 'python_operator' , token.PLUSEQUAL : 'python_operator' , token.MINEQUAL : 'python_operator' , token.STAREQUAL : 'python_operator' , token.SLASHEQUAL : 'python_operator' , token.PERCENTEQUAL : 'python_operator' , token.AMPEREQUAL : 'python_operator' , token.VBAREQUAL : 'python_operator' , token.CIRCUMFLEXEQUAL : 'python_operator' , token.LEFTSHIFTEQUAL : 'python_operator' , token.RIGHTSHIFTEQUAL : 'python_operator' , token.DOUBLESTAREQUAL : 'python_operator' , token.DOUBLESLASH : 'python_operator' , token.DOUBLESLASHEQUAL : 'python_operator' , token.OP : 'python_operator' , token.ERRORTOKEN : 'python_operator' , token.N_TOKENS : 'python_operator' , token.NT_OFFSET : 'python_operator' , tokenize.COMMENT: 'python_comment', } html_escapes = { '&' : '&', '>' : '>', '<' : '<', '"' : '"' } def do_html_escape(string): #return "@" + re.sub(r"([&<>])", lambda m: html_escapes[m.group()], string) + "+" return re.sub(r"([&<>])", lambda m: html_escapes[m.group()], string) def highlight(source, filename = None, syntaxtype = None, html_escape = True): if syntaxtype is not None: highlighter = highlighters.get(syntaxtype, None) elif filename is not None: (root, filename) = os.path.split(filename) highlighter = highlighters.get(filename, None) if highlighter is None: (root, ext) = os.path.splitext(filename) highlighter = highlighters.get(ext, None) else: highlighter = None if highlighter is None: if html_escape: return do_html_escape(source) else: return source else: return highlighter(source, html_escape = html_escape).highlight() class Highlighter: def __init__(self, source, output = None, html_escape = True): self.source = source self.pos = 0 self.html_escape = html_escape if output is None: self.output = StringIO.StringIO() else: self.output = output def content(self): return self.output.getvalue() def highlight(self):raise NotImplementedError() def colorize(self, tokens): for pair in tokens: if pair[1] is None: if self.html_escape: self.output.write(do_html_escape(pair[0])) else: self.output.write(pair[0]) else: if self.html_escape: self.output.write('%s' % (pair[1], do_html_escape(pair[0]))) else: self.output.write('%s' % (pair[1], pair[0])) class PythonHighlighter(Highlighter): def _line_grid(self, str, start, end): lines = re.findall(re.compile(r'[^\n]*\n?', re.S), str) r = 0 for l in lines[0 : end[0] - start[0]]: r += len(l) r += end[1] return (start, (start[0], r)) def highlight(self): buf = StringIO.StringIO(self.source) # tokenize module not too good at getting the # whitespace at the end of a python block trailingspace = re.search(r"\n([ \t]+$)", self.source, re.S) if trailingspace: trailingspace = trailingspace.group(1) curl = -1 tokens = [] curstyle = None line = None for t in tokenize.generate_tokens(lambda: buf.readline()): if t[2][0] != curl: curl = t[2][0] curc = 0 line = t[4] # pick up whitespace and output if t[2][1] > curc: tokens.append(line[curc : t[2][1]]) curc = t[2][1] if self.get_style(t[0], t[1]) != curstyle: if len(tokens): self.colorize([(string.join(tokens, ''), curstyle)]) tokens = [] curstyle = self.get_style(t[0], t[1]) (start, end) = self._line_grid(line, t[2], t[3]) tokens.append(line[start[1]:end[1]]) curc = t[3][1] curl = t[3][0] # any remaining content to output, output it if len(tokens): self.colorize([(string.join(tokens, ''), curstyle)]) if trailingspace: self.output.write(trailingspace) return self.content() def get_style(self, tokenid, str): if tokenid == token.NAME: if keyword.iskeyword(str): return "python_keyword" else: return "python_name" elif tokenid == token.OP: if "()[]{}".find(str) != -1: return "python_enclosure" else: return "python_operator" else: return pystyles.get(tokenid, None) class MyghtyHighlighter(Highlighter): def _match(self, regexp): match = regexp.match(self.source, self.pos) if match: (start, end) = match.span() self.output.write(self.source[self.pos:start]) if start == end: self.pos = end + 1 else: self.pos = end return match else: return None def highlight(self): while (self.pos < len(self.source)): if self.match_named_block(): continue if self.match_block(): continue if self.match_comp_call(): continue if self.match_comp_content_call(): continue if self.match_substitution(): continue if self.match_line(): continue if self.match_text(): continue; break return self.content() def pythonize(self, text): py = PythonHighlighter(text, output = self.output) py.highlight() def match_text(self): textmatch = re.compile(r""" (.*?) # anything, followed by: ( (?<=\n)(?=[%#]) # an eval or comment line | (?=)(.*?)()", re.M | re.S) match = self._match(namedmatch) if match: self.colorize([(match.group(1), 'deftag')]) self.colorize([(match.group(3), 'compname')]) self.colorize([(match.group(4), 'deftag')]) MyghtyHighlighter(match.group(5), self.output).highlight() self.colorize([(match.group(6), 'deftag')]) return True else: return False def match_block(self): blockmatch = re.compile(r"(<%(\w+).*?>)(.*?)()", re.M | re.S) match = self._match(blockmatch) if match: style = { 'doc': 'doctag', 'args': 'argstag', }.setdefault(match.group(2), "blocktag") self.colorize([(match.group(1), style)]) if style == 'doctag': self.colorize([(match.group(3), 'doctag_text')]) else: self.pythonize(match.group(3)) self.colorize([(match.group(4), style)]) return True else: return False def match_comp_call(self): compmatch = re.compile(r"(<&[^|])(.*?)(,.*?)?(&>)", re.M) match = self._match(compmatch) if match: self.colorize([(match.group(1), 'compcall')]) self.colorize([(match.group(2), 'compname')]) if match.group(3) is not None: self.pythonize(match.group(3)) self.colorize([(match.group(4), 'compcall')]) return True else: return False def match_substitution(self): submatch = re.compile(r"(<%)(.*?)(%>)", re.M) match = self._match(submatch) if match: self.colorize([(match.group(1), 'substitution')]) self.pythonize(match.group(2)) self.colorize([(match.group(3), 'substitution')]) return True else: return False def match_comp_content_call(self): compcontmatch = re.compile(r"(<&\|)(.*?)(,.*?)?(&>)|()", re.M | re.S) match = self._match(compcontmatch) if match: if match.group(5) is not None: self.colorize([(match.group(5), 'compcall')]) else: self.colorize([(match.group(1), 'compcall')]) self.colorize([(match.group(2), 'compname')]) if match.group(3) is not None: self.pythonize(match.group(3)) self.colorize([(match.group(4), 'compcall')]) return True else: return False def match_line(self): linematch = re.compile(r"(?<=^)([%#])([^\n]*)(\n|\Z)", re.M) match = self._match(linematch) if match: if match.group(1) == '#': self.colorize([(match.group(0), 'doctag')]) else: #self.colorize([(match.group(0), 'doctag')]) self.colorize([(match.group(1), 'controlline')]) self.pythonize(match.group(2)) self.output.write(match.group(3)) return True else: return False highlighters = { '.myt': MyghtyHighlighter, '.myc': MyghtyHighlighter, 'autohandler' : MyghtyHighlighter, 'dhandler': MyghtyHighlighter, '.py': PythonHighlighter, 'myghty': MyghtyHighlighter, 'python' : PythonHighlighter } myghty-1.1/examples/0000755000175000017500000000000010501065764013514 5ustar malexmalexmyghty-1.1/examples/common/0000755000175000017500000000000010501065764015004 5ustar malexmalexmyghty-1.1/examples/common/autohandler0000644000175000017500000000064310501064123017224 0ustar malexmalex<%flags>inherit =None % m.set_output_encoding('latin1', 'htmlentityreplace') <& SELF:title &> <& header.myc &> % m.call_next()
<%method title> Myghty Demo Server myghty-1.1/examples/common/header.myc0000644000175000017500000000074210501064123016735 0ustar malexmalex<%global> import os
Demo Server Home   |   Documentation   |   www.myghty.org % if not r.path_info.startswith('/source'):   |   View Source of This Section %
myghty-1.1/examples/common/modulecomponents.py0000644000175000017500000000735710501064123020751 0ustar malexmaleximport myghty.component import myghty.request as request import highlight import os import posixpath as unixpath """module components for source code viewing, path translation dhandler. this package illustrates how to create small template-based components within a module component, and also how to write a dhandler as a module component.""" class ViewSource(myghty.component.ModuleComponent): def do_component_init(self, **params): comp = """ <%global> import posixpath as unixpath <%args> content name path uri <%method title> <%args scope="subrequest"> name Source of <% name %> Source of <% path %>/<% name %> (download)
<% content %>
""" dirlist = """ <%args> directories files name binfiles parent path <%method title> <%args scope="subrequest"> name Listing of <% name %>
Source Code Listing of <% path %>/<% name %>/

(parent directory)
% for directory in directories: <% directory %>/
% % for file in files: <% file %>
% % for file in binfiles: <% file %>
%
""" self.viewsource = request.instance().interpreter.make_component(comp) self.dirlisting = request.instance().interpreter.make_component(dirlist) def do_run_component(self, m, r, ARGS, **params): filename = r.filename source_uri = m.interpreter.attributes['source_uri'] if filename.endswith('/index'): (filename, index) = unixpath.split(filename) fileuri = filename[len(unixpath.commonprefix([m.interpreter.attributes['source_root'], filename])):] if os.path.isdir(filename): listing = os.listdir(filename) files = filter(lambda f: not f.endswith(".pyc") and os.path.isfile(os.path.join(filename, f)), listing) binfiles = filter(lambda f: f.endswith(".pyc") and os.path.isfile(os.path.join(filename, f)), listing) directories = filter(lambda f: os.path.isdir(os.path.join(filename, f)), listing) (path, name) = unixpath.split(fileuri) (parent, n2) = unixpath.split(m.get_request_path()) parent = unixpath.join(parent, '..', 'index') m.subexec(self.dirlisting, directories = directories, files = files, binfiles = binfiles, path=path, name=name, parent = parent) else: try: f = file(filename) except IOError: m.abort(404) r.content_type = 'text/html' s = f.read() if ARGS.get('type', None) == 'plain': r.content_type = 'text/plain' m.write(s) return s = highlight.highlight(s, filename = fileuri) (path, name) = unixpath.split(fileuri) (uri, n2) = unixpath.split(m.get_request_path()) uri = unixpath.join(uri, 'index') m.subexec(self.viewsource, content = s, uri = uri, path = path, name=name) myghty-1.1/examples/components/0000755000175000017500000000000010501065764015701 5ustar malexmalexmyghty-1.1/examples/components/autohandler0000644000175000017500000000025510501064122020117 0ustar malexmalex % comp = m.fetch_next()

/examples/components/autohandler

% m.comp(comp, **ARGS)
myghty-1.1/examples/components/component.myt0000644000175000017500000000026310501064122020422 0ustar malexmalex

/examples/components/component.myt

yep, thats me.
go back to index.myt <%method title> Myghty Examples - Component Calling myghty-1.1/examples/components/index.myt0000644000175000017500000000245510501064122017534 0ustar malexmalex<%args> call_type = None <%python scope="init">

/examples/components/index.myt

Select the type of component call you would like. Press "submit" to enact the call upon the template "component.myt".

>None
>Plain
>Subrequest
>Soft Redirect
>Hard Redirect
<%python> if call_type=='plain': m.comp('component.myt') elif call_type=='subrequest': m.subexec('component.myt') elif call_type=='soft': m.send_redirect('component.myt', hard=False) elif call_type=='hard': m.send_redirect('component.myt', hard=True) else: m.write("didnt call component")
<%method title> Myghty Examples - Component Calling myghty-1.1/examples/components/README0000644000175000017500000000016110501064122016542 0ustar malexmalexthe file "index.myt" will illustrate the behavior of autohandlers, component calls, subrequests, and redirects. myghty-1.1/examples/favicon.ico0000644000175000017500000000015010501064125015617 0ustar malexmalexGIF89a‘ÿÿÿ±ãÿÿÿÿ!ù,9œ-™Ç¨/˜Ò9ĵíwe0*Ó(žg¢’,¹–ñÚ¾¦[—¶Œž;b2³âàÒ‚…EãÐl;myghty-1.1/examples/formcontrols/0000755000175000017500000000000010501065764016243 5ustar malexmalexmyghty-1.1/examples/formcontrols/components.myt0000644000175000017500000000115610501064122021151 0ustar malexmalex <%method textfield> <%args> name value = None <%method textarea> <%args> name value = None <%method select> <%args> name options <%method button> <%args> name label onclick myghty-1.1/examples/formcontrols/field.myt0000644000175000017500000000012710501064122020044 0ustar malexmalex <%args> label <% label %> <% m.content() %> myghty-1.1/examples/formcontrols/index.myt0000644000175000017500000000150410501064122020070 0ustar malexmalex<%args> fname = None lname = None address = None comments = None
<&|field.myt, label="First Name"&> <&components.myt:textfield, name="fname", value=fname &> <&|field.myt, label="Last Name"&> <&components.myt:textfield, name="lname", value=lname &> <&|field.myt, label="Address"&> <&components.myt:textfield, name="address", value=address &> <&|field.myt, label="Comments"&> <&components.myt:textarea, name="comments", value=comments &>
<& components.myt:button, label="go", name="go", onclick="form.submit()" &>
name: <% fname %> <% lname %>
address: <% address %>
comments: % if comments is not None: <% comments %> % <%method title> Myghty Examples - Form Controls myghty-1.1/examples/formcontrols/README0000644000175000017500000000025610501064122017111 0ustar malexmalexThe template 'index.myt' illustrates how to use components and methods to build an HTML form with very little redundant HTML, as well as simple usage of the <%ARGS> tag. myghty-1.1/examples/formvisitor/0000755000175000017500000000000010501065764016077 5ustar malexmalexmyghty-1.1/examples/formvisitor/formfields.myc0000644000175000017500000000310210501064121020721 0ustar malexmalex <%doc> each method renders one particular HTML form element, as well as tags to provide basic layout for each. the "form" and "select" methods are component calls with content, to render the form elements within them. <%method form> <%args>form
<% m.content() %>
<%method textfield> <%args>textfield <% textfield.description %>: <%method submit> <%args>submit <%method select> <%args>select <% select.description %> <%method option> <%args>option myghty-1.1/examples/formvisitor/lib/0000755000175000017500000000000010501065764016645 5ustar malexmalexmyghty-1.1/examples/formvisitor/lib/formvisitor.py0000644000175000017500000000705010501064121021566 0ustar malexmaleximport myghty.component as component # the generate_form module component def generate_form(m, form): form.accept_visitor(RenderForm(m)) # base class for an element in an HTML form class FormElement(object): def __init__(self): pass def accept_visitor(self, visitor): pass # FormElement implementations class Form(FormElement): def __init__(self, name, action, elements): self.name = name self.action = action self.elements = elements def accept_visitor(self, visitor): visitor.visit_form(self) class Field(FormElement): def __init__(self, name, description, value = None): self.name = name self.description = description self.value = value class TextField(Field): def __init__(self, name, description, size, value = None): Field.__init__(self, name, description, value) self.size = size def accept_visitor(self, visitor): visitor.visit_textfield(self) class SelectField(Field): def __init__(self, name, description, options, value = None): Field.__init__(self, name, description, value) self.options = options for o in self.options: o.parent = self if o.id == self.value: o.selected = True def accept_visitor(self, visitor): visitor.visit_selectfield(self) class OptionField(FormElement): """an tag. contains a parent attribute that points to a field.""" def __init__(self, value): self.value = value def accept_visitor(self, visitor): visitor.visit_submit(self) # defines an interface for an object that can be # walked along a FormElement structure class FormVisitor: def visit_form(self, form):pass def visit_textfield(self, textfield):pass def visit_selectfield(self, selectfield):pass def visit_option(self, option):pass def visit_submit(self, submit):pass # subclass of FormVisitor that walks along a FormElement # structure and renders components in a request output stream class RenderForm(FormVisitor): def __init__(self, m): self.m = m def visit_form(self, form): def formcontent(): for element in form.elements: element.accept_visitor(self) self.m.execute_component("/examples/formvisitor/formfields.myc:form", args = dict(form = form), content=formcontent) def visit_textfield(self, textfield): self.m.comp("/examples/formvisitor/formfields.myc:textfield", textfield = textfield) def visit_selectfield(self, selectfield): def selectcontent(): for element in selectfield.options: element.accept_visitor(self) self.m.execute_component("/examples/formvisitor/formfields.myc:select", args = dict(select = selectfield), content=selectcontent) def visit_option(self, option): self.m.comp("/examples/formvisitor/formfields.myc:option", option = option) def visit_submit(self, submit): self.m.comp("/examples/formvisitor/formfields.myc:submit", submit = submit)myghty-1.1/examples/formvisitor/register.myt0000644000175000017500000000047310501064121020444 0ustar malexmalex<%args> firstname lastname occupation Thanks for registering, <% firstname %> <% lastname %>! Please join the queue on your <% occupation == "programmer" and "left" or "right" %>.
The args given to this page are: <% repr(ARGS) %> <%method title> Myghty Examples - Form Visitor myghty-1.1/examples/formvisitor/registration.myt0000644000175000017500000000307310501064121021331 0ustar malexmalex<%doc> an example firstname/lastname/occupation registration form. <%python> import formvisitor as form registerform = form.Form('myform', 'register.myt', elements = [ form.TextField('firstname', 'First Name', size=50), form.TextField('lastname', 'Last Name', size=50), form.SelectField('occupation', 'Occupation', options = [ form.OptionField('skydiver', 'Sky Diver'), form.OptionField('programmer', 'Computer Programmer'), form.OptionField('', 'No Answer'), ] ), form.SubmitField('register') ] )

This page illustrates the usage of module components to create programmatically-oriented page components, in contrast to regular template-oriented components.

  • registration.myt - This page, defines the formfields below as a data structure, which is then passed to the FormGenerator method.
  • formfields.myc - Defines the HTML implementation of each type of form field.
  • formvisitor.py - Provides the FormGenerator object which traverses a given Form object and invokes the proper methods within formfields.myc via a visitor pattern.
  • register.myt - Target page which displays form post results.

Welcome to the Skydivers or Computer Programmers Club ! <& @formvisitor:generate_form, form = registerform &> <%method title> Myghty Examples - Form Visitor myghty-1.1/examples/index.myt0000644000175000017500000000220110501064125015337 0ustar malexmalex<%method title>Myghty Demo Server

Myghty Demo Server

README
Documentation

Myghty Homepage

Source Browser:

Examples:

ZBlog Readme - a larger framework featuring MyghtyJax and SQLAlchemy
Shopping Cart - New Implicit Module Components
MyghtyJax - Asynchronous XMLHttpRequest Framework
Component Calling
Form Controls
Form Visitor (module component calling)
Template Layout
myghty-1.1/examples/myghty_small.png0000644000175000017500000000604410501064125016725 0ustar malexmalex‰PNG  IHDR²F_W>UgAMAÖØÔOX2tEXtSoftwareAdobe ImageReadyqÉe<`PLTEÕýõÊõùæýúRY]³åÿ÷þý¼êý "Æíþ¨Ýý×××­áÿv“¢´ÓÚ–¢©¹çþ¶èýet{’ºÏšÇß.59ïïïÖôüÂñú½¼¼Òéë>IP>?@±ãÿÿÿÿ(nx tRNSÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ\\í IDATxÚbr €pˆ³²²21ˆ3000J ° ²I02Š‹3…ÜÉÄ€ËÅLLLŒŒŒ@'³±€ §Äàp3@1àr0#'›ÐÉl< + v50¤ÜÍÄ€ÃÁl,<<‚Œ Œ‚²@÷‚Èá,‚œÒÄ€ÍÅŒ@³ 0°A ÐÑ,@1F¦u3@1`:˜è(9ènƒÌaá‘eË1 ¨›ˆÃÅ‚‚ l‚<\‚P60ÊrÉJ€@8Ž T'ƒ‚˜EVPæJA.'óðH€y@G3\6 T‹ƒsÜ•‚ÈP€„2(™³áÀ8 €P\ r¨ » H 8Ê 7K°pP8J ‚K2„+x¸€™ ái,#!Ë#À90á @ È.¾°€ºR€ èd&°“i†‡‹ædP* p DYÁÀ­4ÒØýL çƒÒ20”a2‚@µâád€B8™IJ‚ÀZR‹ƒ*6N`% æprÂd€E3°yŽs Ü @ ˆ¬ÇjH°Ëd`[oÀ Æ(ÎÀÈ b â â‚À©Ä"9rÖºAš„yx¸¸¸Ø€5†8dƒ5'ˆ LÓ<ƒ&0IœN “ ÊhNæE' 0„x!NÕ4R `×~‚ÌÐTdVlNæÇRHc8̰'ƒ'óÉËØÉ„”–!Ù”0¸Ø@Ñ)r¸ØÉ ¾ ++(ì™@Å2<óñ#»ÙÉÌòÄ8)˜¹ñÚ„Ê,à?h(ÒÕfa…2ÈuüBàö$a€jpU( eP†YÉÍÉbÄ9ÌB(¥ x(°l²s;¨ÀBZ€““”–9A230¨ùÅ9AN •1‚ŒâÖ# ²šd’(rþ‘D)—‰r2(9€ƒY oæ“— x‰Vp)ÀÃÂ)nÉ›ÉÐbUœS@Ta‹‹³•r›ÿ‚‚ ÃËu„ EN–„3?¾Z•À[E¾(0•»û¬ üÄN r˜äd`“T³€Òˆâ Š“aÁ ´E_ €@NædCu²,Š“9E å='ÄÉLâ\Àb,Ê"™Y0,CŽ“!ÁÌTÆ`i± ZrPÇÌu2¨<å`ç”'  “Á¾3(ó‰Èœ,‡ì8Ò ñ(3¡î"@Ì)ˆÛÉâœ<¼À–;’“!Å68˜A™O–… –ý¸™Ÿ°“1³(˜e Õ:n@(M|¡ ¬=@=)x€8™Ú>ã.°BNn3!'Ë`-È  An¼N pGJ6æ‰5”¹¸¸@ [âdDåIã<œâˆF‘$¤I$JÈÉ <-*ž«€v#ð7ñÒ]å”Å eH!Ç ò§8hP€âdFèˆ8˜…dÄÁN†¶1Á¤ Áœv8„øù…8È!‡·y`Aw3 §8#P;#'ˆƒFº@%´(#"åƒÃD>bË oM2‹t2rÛÉÉürx›`@¡Œ”žÿêwÅ€yLœTû °À£ä:¤±|nH8sðQ.#õ`8øå‰kÛC@Á¸0œ )¥ qЀÁêw3ÊÂG=eeA%?°?ŽÇ’’D®JðIvffvvnôî­mFÀîd.ÐØÉ àFv2¨?% ®s0"o,NŒ˜Ž7@AkYÑ3 ÜÝÀòÔ…ô»Q*³„y°Î—€: ¢d8Yšˆ@– Ø8¨­ƒ[J‚œâ @ç °ÀÓ±, (§H–,jY1fò†)ø‰Ò@0'³bÈ)„ Ü#”åB1¸±Äłſ#hºä—“#&´0R¸¬&¬ €Ó;œ YI ^QHÙ ,Ë‚HÐKRpp“âZI9Qbµb è$¼NM ‚UÃÊS~.ä)fä~¾(I.†UñÄy €&Wq& Y“ÁóÛ Æ=´`áA÷c—U%ü$¦ ˆ“9؉JL„€B™vçÄÊðØÍæÃ›– ”Å â8ÂÊ ZE@hKHp¸Y@Ø÷D-¯ÊÅò„¾P‡S›£YÀÓÖÈ2ÀÆé@­Ô ôåP ¥:Ø :”T!À)>`Ë¡sÑ™8§A'ÜÂ"  ,Kûp4R*æÐU”„u¥¸¸°M«•Ä9A „c™ª¸8'–Œ( l‚Š ðBU€Â¾˜ìh 7 °’Ä@¯¬ ÜK®Å@ j"C!hxó»X €ä‡ !èd€‚N0ãöõ¥°C¹cIEND®B`‚myghty-1.1/examples/myghtyjax/0000755000175000017500000000000010501065764015540 5ustar malexmalexmyghty-1.1/examples/myghtyjax/docs/0000755000175000017500000000000010501065764016470 5ustar malexmalexmyghty-1.1/examples/myghtyjax/docs/document_base.myt0000644000175000017500000000055010501064121022015 0ustar malexmalex<%flags>inherit="/doc/doclib.myt" <%python scope="global"> files = [ 'intro' ] <%attr> files=files wrapper='section_wrapper.myt' onepage='documentation' index='index' title='MyghtyJax Documentation' version = '0.01' lib_uri = '/doc/' root = '/examples/myghtyjax/docs/' myghty-1.1/examples/myghtyjax/docs/index.myt0000644000175000017500000000005410501064121020313 0ustar malexmalex<%flags>inherit="document_base.myt"myghty-1.1/examples/myghtyjax/docs/intro.myt0000644000175000017500000000034110501064121020336 0ustar malexmalex<%flags>inherit='document_base.myt' <&|/doc/doclib.myt:item, name="intro", description="Introduction", &> the future home of MyghtyJax docs. <&|/doc/doclib.myt:item, name="lala", description="lala", &> myghty-1.1/examples/myghtyjax/hourglass.gif0000644000175000017500000001142410501064122020223 0ustar malexmalexGIF89a ‡€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿ!ÿ NETSCAPE2.0è!ù , hðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k59ÎüÓç¬ÛϧºÅ|B¢ÐxDªˆÎ&eµ”ZZ@«3«Ý1µ²(8\—§ßf–̰ÝéN7'ÇÞb­Žy‰Žxg:~6„(f‰Š‹!ù , fðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k59Î÷Ãû=›ÊG¼åtÆ¢*̦“8•vzG벪½r»²/¸••® `±™)ã¢Þ¦{+‡Sæ<ÞòÕÙ6}!1‚~i‡ˆ‰!ù , fðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k59Îwùä¬ïGüõb«¡ð3ªŠÀfRÅlÊ®VÌ®š½F»Îo¶%F ©`´¥Œ;º¹48Ö·²Ùk¹ÚüBу|}~:‚!i‡ˆ‰)!ù , gðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k59ÞÝ“³·™Ð§âÅVà1æ ›L ²½´~U*6 ÝfwK.”*&+ËX/ÍÈ×Qõøè¦È¥Ë|=ó1ïs@}!tƒ(eˆ‰Š!ù , gðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k59ÞÝ“³·/È‹­„>U1æ &›LËQ½´~U*6 ÝfwK.”*&ËX/­È×QõØè¦È¥K$™þBÑuuV}"|ƒeˆ‰Š!ù , hðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k59ÞÝ“³7žJ%´ gÁbl…$Æ.LçÊûMe>åkÝf¿ÞNU;‚½Ö."TgÜg#Žü¦K—UŸþn{¡ð:vb~!x„a‰Š‹!ù , eðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k59ÞWÎÞ<•*hÎ€ÄØê8Œõ€MçS&Íì’Uª/ÛÜf[^ç2Ê݆iÁ4ͯ©n‹Z ~0ɶJù¦œ;z"|{\†‡ˆN!ù , jðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k5™[;}ß%0¶š…±Îïñ&‘LèS)›bF=+ÓèÔ"³Ó˜¸Üzͼ®2_ÕlxÆíN³£Õ¤8 ¬+_(d:ri€!d†g‹Œ!ù , hðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k5™[;}ß<`l5+ª„±QyLUMç3*}öªÊ"Û¼J[ÞáZí…qÂô–µ¦œ§¥N;S3Êœ`&ð-¡Ä:sA~!b„\‰Š‹!ù , iðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k5™[;}g=Ÿê1»YF¢ê£¬ŒÌ¦ów”ŽªÖbTÚÑr1-¬.IÜ6{b0smâÚÀ¨®v*eYeñ^ξP1iiK!…†_Š‹Œ‹!ù , jðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k5™[;}g=Ÿêñ#cÆá-h&‘—:šRgVdõ¥H¹ÝÕëe1µâ¹C–];ìw‰èm£±t<Ö}…´|*2~6…(]Š‹Œ!ù , gðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k5™[;}g=å÷²n3$1&S©–Ìæ3 LB©M#u¤e¶º:ä;î\ÃÃá·.Û:æè*YTËéø:ØüBÅö{O}!ƒ„dˆ‰ŠQ!ù , iðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k5™[;}g=ðSÝfGbѨb.y¿às$"•O ›}´ª¶¦—ë‡Z-·v–•ðöe…ôÚÇKª½~Í¿BEgx6€„…]Š‹ŒO!ù , fðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k5™[;}g=ðSoA›ÊXÄ´’Í4:›¥Äfçge!™ZÙ¥;>RÈç,O-\fÑàêC>gãäøyúBźh}06‚~a‡ˆ‰‡!ù , fðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k5™[;}g=ðSeEÞmyL‘ÎË(6£Oj²¥µ©žØG¯;¤ÉJa‘+nƒc«™Ü6Ïïíºì…‚_Ó6|!~‚}a‡ˆ‰!ù , fðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k5™[;}g=ðSeEÞ1Xl1“£'t&mRIJôQe-±Ö2˳5lWû£rDZ•Û]ž¾çwóð…ŠëÙ~|!‚d‡ˆ‰‰!ù , eðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k5™[;}g=ðSeEÞ1Xl1“£'t&mRIŠ›]ݪ8.øÂk‰Ôó´ôH×í¸ÛüNÛÛÜð ›÷06€|Y…†‡ˆ!ù , eðÉI«½8ë]ÿ`èq“h‚¤4rkzši l­;k5™[;}g=ðSeEÞ1Xl1“£'t&mRIŠ›ÍUqÜï¥FkË2\J»S•› ·Îï×é ûš÷06€|Y…†‡ˆ!ù , pðÉI+6ë úþ_çää‰%)bhªçèV˜ÉʳTÛO›÷–Øn6äù\E#®”T69¡tT=uוöÚËr»Ûg5ø½ŒiH!e"ªÙm÷½+“çÀxÅ×ïÕ~x]?fsK…|ˆ…`„Œwvn“!ù , qðÉI«½8ëÍ»ÿ`(Ž$œå Tj»j®Ê®qV£úJøœ?=m$YÇ ¨œyIç)•Y¦Í%Ñz ËVÊ N/Ø\äþž:êwþ6ê¶v»:N¾n÷C-6.€501yhn%Ž‘’“”•!ù , qðÉI5ë=ïåàæYXh’Øx‚£÷­Zû¾p÷©RY£¼®Ã²åw Z޹šQØÞnGŸ'ÚQcÄb¶âꆺ^mÊÚšÌ6tUœRgÐO7WÔ–Ïßõr˜×ãzI6<$9}…(ˆ‰†WŒ.ŒxvV‘‰!þïThis GIF file was assembled with GIF Construction Set from: Alchemy Mindworks Inc. P.O. Box 500 Beeton, Ontario L0G 1A0 CANADA. This comment block will not appear in files created with a registered version of GIF Construction Set!ÿ GIFCONnb1.0((C:\anii\ANI-BUSY.ANI.DIR\ani-busy.0.gif((C:\anii\ANI-BUSY.ANI.DIR\ani-busy.1.gif((C:\anii\ANI-BUSY.ANI.DIR\ani-busy.2.gif( (C:\anii\ANI-BUSY.ANI.DIR\ani-busy.3.gif( (C:\anii\ANI-BUSY.ANI.DIR\ani-busy.4.gif( (C:\anii\ANI-BUSY.ANI.DIR\ani-busy.5.gif((C:\anii\ANI-BUSY.ANI.DIR\ani-busy.6.gif((C:\anii\ANI-BUSY.ANI.DIR\ani-busy.7.gif((C:\anii\ANI-BUSY.ANI.DIR\ani-busy.8.gif((C:\anii\ANI-BUSY.ANI.DIR\ani-busy.9.gif))C:\anii\ANI-BUSY.ANI.DIR\ani-busy.10.gif))C:\anii\ANI-BUSY.ANI.DIR\ani-busy.11.gif))C:\anii\ANI-BUSY.ANI.DIR\ani-busy.12.gif))C:\anii\ANI-BUSY.ANI.DIR\ani-busy.13.gif))C:\anii\ANI-BUSY.ANI.DIR\ani-busy.14.gif)!)C:\anii\ANI-BUSY.ANI.DIR\ani-busy.15.gif)#)C:\anii\ANI-BUSY.ANI.DIR\ani-busy.16.gif)%)C:\anii\ANI-BUSY.ANI.DIR\ani-busy.17.gif)')C:\anii\ANI-BUSY.ANI.DIR\ani-busy.18.gif)))C:\anii\ANI-BUSY.ANI.DIR\ani-busy.19.gif)+)C:\anii\ANI-BUSY.ANI.DIR\ani-busy.20.gif;myghty-1.1/examples/myghtyjax/hourglass.myt0000644000175000017500000000075610501064122020275 0ustar malexmalex<%doc> in this page, the first argument to the resulting javascript function must be the DOM id with which to write the resulting html, which is because it specifies 'writedom' as the type of delivery, but no 'dom_id' argument. the second argument to the javscript function is specified as the only component argument below, which is the message to be displayed. <%attr> type='writedom' <%args> message <% message %> myghty-1.1/examples/myghtyjax/index.myt0000644000175000017500000001416310501064122017372 0ustar malexmalex <%doc> first, we define all the various AJAX methods we want to call, and register them with the myghtyjax handler. all of these functions, as well as the myghtyjax initializer functions, can be anywhere, at the bottom of the whole page or even in a totally different file, and also shared by any number of templates. <%once> # a plain python function which will be mapped to a javascript function def myresult (firstname, lastname, email): return "document.getElementById('result').innerHTML = 'well hi %s %s %s';" % (firstname, lastname, email) <%doc> a myghty "hello" method that will be mapped to a javascript function <%method myres> <%args> firstname lastname document.getElementById('result').innerHTML = "hello <% firstname %> <%lastname %>!"; <%doc> a myghty "hello" method whos HTML contents will be returned to a callback function. note that you can also call this component within the original template (or from any other template), not just via XMLHttpRequest. This illustrates multi-contextual templating components. <%method drawtable> <%attr> type='source' <%args> firstname lastname email
First Last Email
<% firstname %> <% lastname %> <% email %>
<%doc> a myghty method to add two numbers, and the resulting HTML contents will be drawn directly to a DOM element. Includes an optional argument "slow" which will cause it to pause 2 seconds. while pointless here, it is meant to simulate an operation that might be doing a long database operation or something similarly time-consuming. <%method addnumbers> <%args> leftop rightop slow = False <%attr> type='writedom' # specify a fixed target DOM id. if # omitted, the target id must be specified # as the first argument to the javascript # function. dom_id='addresult' <%init> import time if slow: time.sleep(2) try: a = str(int(leftop) + int(rightop)) except: a = '' % if slow: After much consideration, I can safely say that <% leftop %> + <% rightop %> = <% a %> ! % else: <% a %> ! that was easy. % <%init> # register the functions with myghtyjax. # if this function returns True, it means myghtyjax # executed the ajax-enabled component, so we return # from execution. # note that autohandlers are not executed within the # ajax call, as myghtyjax.myt, the entry point for # all XMLHttpRequest calls, inherits from None. if m.comp('myghtyjax.myt:init', # local python def myresult = myresult, # local myghty methods add = 'SELF:addnumbers', myres = 'SELF:myres', drawtable = 'SELF:drawtable', # an external template hourglass = 'hourglass.myt', ): return <%doc>Deliver the myghtyajax javascript stub functions. This method takes the optional arguments 'handleruri' and 'scripturi', which reference the web-addressable URI of the myghtyjax.myt file and the myghtyjax.js file. They can also be set as global interpreter attributes. If not supplied, myghtyjax determines them based on where it is being called from. <& myghtyjax.myt:js &>

Myghty Ajax Toolkit

In this example, Myghty is used to create a seamless bridge between client-side Javascript and server-side Python functions, or between Javascript and Myghty template components. You can write regular Python-embedded HTML, put a set of tags around it, and have that component's server-side code and textual output "magically" become available as a regular javascript function, which can return its text, execute server-generated Javascript, or write its output into any DOM element on the page.

index.myt - this page illustrates various functions that utilize myghtyjax
"hourglass" page - this page illustrates how functions can exist in a second page
myghtyjax library / javascript functions - this is the actual library

Try out the functions here. Each click of a button generates a request to the server, whose output is then populated into the current page:
First Name:<& text, name="firstname", value="wile e"&>
Last Name:<& text, name="lastname", value="coyote"&>
Email:<& text, name="email", value="wilee@suprgenius.net"&>


<& text, name="leftop", value="4", size=3 &> + <& text, name="rightop", value="5", size=3 &> =
# for the "slow" version, we call the "hourglass" function first.
<%method text> <%args> name size=20 value = None myghty-1.1/examples/myghtyjax/myghtyjax.js0000644000175000017500000000407410501064122020112 0ustar malexmalex function error(message) { var console = window.open("",'console',"width="+400+",height="+400+",status=yes,dependent=yes,resizable=yes,scrollbars=yes"); console.document.write(message); console.document.close(); } /* receives javascript from the given url + args and evaluates it */ function runRemoteJS(url, args) { doCall(url, function (x) {eval(x);}, args); } function doCall(url, wrapper, args) { var req = openConnection(); if (url.indexOf('?') == -1) { url = url + "?"; } for (var key in args) { if (key=='_mjax_named') { for (var k in args[key]) { url = url + '&' + escape(k) + "=" + escape(args[key][k] || ''); } } else { url = url + '&' + escape(key) + "=" + escape(args[key] || ''); } } url = url + "&_rnd=" + new Date().getTime() req.open("GET", url); req.onreadystatechange = function () { if (req.readyState != 4) { return; } //var s = getResponse(); var s = req.responseText; if (s) { try { wrapper(s); } catch (e) { error(e + "

\n\nArgument:\n
" + s); } } } req.send(null); delete req; } function openConnection () { var conn = null; try { conn = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { conn = new ActiveXObject("Microsoft.XMLHTTP"); } catch (oc) { } } if(!conn && typeof XMLHttpRequest != "undefined") { conn = new XMLHttpRequest(); } if (!conn) { error("XMLHttp connection object not defined on this browser"); } return conn; } /* given the id of a document element, a url, and argument hash, populates the given document element with the results of the url + args */ function populateDOM(id, url, args) { doCall(url, function (x) { var elem = document.getElementById(id); if (elem) { elem.innerHTML = x; } else { error("No such element '" + id + "'"); } }, args); } function writeDOM(id, string) { var elem = document.getElementById(id); if (elem) { elem.innerHTML = string; } } myghty-1.1/examples/myghtyjax/myghtyjax.myt0000644000175000017500000002521010501064122020302 0ustar malexmalex<%flags> # myghtyjax.myt needs to inherit from either None, or an autohandler # that doesnt have any textual output. inherit=None trim="both" <%doc> MyghtyJax - a Myghty AJAX Adapter The "init" method is called to register the names of javascript functions that will map to Myghty components and/or python function definitions. supply key=value arguments, where keys are the names of javascript functions to be generated and the value is one of: a function object, the string address of a Myghty component (path or method name), or a Myghty component object. This method should be called in the %init section of the calling component, usually before anything else has executed. If it returns true it means the XMLHttpRequest action is taking effect, and the calling component should return immediately. If false is returned, the component should continue on normally. Note that the "js" method below must be called within the HTML body of a page to actually write out the javascript functions defined by the init method. components that are called can specify options for how they should be handled via their <%attr> sections. the available options are: type='source' take the HTML source of the component and send it to a supplied javascript callback function. type='writedom' take the HTML source of the component and send it to the DOM element with the id given by either the <%attr> 'dom_id', or the first argument to the javascript function. type='exec' take the output of the component and evaluate it directly as javascript. Example: if m.comp('myghtyjax.myt:init', mypage = 'SELF:mymethod' ): return The above example will create a javascript function 'mypage()', which results in an Ajax call back to the myghtyjax.myt page which will route the request to the <%method mymethod> inside the current page. The results of the method will be processed per the "type" attribute on the method. A second form of init argument allows the values normally inside <%attr> to be specified directly to init: if m.comp('myghtyjax.myt:init', mypage = { 'component' : 'SELF:mymethod', 'exectype' : 'writedom', 'dom_id' : 'leftnav' } ): return The above example will create a javascript function 'mypage()', which results in an Ajax call back to the myghtyjax.myt page which will route the request to the <%method mymethod> inside the current page. The resulting output from the method will be written to the 'leftnav' DOM element on the page. There is also a way to circumvent the usual URL of the myghtyjax controller directly to any URL: if m.comp('myghtyjax.myt:init', mypage = { 'handler_uri' : '/my_ajax_page/', 'exectype' : 'writedom', 'dom_id' : 'leftnav' } ): return The above example will create a javascript function 'mypage()', which results in an Ajax call directly to the uri '/my_ajax_page/', and the resulting HTML will be written into the DOM element 'leftnav'. <%method init> <%init> frame = m.execution_stack.pop() try: ret = init(m, **ARGS) finally: m.execution_stack.append(frame) return ret <%doc> js is called after init has been called, and delivers the javascript "stub" functions that connect page javascript to server side calls via XMLHttpRequest. the scripturi and handleruri arguments are optional arguments that reference the web-accessible URIs of the myghtyjax.js and myghtyjax.myt files respectively. If these parameters are not supplied, they will be searched for in the interpreter attributes "myghtyjax.handleruri" and "myghtyjax.scripturi". if not there either, they will be determined based on the path of the original calling component. <%method js> <%args> scripturi = None handleruri = None <%init> js(m, handleruri = handleruri, scripturi = scripturi) <%args> _mjax_def _mjax_component <%init> m.comp(_mjax_component, **ARGS) <%once> import inspect, string, types, posixpath import myghty.request as request # initialization method, receives javascript function names mapped # to python function pointers, component names, or component objects. # referenced by the "init" myghty method. def init(m, **params): run_func = m.request_args.get('_mjax_def', None) if (run_func is not None): object = params[run_func] callable = _get_callable(m, run_func, object) callable.run(m) return True else: defs = m.attributes.setdefault('__mjax_defs', {}) for (jsname, object) in params.iteritems(): defs[jsname] = _get_callable(m, jsname, object) return False def _get_callable(m, name, object): if isinstance(object, types.FunctionType): return DefCallable(name, object) elif isinstance(object, dict): return ComponentCallable(m, name, **object) else: return ComponentCallable(m, name, object) def js(m, handleruri = None, scripturi = None): try: defs = m.attributes['__mjax_defs'] except KeyError: raise "myghtyjax.js() method requires components and defs to be init()ialized first" path = m.current_component.path if handleruri is None: handleruri = m.interpreter.attributes.get('myghtyjax.handleruri', path) if scripturi is None: scripturi = m.interpreter.attributes.get('myghtyjax.scripturi', None) if scripturi is None: (dir, file) = posixpath.split(path) scripturi = posixpath.join(dir, "myghtyjax.js") m.write("\n" % scripturi) m.write("\n") class JaxComponent: def init_callerpath(self, m): if getattr(self, 'callerpath',None) is None: self.callerpath = m.caller.path def get_function_args(self, leading_comma = False): args = string.join(self.argnames + ['_mjax_named'], ',') if leading_comma and args: return ', ' + args else: return args def get_handler_uri(self, handler_uri): return handler_uri def get_remote_args(self, m): return string.join( [ "'_mjax_def' : '%s'" % self.jsname, "'_mjax_component' : '%s'" % self.callerpath, "'_mjax_named':_mjax_named" ] + ["'%s' : %s" % (name, name) for name in self.argnames], ",\n", ) class ComponentCallable(JaxComponent): def __init__(self, m, jsname, component=None, exectype=None, argnames=None, dom_id=None, handler_uri=None): self.jsname = jsname self.component = component self.handler_uri=handler_uri if type(component) == types.StringType: componentobj = m.fetch_component(component) else: componentobj = component if exectype is None: if componentobj is not None: self.exectype = componentobj.attributes.get('type', 'exec') else: self.exectype = 'exec' else: self.exectype = exectype if dom_id is None and componentobj is not None: self.dom_id = componentobj.attributes.get('dom_id', None) else: self.dom_id = dom_id if argnames is None: if componentobj is not None: self.argnames = [arg.name for arg in componentobj.arguments] else: self.argnames = [] else: self.argnames = argnames def get_handler_uri(self, handler_uri): if self.handler_uri is not None: return self.handler_uri else: return handler_uri def run(self, m): m.comp(self.component, **m.root_request_args) class DefCallable(JaxComponent): def __init__(self, jsname, defobj): self.jsname = jsname if isinstance(defobj, types.MethodType): defobj = defobj.im_func self.defobj = defobj self.argnames = inspect.getargspec(defobj)[0] self.has_named = inspect.getargspec(defobj)[2] is not None self.exectype = 'exec' def run(self, m): if self.has_named: m.write(self.defobj(**m.root_request_args)) else: args = {} for name in self.argnames: args[name] = m.root_request_args[name] m.write(self.defobj(**args)) <%method write_docall> <%args> callable handleruri function <% callable.jsname %>(callback<% callable.get_function_args(leading_comma = True) %>) { doCall('<% callable.get_handler_uri(handleruri) %>', callback, { <% callable.get_remote_args(m) %> }); } <%method write_remotejs> <%args> callable handleruri function <% callable.jsname %>(<% callable.get_function_args() %>) { runRemoteJS('<% callable.get_handler_uri(handleruri) %>', { <% callable.get_remote_args(m) %> }); } <%method write_writedom> <%args> callable handleruri % if callable.dom_id is None: function <% callable.jsname %>(domid <% callable.get_function_args(leading_comma = True) %>) { populateDOM(domid, '<% callable.get_handler_uri(handleruri) %>', { <% callable.get_remote_args(m) %> }); } % else: function <% callable.jsname %>(<% callable.get_function_args() %>) { populateDOM('<% callable.dom_id %>', '<% callable.get_handler_uri(handleruri) %>', { <% callable.get_remote_args(m) %> }); } % myghty-1.1/examples/shoppingcart/0000755000175000017500000000000010501065764016215 5ustar malexmalexmyghty-1.1/examples/shoppingcart/components/0000755000175000017500000000000010501065764020402 5ustar malexmalexmyghty-1.1/examples/shoppingcart/components/address.myc0000644000175000017500000000202010501064120022514 0ustar malexmalex<%args> form <& forms.myc:formstatus, form=form &> <&| SELF:tablefield, name="First Name" &> <& forms.myc:text, field=form.elements['firstname'] &> <&| SELF:tablefield, name="Last Name" &> <& forms.myc:text, field=form.elements['lastname'] &> <&| SELF:tablefield, name="Street 1" &> <& forms.myc:text, field=form.elements['street1'] &> <&| SELF:tablefield, name="Street 2" &> <& forms.myc:text, field=form.elements['street2'] &> <&| SELF:tablefield, name="City" &> <& forms.myc:text, field=form.elements['city'] &> <&| SELF:tablefield, name="State" &> <& forms.myc:text, field=form.elements['state'] &> <&| SELF:tablefield, name="Zip" &> <& forms.myc:text, field=form.elements['zipcode'] &> <&| SELF:tablefield, name="Country" &> <& forms.myc:text, field=form.elements['country'] &>
<%method tablefield> <%args>name <% name %>: <% m.content() %> myghty-1.1/examples/shoppingcart/components/autohandler0000644000175000017500000000064110501064120022615 0ustar malexmalex<%flags>inherit=None Myghty Shopping Cart Example <& header.myc &>
<& sidebar.myc &>
% m.call_next()
myghty-1.1/examples/shoppingcart/components/breadcrumb.myc0000644000175000017500000000135710501064120023211 0ustar malexmalex<%args> category <%python> import string list = [] c = category while c is not None: list.insert(0, c) c = c.parent <%method link trim="both"> <%args> category bold = False <% category.description %> myghty-1.1/examples/shoppingcart/components/ccard.myc0000644000175000017500000000125210501064120022151 0ustar malexmalex<%args> form <& forms.myc:formstatus, form=form &> <&| SELF:tablefield, name="Name on Credit Card" &> <& forms.myc:text, field=form.elements['ccname'] &> <&| SELF:tablefield, name="Credit Card Type" &> <& forms.myc:select, field=form.elements['cctype'] &> <&| SELF:tablefield, name="Credit Card Number" &> <& forms.myc:text, field=form.elements['ccnumber'] &> <&| SELF:tablefield, name="Expiration Date" &> <& forms.myc:datefield, field=form.elements['ccexp'] &>
<%method tablefield> <%args>name <% name %>: <% m.content() %> myghty-1.1/examples/shoppingcart/components/common.myc0000644000175000017500000000351310501064120022367 0ustar malexmalex<%doc>Library of common functions used throughout the store <%global> import re <%doc> Method: store_uri Purpose: Writes the base URI of the store application <%method store_uri trim="both"> <% m.interpreter.get_attribute('store_uri') %> <%doc> Method: document_uri Purpose: Writes the base URI of the store's document root <%method document_uri trim="both"> <% m.interpreter.get_attribute('store_document_uri') %> <%doc> Method: category_uri Purpose: Given a Category object, writes the URI to view that category <%method category_uri trim="both"> <%args>category # chop root token off % path = re.sub(r'^/.+?(/|\Z)', '/', category.get_path()) % path = re.sub(r'([^/])$', r'\1/', path) <& SELF:store_uri &>catalog<% path %> <%doc> Method: conditional_bold Purpose: based on a boolean, prints the inside content in boldface or plain <%method conditional_bold trim="both"> <%args>value % if value: <% m.content() %> % else: <% m.content() %> % <%doc> Method: item_link Purpose: Writes a hyperlink with an item's name <%method item_link trim="both"> <%args> item <% item.name %> <%doc> Method: item_image Purpose: Writes the standard HTML to display an item's image <%method item_image> <%args> item <%doc> Method: price Purpose: Writes a price, or if None does nothing. <%method price trim="both"> <%args> price % if price is not None:  <% '$%.2f' % price %> % myghty-1.1/examples/shoppingcart/components/dhandler0000644000175000017500000000031210501064120022063 0ustar malexmalex % if m.request_path == '/' or m.request_path == m.interpreter.attributes.get('store_path', m.interpreter.attributes['store_uri']): % m.send_redirect('index.myt', hard=False) % else: % m.decline() % myghty-1.1/examples/shoppingcart/components/forms.myc0000644000175000017500000001134710501064120022231 0ustar malexmalex<%doc> forms.myc - library of HTML form controls. support for form.Form validation objects is included. <%doc> method: form purpose: displays a
tag pair, inserting content in the middle <%method form> <%args> name = None action = None
<% m.content() %>
<%doc> method: select purpose: displays a tag pair, inserting options in the middle <%method select> <%args> name = None value = None field = None options = None <%init> if field is not None: if name is None: name = field.displayname if options is None: options = field.options if value is None: value= field.displayvalue <& fieldstatus, field=field &> <%doc> method: text purpose: displays a tag <%method text trim="both"> <%args> name = None value = None field = None size = None <%init> if field is not None: if name is None: name = field.displayname if value is None: value = field.displayvalue if size is None: size = field.textsize if size is None: size = 50 <& fieldstatus, field=field &> <%doc> method: button purpose: displays a tag <%method button trim="both"> <%args> value = None onclick = None cssclass = None <%doc> method: submit purpose: displays a tag <%method submit trim="both"> <%args> value = None onclick = None <%doc> method: datefield purpose: displays a compound date field <%method datefield> <%args> fieldnames = {'month':'month', 'year':'year', 'day':'day', 'hour':'hour', 'minute':'minute', 'second':'second', 'ampm':'ampm'} fieldvalues = {'month':None, 'year':None, 'day':None, 'hour':None, 'minute':None, 'second':None, 'ampm':None} yearrange = range(2000, 2009) months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] field = None <%init> if field is not None: fieldnames = {} fieldvalues = {} for f in field.elements.values(): fieldnames[f.name] = f.displayname fieldvalues[f.name] = f.displayvalue yearrange = field.yearrange % if fieldnames.has_key('month'): <& SELF:select, name=fieldnames['month'], options = [('', '')] + [(i, months[i - 1]) for i in range(1, 13)], value=fieldvalues['month'] &> % % if fieldnames.has_key('day'): <& SELF:select, name=fieldnames['day'], options = [('', '')] +[(i, i) for i in range(1, 32)], value=fieldvalues['day'] &> % % if fieldnames.has_key('year'): <& SELF:select, name=fieldnames['year'], options = [('', '')] +[(i, i) for i in yearrange], value=fieldvalues['year']&> % % if fieldnames.has_key('hour'): % if fieldnames.has_key('ampm'): <& SELF:select, name=fieldnames['hour'], options = [('', '')] +[(i, i) for i in range(1, 12)], value=fieldvalues['hour']&> % else: <& SELF:select, name=fieldnames['hour'], options = [('', '')] +[(i, i) for i in range(0, 24)], value=fieldvalues['hour']&> % % % if fieldnames.has_key('minute'): <& SELF:select, name=fieldnames['minute'], options = [('', '')] + [(i, i) for i in range(0, 59)], value=fieldvalues['minute'] &> % % if fieldnames.has_key('second'): <& SELF:select, name=fieldnames['second'], options = [('', '')] + [(i, i) for i in range(0, 59)], value=fieldvalues['second'] &> % % if fieldnames.has_key('ampm'): <& SELF:select, name=fieldnames['ampm'], options = [('', '')] + [('am', 'am'), ('pm','pm')], value=fieldvalues['ampm'] &> % <%doc> method: fieldstatus purpose: displays a blue or red asterisk, given a form.FormField object <%method fieldstatus trim="both"> <%args> field = None <%init>if field is None:return % if field.is_valid() is False: * % elif field.required: * % <%doc> method: formstatus purpose: displays a list of invalid fields, given a form.Form object <%method formstatus> <%args> form <%init>if form.is_valid(): return
% for element in form.elements.values(): % if element.is_valid() is False: <% element.get_valid_message() %>
% %
myghty-1.1/examples/shoppingcart/components/header.myc0000644000175000017500000000057110501064120022330 0ustar malexmalex
The Myghty Store
myghty-1.1/examples/shoppingcart/components/sidebar.myc0000644000175000017500000000171310501064120022510 0ustar malexmalex<%python scope="global"> files = [ {'' : [ "run_cart.py", ]}, {'components' : [ "address.myc", "autohandler", "dhandler", "breadcrumb.myc", "ccard.myc", "common.myc", "forms.myc", "header.myc", "sidebar.myc", ]}, {'htdocs' : [ "architecture.myt", "index.myt", "shopping.css", "syntaxhighlight.css", ]}, {'templates': [ "catalog.myt", "item.myt", "cart.myt", "checkout.myt", "confirm.myt", "viewsource.myt", ]}, {'lib': [ "form.py", "shoppingcontroller.py", "shoppingdata.py", "shoppingmodel.py", "statemachine.py", "sample_httpd.conf", ]}, ] myghty-1.1/examples/shoppingcart/htdocs/0000755000175000017500000000000010501065764017501 5ustar malexmalexmyghty-1.1/examples/shoppingcart/htdocs/architecture.myt0000644000175000017500000000711210501064120022700 0ustar malexmalexMyghty Demo Store Architecture

The Myghty Store is laid out in a model-view-controller pattern:

  • shoppingcontroller.py - this module contains a class instance that serves as the controller, which is referenced by the module variable 'index'. Each method of the class is served as a Module Component. The resolution of URIs to these module components is specified in the Myghty configuration parameter "module_root". This configuration parameter can be seen in the sample httpd.conf file as well as the example standalone runner run_cart.py.

    An alternative to module_root which is a more specific to individual URL patterns, as well as a little more secure, is module_components, which references URL patterns to specific module component objects.

    shoppingcontroller is responsible for receiving input from the request, pulling data from the data storage module as well as the user's current session, and configuring the proper response template as well as the data used by that template. All templates that are called by shoppingcontroller are in the templates/ folder.

  • shoppingmodel.py - this module defines a set of classes which formulate the business entities used by the store. Real-world concepts like items, users, shopping carts, and item categories are laid out as Python classes.

  • shoppingdata.py - this module serves as a micro-database of a store's catalog. For most ecommerce applications, shoppingdata.py would be a library of database access code which queries a database and formats the resulting data into entity objects, i.e. instances of the classes found in the shoppingmodel module. For the purpose of this example application, it is merely a hardcoded set of product names and descriptions, organized into categories.

The file layout is as follows:

  • components/ - contains Myghty component files, which are always referenced by another Myghty template as either a straight included component, or as a source of method components. These files have the extension .myc. Included here are regular layout components such as the header, the sidebar, the root autohandler, as well as the method libraries common.myc and forms.myc.

  • htdocs/ - these are Myghty templates that are served directly, without being supplied arguments by a controller. While these templates could be served directly from the Myghty handler, in this case the ApacheHandler, this particular application's configuration has them served via a controller called a PathTranslator, which strips off the leading "<& common.myc:store_uri &>" prefix from the file URI before having Myghty service the template request.

  • templates/ - these Myghty templates are always serviced from a module component first (i.e. a controller), and all of them require arguments to be set up by its controller.

  • lib/ - this is where the regular Python modules are located. A sample httpd.conf file is here as well.

myghty-1.1/examples/shoppingcart/htdocs/images/0000755000175000017500000000000010501065764020746 5ustar malexmalexmyghty-1.1/examples/shoppingcart/htdocs/images/hat_myghty.gif0000644000175000017500000000416110501064120023575 0ustar malexmalexGIF87aÈÈ÷€€€€€€€€€ÀÀÀÀÜÀ¦Êð@ ` €   À à @ @@@`@€@ @À@à@` `@```€` `À`à`€ €@€`€€€ €À€à€   @ ` €   À à À À@À`À€À ÀÀÀàÀà à@à`à€à àÀààà@ @@@`@€@ @À@à@ @ @@ @` @€ @  @À @à @@@ @@@@@`@@€@@ @@À@@à@@`@ `@@`@``@€`@ `@À`@à`@€@ €@@€@`€@€€@ €@À€@à€@ @  @@ @` @€ @  @À @à @À@ À@@À@`À@€À@ À@ÀÀ@àÀ@à@ à@@à@`à@€à@ à@Àà@àà@€ €@€`€€€ €À€à€ € €@ €` €€ €  €À €à €@€ @€@@€`@€€@€ @€À@€à@€`€ `€@`€``€€`€ `€À`€à`€€€ €€@€€`€€€€€ €€À€€à€€ €  €@ €` €€ €  €À €à €À€ À€@À€`À€€À€ À€ÀÀ€àÀ€à€ à€@à€`à€€à€ à€Àà€àà€À À@À`À€À ÀÀÀàÀ À À@ À` À€ À  ÀÀ Àà À@À @À@@À`@À€@À @ÀÀ@Àà@À`À `À@`À``À€`À `ÀÀ`Àà`À€À €À@€À`€À€€À €ÀÀ€Àà€À À  À@ À` À€ À  ÀÀ Àà ÀÀÀ ÀÀ@ÀÀ`ÀÀ€ÀÀ ÀÀÿûð  ¤€€€ÿÿÿÿÿÿÿÿÿÿÿÿ!ù,ÈÈùÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳ§ÏŸ@ƒ J´¨Ñ£H“*]Ê´©Ó§P£JJµªÕ«X³jÝʵ«×¯`ÊK¶¬Ù³hÓª]˶­Û·pãÊK·®Ý»xóêÝË·ïF€ ö»S0…ƒ ÇL1°â•Ž3F~<0ÈÉ”ÿZ&¹9óÅÎ'A{nìòðhˆ¢!›>½põbÖ SÃt sMÚ£qß”­XwNß}ÿŽ—¸Nãt‘Wþ–ya¾Î GG;gu²×­×ÍÞ“ÿ{WïÝã‚÷ÉûìøŸç§¦GÏv=û´îß›÷_i} ÷æÇ}ìþ ÿV€‚E`ßQGŸ‚ 2¨ÕEAH“„CQøÚZΆQ Øó‡ &°T†-y¢‡!†˜‰,eÇ¡=0~Ø!Œö¼H#R,ªt‡ÿÜh#Œ<Ò(¢~Ví¢3™dIUu*"é¡@?29$Q9†&ˆTª(#_¦aUÓi%;õp€—¢yO=\>„À=2eiRt)iÏ&Õ3IÁΕ Ýc[iêm)æ@¤–ÄŸ à˜jç@—Š„gž¢ `Pù|öbIê›=ÿ¢”iH›.*)£H€TÖ€®¦J¤*BIàêj¢åIè(£ ª@»¶c;QH!Å(ÿÜ#Ená诿&l£Þ>¬[ô,½ú‘s'N¢&¤Œ @=6Êf¶À£ZÈëa¯ªØÚ*,Ÿ¸zî³Ö§®GìšÙ%‡›Äû©¾¤Ð .¦úŠx˜²¦*ì¯ÿܨ°Ì’* CU¬MNœÀ&š¤üé‡‹È ’²IÊÚoÉ›‘Œ0A ×{ÙËE)³&›ˆîÏ c7J¶<£K´iFßZÐÉØ. ÌœþC«Œ€ñhé—¼=ªÜ‘v¹~ÿrÜêºLk,ˆùJ!â&¸&/σz¼š¤ãJAЇªÞjO _“²wµLÉ·SŒ9ô7‡jªi#èÔczˆ£Ðm`0€Øã:ì°Ýÿ p¹Ë#Zåß f˜»šõ4ŠûC›´löQ˜ýüB)Ö“ÄÏÙZЮíùßÃÉfäç™çÛ‰ìúÁN˜×›c Üøäw)3àì„)Ò6OQê•‘_k*B>6PBr¸SÀSÕoL |¨$cAO>´­”"Ía©;Õ1Q-xAíPd<#ôR c‘ó¤pDËùLG>Çðä<6dáo4’Cœô%pTùámvD·ô0>BœIï“D íVíñ¡¦Ô²DÎÀGŠœÿÏKÒÄ”TQKþ!"$Æ1.¨ŒfÜŠ3Tž'=§Dj4!±ÒD;uQ2äYÌ+rÇWí1|üQâÒF, †Úâ …²ÄArÌI>¤#·gBÖi’é¢%K„I„ôq“Ãâ#AÉ™NÆF~‡ªLçj¸¢Tòp2¦`ïVÇZ¦&– Áekl¹:‡è2—Í¡c[~™A*ÆÑ˜j$¦f¾¢LŒ4ó„aôÏ3Q3Mr¥š’ÁæAHIm¦L<ÕäfYÄYɼs7Þôâ9g³N#¶ÓUï”Ë*ÙÏ⸒‹÷„M& +~êó3´lL@ÿYJ^ö’ M¨BÊІ:ô¡¨D 'JÑŠZô¢ͨF7Ê ÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0mH@;myghty-1.1/examples/shoppingcart/htdocs/images/hat_python.gif0000644000175000017500000000405110501064120023573 0ustar malexmalexGIF87aÈÈ÷€€€€€€€€€ÀÀÀÀÜÀ¦Êð@ ` €   À à @ @@@`@€@ @À@à@` `@```€` `À`à`€ €@€`€€€ €À€à€   @ ` €   À à À À@À`À€À ÀÀÀàÀà à@à`à€à àÀààà@ @@@`@€@ @À@à@ @ @@ @` @€ @  @À @à @@@ @@@@@`@@€@@ @@À@@à@@`@ `@@`@``@€`@ `@À`@à`@€@ €@@€@`€@€€@ €@À€@à€@ @  @@ @` @€ @  @À @à @À@ À@@À@`À@€À@ À@ÀÀ@àÀ@à@ à@@à@`à@€à@ à@Àà@àà@€ €@€`€€€ €À€à€ € €@ €` €€ €  €À €à €@€ @€@@€`@€€@€ @€À@€à@€`€ `€@`€``€€`€ `€À`€à`€€€ €€@€€`€€€€€ €€À€€à€€ €  €@ €` €€ €  €À €à €À€ À€@À€`À€€À€ À€ÀÀ€àÀ€à€ à€@à€`à€€à€ à€Àà€àà€À À@À`À€À ÀÀÀàÀ À À@ À` À€ À  ÀÀ Àà À@À @À@@À`@À€@À @ÀÀ@Àà@À`À `À@`À``À€`À `ÀÀ`Àà`À€À €À@€À`€À€€À €ÀÀ€Àà€À À  À@ À` À€ À  ÀÀ Àà ÀÀÀ ÀÀ@ÀÀ`ÀÀ€ÀÀ ÀÀÿûð  ¤€€€ÿÿÿÿÿÿÿÿÿÿÿÿ!ù,ÈÈùÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳ§ÏŸ@ƒ J´¨Ñ£H“*]Ê´©Ó§P£JJµªÕ«X³jÝʵ«×¯`ÊK¶¬Ù³hÓª]˶­Û·pãÊK·®Ý»xóêÝË·ïF€ ö»S0…ƒ ÇL1°â•Ž3F~<0ÈÉ”ÿZ&¹9óÅÎ'A{nìòðhˆ¢!›>½põbÖ SÃt sMÚ£qß”­XwNß}ÿŽ—¸Nãt‘Wþ–ya¾Î GG;gu²×­×ÍÞ“ÿ{WïÝã‚÷ÉûìøŸç§¦GÏv=û´îß›÷_i} ÷æÇ}ìþ ÿV€‚E`ßQGŸ‚ 2¨ÕEAH“„CQøÚZÎÖV†-q(”‡5wˆª'^Un±‰+Þ3ÐRlAФ‡âI¶šŽ6R%!IücO`.Z6$`5îv£IC&Xl"P€Iñ›‰‘¢£`TvŽVþX›miOŽT¦ ä–†]†åC[T •RV'öl"'IÞi%[$ç÷h (uÞ¹'¢ŒpÀG$r‘–iZœ=J)A–)¨‹Ф¤bZãöª¨G‘ÿVQœa*ž‘åh`þ#Eªpç tO ºv@—¼rÔjHÑÁúÏ=*Òø¨­ÿ` Xª_j—U4c’[äú-S&)d‹‚–ë_RE§§@÷$£Óš6ì¼ðv+¦aëîkÚ&¹¶©«¹9nÖe˜ìB5³ùi.A_š®`anÁ­@áN¼¹ºf—ýJÕtïÔ)Âp•_†)pÆþì䓇ŒÑ²Jäl¼R$¡è“Œ&-`Rú ¯Ë‰‘R´ÀS<1Ê"+,Ѧ´²YèÕù"Úë&VdÌ—†y×\ zªÀÙüÙSŒ9TgÖ[ÚÌ-`]ÿ£ò”IæÓ]PÄŦ-Ÿá,™eÕi™ªµk¶m·Ž¦jè׆¥z§”©JNôâ‰VtÂGaVÝܶ±)ú褗fàëVšô3˜I€Î¦ì¦Ï©ãÃ7ë\v<Tð– V=:ص@Êïs*KTy‚V4¬Ò›lNÀJ1ì°ÙC=)2F?c¼šv¿ ÷ÐrýøÖ§Ÿjô2Ò«<€È ^¢uÌÉÏyÙ?ýé¯Ýÿý›‹ÿÂö'ÌkˆxÀµÁÅ€çYàm˜;· Ð=”ÉYeAæ Cü Zü·Ÿ v(„Ì‚ Ý4ÿ%•Ä„(!a û³BI‰eƒ)¡H ¢QÐ$ l×sNø ²D‡QbiF´¿—)0ÄI#ŸÅL‘!Oœà„®ˆ.ê‹# ÅF1-çCP¤_³hÄ3"°Œ‰"ȶÍq‡pìâòÇ>Ú')~Ôcòˆ¦ˆî!‡ £|T;‡R"bb{Š8­<2…–¼¤²4@È?ØádþD™DJR‡”ˆAeàTI@VBJ•w eSbiBÒr„·tb.7´ËÐôR<¶™M0a³ “˜ql«”‰Ìþm©1ÏlfeŠçJiZóšØÌ¦6·ÉÍnzó›à §8ÇIÎršóœèL§:×ÉÎvºóðŒ§<çIÏzÚóžøÌ§>÷ÉÏ~% ;myghty-1.1/examples/shoppingcart/htdocs/images/hat_snake.gif0000644000175000017500000000417610501064120023363 0ustar malexmalexGIF87aÈÈ÷€€€€€€€€€ÀÀÀÀÜÀ¦Êð@ ` €   À à @ @@@`@€@ @À@à@` `@```€` `À`à`€ €@€`€€€ €À€à€   @ ` €   À à À À@À`À€À ÀÀÀàÀà à@à`à€à àÀààà@ @@@`@€@ @À@à@ @ @@ @` @€ @  @À @à @@@ @@@@@`@@€@@ @@À@@à@@`@ `@@`@``@€`@ `@À`@à`@€@ €@@€@`€@€€@ €@À€@à€@ @  @@ @` @€ @  @À @à @À@ À@@À@`À@€À@ À@ÀÀ@àÀ@à@ à@@à@`à@€à@ à@Àà@àà@€ €@€`€€€ €À€à€ € €@ €` €€ €  €À €à €@€ @€@@€`@€€@€ @€À@€à@€`€ `€@`€``€€`€ `€À`€à`€€€ €€@€€`€€€€€ €€À€€à€€ €  €@ €` €€ €  €À €à €À€ À€@À€`À€€À€ À€ÀÀ€àÀ€à€ à€@à€`à€€à€ à€Àà€àà€À À@À`À€À ÀÀÀàÀ À À@ À` À€ À  ÀÀ Àà À@À @À@@À`@À€@À @ÀÀ@Àà@À`À `À@`À``À€`À `ÀÀ`Àà`À€À €À@€À`€À€€À €ÀÀ€Àà€À À  À@ À` À€ À  ÀÀ Àà ÀÀÀ ÀÀ@ÀÀ`ÀÀ€ÀÀ ÀÀÿûð  ¤€€€ÿÿÿÿÿÿÿÿÿÿÿÿ!ù,ÈÈùÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳ§ÏŸ@ƒ J´¨Ñ£H“*]Ê´©Ó§P£JJµªÕ«X³jÝʵ«×¯`ÊK¶¬Ù³hÓª]˶­Û·pãÊK·®Ý»xóêÝË·ïF€ ö»S0…ƒ ÇL1°â•Ž3F~<0ÈÉ”ÿZ&¹9óÅÎ'A{nìòðhˆ¢!›>½põbÖ SÃt sMÚ£qß”­XwNß}ÿŽ—¸Nãt‘Wþ–ya¾Î GG;gu²×­×ÍÞ“ÿ{WïÝã‚Ol¼FÞgÇG.OÓz(©ÁÚÒ¢DX¨ªúäªk®<ò)+A¿:QŒ¶ê‰ë®Èªzj¤+æ°¡^z¬`ÉžÊã¨UъꪺVm´¢ »_E€áÊí¹æîJm«¹–Ûª¯³N…gºèr«îºòµ €®„2Ë‘³&Í›ìÀú>Ȫ¾É‹í¸äîKð·q"_¯ÆËp¥õ;gžùêcpÇhTê9±Ä )Àsæf`ùÄsœ{lÚÄÈ®¼)T[ &óÌ£,»9WlñS=ös>y~ 컽3Òš°tÓNT¿RíTÏK³‹2h8GÝõ þijØÇŽíÑ\Ÿ=²RŽȶÛOÿ£i6šþ^–fúÝ·Ö¡òªp©FÉæÞÏâVöÞ|· lâÀ™wµƒÁ·Ü^• x2ÿ3CŒBy¥I.‘‰ ç#Ðè­nøáЦî9sv»~PÐëÜp߈!¹úF°g~æÀ…?´ÚFvWÝ‘™½2O«éä_á™ZY´¦GN=áÖùo”Ú‹ôx÷®žþO¦Ã9þp>†¤ùæ¯*Ì~ÊÊg~Üy—9L°¯&W¿¶´?Iʽ«_£¬å;Œ¨.6ïK¥Ð·­ æOmŠà¿d² zÐ}üË 5c ~ÐO´™ívã7 þï„„Ûc%Âf5+…&ìØ§ÈÆ¼æ±'P~.|ýtªê-è4ß õ&D›5Ñf“B" +³D&l]Wìá ÿï°ÖLlá3гn£ J/Ua,PkX5®€?\ãÅÈF2Šk+RÜjÆǹqŠ|h³J"¹A1{k2书‹!r1ŠD]Pò8ÃÅ‘DMQYÉQe’tËÊ'2J-Fh”Aåú"ÙIKúÄ”w¤›([ùU’f(†é¢&ô7Xþn5¶S0—J:NN^ïiÜ. )Èæü‘-Á aZ¢)Mêp…–¢æ"âMIÑ;ÝÔLzøHpšÊœ¡Ag1Å£ÎØ´Ó“iË 6á y¶Ñž4§ßùJ_*ÈŸYfÀZ›\öO °iŒQ³Ð„rF™u¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©Nm;myghty-1.1/examples/shoppingcart/htdocs/images/logo.gif0000644000175000017500000000665010501064120022365 0ustar malexmalexGIF87a¡d÷€€€€€€€€€ÀÀÀÀÜÀ¦Êð@ ` €   À à @ @@@`@€@ @À@à@` `@```€` `À`à`€ €@€`€€€ €À€à€   @ ` €   À à À À@À`À€À ÀÀÀàÀà à@à`à€à àÀààà@ @@@`@€@ @À@à@ @ @@ @` @€ @  @À @à @@@ @@@@@`@@€@@ @@À@@à@@`@ `@@`@``@€`@ `@À`@à`@€@ €@@€@`€@€€@ €@À€@à€@ @  @@ @` @€ @  @À @à @À@ À@@À@`À@€À@ À@ÀÀ@àÀ@à@ à@@à@`à@€à@ à@Àà@àà@€ €@€`€€€ €À€à€ € €@ €` €€ €  €À €à €@€ @€@@€`@€€@€ @€À@€à@€`€ `€@`€``€€`€ `€À`€à`€€€ €€@€€`€€€€€ €€À€€à€€ €  €@ €` €€ €  €À €à €À€ À€@À€`À€€À€ À€ÀÀ€àÀ€à€ à€@à€`à€€à€ à€Àà€àà€À À@À`À€À ÀÀÀàÀ À À@ À` À€ À  ÀÀ Àà À@À @À@@À`@À€@À @ÀÀ@Àà@À`À `À@`À``À€`À `ÀÀ`Àà`À€À €À@€À`€À€€À €ÀÀ€Àà€À À  À@ À` À€ À  ÀÀ Àà ÀÀÀ ÀÀ@ÀÀ`ÀÀ€ÀÀ ÀÀÿûð  ¤€€€ÿÿÿÿÿÿÿÿÿÿÿÿ!ù,¡dÿÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3 < cBAidA“QT¹±£Ë/˜9³$M“7qÞ´I“gÍ“;öú3eP£C‘]y”iR¦JŸæ\:UdÕ¨T§bµªÕiÓ«^¥vÝ8öX² {zü©–,Z³nÍÖ\K—#Ý·wåÚÛV.^¾sïõ«.á¾lÎÚhc£¡†¬¹rfÇž9ƒ¶Ü™¥Æ‡lK«}9tjÖ«)+9€íÛ¸¼}Ú'í¥^£lÜ·q¡DuÖÌÍ|·íÞ>•×;=gôëɱ˞]Ò9sÞÿÀCÿ'*¼üqîäÏghwúAñoÛÃà_ýöÂÇØgá“ÿ¥àJ¶ô”ÕR\ í§ßw D˜Àwå% a&ˆ!6¦á‡,½æžAâIˆ›=Þf"+êQ^"˜‡2.Fãg‰ÝØa["$Ÿ„)ÚfO~Ú$‹GÞ'Qb{ Å#“p9˜”MBÙß_T&gZA*Y$‹ö}y$sbtubYVVj¢õUY¶¹Ð|Big— Ð ÀßÕ‰_~Éé¦t„j–¡ ^Øмåw§‘êÉ'¤õ9ŠßŸ™¦iL#qÚÝxùç£Hæ)Ÿž@Ú9ä¥JVè)€0Åÿº ¬½yªªxJ:)’bZ&¨À[‘—·’*¡®zžh'«¿ ëì³ ['®”"Ëh±Ë¦ í¶Û¢èe¤ªÚé媖z[f¸«zÉíºÁšûm¸ÌJ( €9ªªaÚËî¾§}«¯AêpAþ¶Ê¯™¯~dšºÙúK°Ãñ<ðÁ Šfgo!ôí¨ùŽ;»ÿ‚,±¼ çÉi(r¥õJÈŽ0o"³Ì$á&1à 3ĽÙs+¤H±É³V ¶ÝÃ×g/;I|GJÇ$G:ÊwIÔ³qo›4}[ë¾v´Ê#K=l’×ËkÖ·±b/¼§E‘[]¯…±Si‡]é¥!ÿÀÜ=y×ÇÊÜcºjo°M7¿ÃÜÊþ:6Û¶-n3rãF÷Û–Žgvå’;zÅ­E«÷¹Í—„¸‘äàQdÀæj“;:FŸÏþb€›9âB§7;6I°£9Ò È}ìQ¸k8¨¹7)ôîû–AyöM'Àúm"I»=̾ìá§EÏ_ÓÖû®°ÀÁDgÆß&ÅåÉWÿóî¢Ë·FÑ»]H´Ö>„í8ÁÞǺ—?ð $BÆC[ù"ÄŠ Z°‚yÚÈ=6x A‹B¢°‰{äç±{š@Ô÷åÅn"±GÐpS rÌ!HpÈA’Ô9L ÍÿúF¼< Î~üëH"æ)r·©Ç@(çRh„WœÚm<øè!à{¸Q¡Yúd®Ýã;CÈ·¦0‚de“\Á„4FÜX­>~K‚„W)jŽ¿:À÷F‘êÙ&ä»Í=P(½.n­i%”<â³5Úf;‘an6ÁEü°‚€Ù’ÃÊÄoP’”B™Îø´9ñRà#Éü¶8¿mq%·¡[èrG<.®í6i$¢"¢EÛˆq ¬+àDFI®Rz©i€:b­æHFˆ˜*fÕHF=XÒrøi]AxÙ>0‚Ó–¶Q&~Ø–„Э œÃÚXçZfÊv†Šr›HÑ(òåÿDU%€mª<×bHÝ-p˜É=ÀâF-|(Aº‰‘š9*ts,Õ?¤YD³Yóš¹ì˜=êg@æ‰zÖ³äžêÀ„†Ô¥•#:õ‡ÙÝo^­¨^—ûgö^’–Ö[èw77L­ôç <âÒß%?Iøä†Dtç‰Ï?«·¼WGè7™¼š.ì2R~ò‘|ÓÿK‡ž#þõËþ{ãsC(»½í¦W=â;ejÆ)èJŠýåkø™ Þð}¯=x– ö>×U?:”õé¿O?y»w\´‘ãS÷úÜç»ãd¡Õä(4Ð9¿³u£îÓ¾ôg–Ová·ù3BJ”Š}™üçI?|øâ$vñvAt§|¹g|û7nƒÂ&»·$(ãt¡7~R~ƒ"(~«ÇtOç8 W‚=t'h‚Ÿ{HáçÅ€.ƒº—}^'t!h›¡(47sŸÂƒÖ)“·}ª7ÞÇ}äGy-á Ñ·)5§ƒ=¸ƒPØPf„—·}©w„·w…†·rÑÏ2c§G…–…ï¡…ãÇ…Ûr…ªg„V8†j˜†mè f‡r8‡tX‡vèqLèƒNøƒO؇|ø‡{ˆ1…€8d)xˆ+˜ˆ…²yƒt*¸ˆ?LjCWIW‰h‰\1‚šh˜¸ž¸&™HuFóuÕ÷‹v8ŠªxŠ¥X¤øŠ«(v&„·vµx‹f‡‹ƒ—‹¼¸‹¾H‹½x@›aw¿HŒÀXŒzŒÆ¸ŒÉˆŒÃØŒÔ{î!3ŒwÖ˜Õ¸æ‘@t‡àŽâ8Žäh‡;myghty-1.1/examples/shoppingcart/htdocs/images/mug_myghty.gif0000644000175000017500000000426410501064120023615 0ustar malexmalexGIF87aÈÈ÷€€€€€€€€€ÀÀÀÀÜÀ¦Êð@ ` €   À à @ @@@`@€@ @À@à@` `@```€` `À`à`€ €@€`€€€ €À€à€   @ ` €   À à À À@À`À€À ÀÀÀàÀà à@à`à€à àÀààà@ @@@`@€@ @À@à@ @ @@ @` @€ @  @À @à @@@ @@@@@`@@€@@ @@À@@à@@`@ `@@`@``@€`@ `@À`@à`@€@ €@@€@`€@€€@ €@À€@à€@ @  @@ @` @€ @  @À @à @À@ À@@À@`À@€À@ À@ÀÀ@àÀ@à@ à@@à@`à@€à@ à@Àà@àà@€ €@€`€€€ €À€à€ € €@ €` €€ €  €À €à €@€ @€@@€`@€€@€ @€À@€à@€`€ `€@`€``€€`€ `€À`€à`€€€ €€@€€`€€€€€ €€À€€à€€ €  €@ €` €€ €  €À €à €À€ À€@À€`À€€À€ À€ÀÀ€àÀ€à€ à€@à€`à€€à€ à€Àà€àà€À À@À`À€À ÀÀÀàÀ À À@ À` À€ À  ÀÀ Àà À@À @À@@À`@À€@À @ÀÀ@Àà@À`À `À@`À``À€`À `ÀÀ`Àà`À€À €À@€À`€À€€À €ÀÀ€Àà€À À  À@ À` À€ À  ÀÀ Àà ÀÀÀ ÀÀ@ÀÀ`ÀÀ€ÀÀ ÀÀÿûð  ¤€€€ÿÿÿÿÿÿÿÿÿÿÿÿ!ù,ÈÈÿÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳ§ÏŸ@ƒ J´¨Ñ£H“*]Ê´©Ó§P£JJµªÕ«X³jÝʵ«×¯`ÊK¶¬Ù³hÓª]˶­Û·pãÊ €Ý»xóÒÍ™·/€‡~õî-Ù÷dáÁñÎTŒXâÝ/Œü“²ävf¾¼9igºŸ—†~ûWji¸§M£¾šZmkª¯ÏÆV½vöTÛbqGÕ –÷Sß^GßÞU¸iâ\ÿ"×üo¹ÖÒ·B¯ùvu×kNëkíBGwg|Oãã­â¶||úª¾ÙÛ”ð=ìºôY2vhÿßúÄÄ æu¶Cæ7 v&¨`GýÑ–U„»mE!TVF k:Hiââ}þÅ5"M'’ˆ!hÊ•"L/–èTŒ-6EcMšwÝoNÆÛúõˆàd>ÇŸB@®”$Š€%´dJOÊDœmQVQ Øó–\&Àr³UiRt]jÙe—:Y^‘aiÏ›[fù¦=nÎ)b]k®Ø¦—vÖù&–ÿÌéå|xÖÇ&CeÂé'rþ™%Ž…$&a¡Ùç£-Ê%¤MªçB›Æ*š¢žÉd§’š—Ž&ÀN=@*–¯ÞSO¨!pOHjzÊ#¨¦ºÉ]õ è]Rÿ°3¨C÷ÔlŠÐ4)Iº è@£hb™67ì_nêж³Ùƒ­Eáf§ªAæ.û)£H€™Ö€ÁðJD/BIŒo¤âî[P¿2 ë tp;™±…RŒòÏ=R$¡å×.¼°][Ë2¶ K±E  ïŠ$ÄúJË/«Z–ª )ÍE ²™À*…Ak™M¯É;<,ÁZº|@µ?â¼ãŒ %š)—›­nÒ¤ìê2fI{ùÆ¥5ëðÂÿÐm­Ã°‰@RlM$ªë<1«Ùb¹‰&y«»åÚ^²¶¤ìÝô?ÿ]÷ft_MÜD7”ï×6†½iÏ]j²‰—.ï9™ú´µ¡ü¸Í——–ùÀÝm²ç^k,qáÿ+2–vú­¨[ÞœòÑ}ûÌŲ}ï€çü+Býv‰´^nBpH?®¬Û§m³¤hIïÀö [;)Ó @ÏÔé+Ø¢gk¬ubyõ`—FÁ<¿¼é8€=è·lyÿ@@üþf¿h]{ÙÜØ€ç¶‚(0Võ°² ¿ñ®z cJ¹²ç/ r©IßZs0‰ØcX½sRž.èBê\æ:Aäö¿4ì? Ý +8ć£ú¡¹’ed‚%¤#ò9ß î‰Pb[è’.6çw;ƒ¢™Xx*&Zˆ c«ÌÅ)7¦P4{òáËTÇ%6Ž c …B/:1Ž‚<!íhH@æŠiÊá¸F’6BF’h¬ÿ:#òŒ_T‘ßØÉ a2”2ª'U($F‚ò¢Üä*ñèÉQ’Î’•µ”å,•2É ©ò~¥ôŽ~‰KuÈ•žÙK/y¥ÌLnÈ‘«Aå3¡é–e2ÓE©l‹5E²ÍöÀgB92ЇLÉ r¦¥›V*§0Õ9N´ sLÓTO<¿ÉNyÖ“žçl¥;õ)~šEG6zç"íɂĠwŠ¥6³YM„âË¡Þä%DÐ4MT:]LF•¹Q%uT2zIH/ã‘ˤ$åÈ1ùã¡”Â(œ.©LgJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—Ê!Ô¦:õ©PªT§JÕªZõªXͪV·ÊÕ®zõ«` +F;myghty-1.1/examples/shoppingcart/htdocs/images/mug_python.gif0000644000175000017500000000415710501064120023616 0ustar malexmalexGIF87aÈÈ÷€€€€€€€€€ÀÀÀÀÜÀ¦Êð@ ` €   À à @ @@@`@€@ @À@à@` `@```€` `À`à`€ €@€`€€€ €À€à€   @ ` €   À à À À@À`À€À ÀÀÀàÀà à@à`à€à àÀààà@ @@@`@€@ @À@à@ @ @@ @` @€ @  @À @à @@@ @@@@@`@@€@@ @@À@@à@@`@ `@@`@``@€`@ `@À`@à`@€@ €@@€@`€@€€@ €@À€@à€@ @  @@ @` @€ @  @À @à @À@ À@@À@`À@€À@ À@ÀÀ@àÀ@à@ à@@à@`à@€à@ à@Àà@àà@€ €@€`€€€ €À€à€ € €@ €` €€ €  €À €à €@€ @€@@€`@€€@€ @€À@€à@€`€ `€@`€``€€`€ `€À`€à`€€€ €€@€€`€€€€€ €€À€€à€€ €  €@ €` €€ €  €À €à €À€ À€@À€`À€€À€ À€ÀÀ€àÀ€à€ à€@à€`à€€à€ à€Àà€àà€À À@À`À€À ÀÀÀàÀ À À@ À` À€ À  ÀÀ Àà À@À @À@@À`@À€@À @ÀÀ@Àà@À`À `À@`À``À€`À `ÀÀ`Àà`À€À €À@€À`€À€€À €ÀÀ€Àà€À À  À@ À` À€ À  ÀÀ Àà ÀÀÀ ÀÀ@ÀÀ`ÀÀ€ÀÀ ÀÀÿûð  ¤€€€ÿÿÿÿÿÿÿÿÿÿÿÿ!ù,ÈÈÿÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳ§ÏŸ@ƒ J´¨Ñ£H“*]Ê´©Ó§P£JJµªÕ«X³jÝʵ«×¯`ÊK¶¬Ù³hÓª]˶­Û·pãÊ €Ý»xóÒÍ™·/€‡~õî-Ù÷dáÁñÎTŒXâÝ/Œü“²ävf¾¼9igºŸ—†~ûWji¸§M£¾šZmkª¯ÏÆV½vöTÛbqGÕ –÷Sß^GßÞU¸iâ\ÿ"×üo¹ÖÒ·B¯ùvu×kNëkíBGwg|Oãã­â¶||úª¾ÙÛ”ð=ìºôY2vhÿßúÄÄ æu¶Cæ7 v&¨`GýÑ–U„»mE!TVF k:Hiââ}þÅ5"M'’ˆ!hÊ•"L/–èTŒ-6EcMšwÝoNÆÛúõˆàd>ÇŸB@®”$Š€%´dJOÊDœmQ&ä‘õ9a]åiùÑ›Hæ=m!ŤHAŠJÈÍV¥I) €V6™åŠ!Ä?öÜEff}ÚµfXô&a!õ9è]{°‰@IØ%Jmv‰'B¤$©b÷ô5is‚‚*_öÈé©zvhg¡Jn[HjW¤RäU+öl"«žƒÞ:)[Œ @­·Þ“)°Ãÿ&{×­ÉÞz«CÞÉcB™hi±Þõlµ]«ç¨d tl¨öðùO¶köYn¹Ì6T©´3*+BÙ ¤kdrzW°ÿHQ®@ܺE¸ÝSk¸©°é¿Hr ¯ò–v˜j>›ï¹x•ÛéÀân*)Aiº¿!« é |Ž),°D®Úª—é*Ð=I˜)…ÅŸ)ŧ»ò¾Ùm¶ ¿‚™ pd›~ê¤Ã†Â\мùŠ2A=m«@[xŒµÕ‘Íì§›ò»´ËÙ9MÌy«4Ar² ê°~*ØÈ?—–Ä¢Œ®,¶¥ÐîoBPÓ,EÌ2šlÛõèÛ3ËÍ)‹÷Çoÿ¯ýpËM_z·÷fìç3S~À&Ïž»çÛ›™ 0餚®`@7L¶ß'Tkèí i^¥ÿÓ6¿—vlï¸#»¯˜»Ë÷ߘ⊱áúÞW°å{z^ì*nnöŽJÏØ]‹Ý÷¨f<{9¦¯~ú–g>~så}~A…{šÄùá¯>þS_.»ûÌs’€Ò6¿„«s BÝúHQÀY%/Z¬B ¹M„ j˜ f°Ø#ƒ¤@“ÓD³m‰p!œX-ˆÂ º°\DÓÍHCòiîCïsÕHt¤ÙÙpZ[ò!ün¨¦½Œˆð1bÙ¨"åùO):•hQ¥74HO’,²ôH.[²ËJÒòŒféåF„yÅEÚÒ˜±¼åXˆ9Jѓ̴ˆŽzóÌ_îÑ™×´¦ QM!fš>g&»)¬Ë´JœRB'hÔÉ&v6&z.§9ñ#O¸rž‰ñô‰Ïx¦¯Ÿ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽzô£ ©HGJÒ’šô¤(M©JWÊÒ–ºô¥ ;myghty-1.1/examples/shoppingcart/htdocs/images/mug_snake.gif0000644000175000017500000000431310501064120023370 0ustar malexmalexGIF87aÈÈ÷€€€€€€€€€ÀÀÀÀÜÀ¦Êð@ ` €   À à @ @@@`@€@ @À@à@` `@```€` `À`à`€ €@€`€€€ €À€à€   @ ` €   À à À À@À`À€À ÀÀÀàÀà à@à`à€à àÀààà@ @@@`@€@ @À@à@ @ @@ @` @€ @  @À @à @@@ @@@@@`@@€@@ @@À@@à@@`@ `@@`@``@€`@ `@À`@à`@€@ €@@€@`€@€€@ €@À€@à€@ @  @@ @` @€ @  @À @à @À@ À@@À@`À@€À@ À@ÀÀ@àÀ@à@ à@@à@`à@€à@ à@Àà@àà@€ €@€`€€€ €À€à€ € €@ €` €€ €  €À €à €@€ @€@@€`@€€@€ @€À@€à@€`€ `€@`€``€€`€ `€À`€à`€€€ €€@€€`€€€€€ €€À€€à€€ €  €@ €` €€ €  €À €à €À€ À€@À€`À€€À€ À€ÀÀ€àÀ€à€ à€@à€`à€€à€ à€Àà€àà€À À@À`À€À ÀÀÀàÀ À À@ À` À€ À  ÀÀ Àà À@À @À@@À`@À€@À @ÀÀ@Àà@À`À `À@`À``À€`À `ÀÀ`Àà`À€À €À@€À`€À€€À €ÀÀ€Àà€À À  À@ À` À€ À  ÀÀ Àà ÀÀÀ ÀÀ@ÀÀ`ÀÀ€ÀÀ ÀÀÿûð  ¤€€€ÿÿÿÿÿÿÿÿÿÿÿÿ!ù,ÈÈÿÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳ§ÏŸ@ƒ J´¨Ñ£H“*]Ê´©Ó§P£JJµªÕ«X³jÝʵ«×¯`ÊK¶¬Ù³hÓª]˶­Û·pãÊ €Ý»xóÒÍ™·/€‡~õî-Ù÷dáÁñÎTŒXâÝ/Œü“²ävf¾¼9igºŸ—†~ûWji¸§M£¾šZmkª¯ÏÆV½vöTÛbqGÕ –÷Sß^GßÞU¸iâ\ÿ"×üo¹ÖÒ·B¯ùvu×kNëkíBGwg|Oãã­â¶||úª¾ÙÛ”ð=ìºôY2vhÿßúÄÄ æu¶Cæ7 v&¨`GýÑ–U„»mE!TVF k:Hiââ}þÅ5"M'’ˆ!hÊ•"L/–èÔ…Œ¨TŒ.Ñ™-uhÞu¿%ÀD`W‘EþHØd¼áØ£H‰¤‘N"ØnU®dŸC&™eyü)ôeJ[úå˜gš†ad×>pîófœpJ©¤Hj&ĦIeÒ9§Ÿ]Þr³í¹äEsòÃO¢Š.¨¡ 抈§¢ŒbúèK’ÖWa¥—:ª¦D R§i~j£™:Z*¤ÍÕ5i Æù§­š ¡¬žRºª¥¸ÒYg®†¢Z¬xÿþÚ׫xMI%É6ëŒÊæÅì]ÎÖ¦´½ÒŠŸ”Ά.¸^êº`˜ÝRQ³â¶{f¶Èkª¾^Ù¥»øNIÎ{¬ªöæ+°—âšË-ºôzËäÀ <îˆÆf°˜÷6\¤>`¬ÆÃûQÄ@ª‹à£ï¶«ñÆ(oüjÁû+q½¯Üñ”'§l3•,ìrÈM·¯À'ãe³>?óËÈ‘N¬gÅùÖœ×ÐE{¹ëÁÿ¼´ÆgõÖ( ÍñÆv©Ü®ÁbÕå°µÖlÛüô×D«-öÃS#\µÂ­w×ÖfÌ÷Ð9mv¬h×'÷Þn/›¸ß)Ó-8Õ/ã½ðÚ'ÿ?‹íÅq“‹²ã!M¸ä1ãk-Ô—¹¹Ç۹Ȁå•Ïë¯÷òfGŽzçƒ#ÛïºxÁ;»‰Gzxã·'–»Ò#Ûå{>—“ž]—Ä]÷•Óöì˜ò¾7ïKÚRÏÑd©Ã„i Ñ;cN3ôÜwôù¡xZˆŸêdЄ ‚ª-õIBe²P "ôŸ®iè9Ù"Ñk>4 n©¨›Z\$#Ôh´‡ƒ±æb>Ê‘戤•1©aTO–~Ì¥mñæ7a:h J@—¹gùóȜˆK> ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©PªT§JÕªZõªXͪV·Ê!Õ®zõ«` «XÇJÖ²šõ¬hM«Z×ÊÖ¶ºõ­p«R;myghty-1.1/examples/shoppingcart/htdocs/images/tshirt_myghty.gif0000644000175000017500000000423310501064120024336 0ustar malexmalexGIF87aÈÈ÷€€€€€€€€€ÀÀÀÀÜÀ¦Êð@ ` €   À à @ @@@`@€@ @À@à@` `@```€` `À`à`€ €@€`€€€ €À€à€   @ ` €   À à À À@À`À€À ÀÀÀàÀà à@à`à€à àÀààà@ @@@`@€@ @À@à@ @ @@ @` @€ @  @À @à @@@ @@@@@`@@€@@ @@À@@à@@`@ `@@`@``@€`@ `@À`@à`@€@ €@@€@`€@€€@ €@À€@à€@ @  @@ @` @€ @  @À @à @À@ À@@À@`À@€À@ À@ÀÀ@àÀ@à@ à@@à@`à@€à@ à@Àà@àà@€ €@€`€€€ €À€à€ € €@ €` €€ €  €À €à €@€ @€@@€`@€€@€ @€À@€à@€`€ `€@`€``€€`€ `€À`€à`€€€ €€@€€`€€€€€ €€À€€à€€ €  €@ €` €€ €  €À €à €À€ À€@À€`À€€À€ À€ÀÀ€àÀ€à€ à€@à€`à€€à€ à€Àà€àà€À À@À`À€À ÀÀÀàÀ À À@ À` À€ À  ÀÀ Àà À@À @À@@À`@À€@À @ÀÀ@Àà@À`À `À@`À``À€`À `ÀÀ`Àà`À€À €À@€À`€À€€À €ÀÀ€Àà€À À  À@ À` À€ À  ÀÀ Àà ÀÀÀ ÀÀ@ÀÀ`ÀÀ€ÀÀ ÀÀÿûð  ¤€€€ÿÿÿÿÿÿÿÿÿÿÿÿ!ù,ÈÈÿÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳ§ÏŸ@ƒ J´¨Ñ£H“*]Ê´©Ó§P£JJµªÕ«X³jÝʵ«×¯`ÊK¶¬Ù³hÓª]˶­Û·pãÊK·®Ý»xóêÝË·¯ß¿€EL¸°áÂK^̸±ãlj!.8deÊ"/Œ9³ÉË9k&úŸè•§C{N\Zàj—¯UÇÖ›ÚàlÔ k×mm;æmÌ»uçþ­˜¸mãg…;ämYùfçb™K”Ž‘úCè\­W\<‘{GìVµ{$|Ìø¤ø§à½¦Wzìz¢íɾÿ9m|œ÷ÙÖ—™îþ–ÿÑÿàgÚÕŸ`ÈfZ‚æ)¸ƒ$Aèà‚/I8!…,Yx¡†q¨ ‡¨™ˆ‘Èl.WáL $`Ï?-Ƙ€V&V“Œ/Ê(#V5^ÔcoµhÏ0º8¤=BYÕ1Iƒ-þ£d’CFy䌾…ä$E[b¸ŽDR‰¤‘UºS¹ùi í8¥™‰£š]vÙ]ˆž!7g‘{îȧŽ+²Ùa–Õ½¦g’1²SÏøÙ¢¢÷Ô³§CÜ3¨k—Ú$鈥=›VÏ@Q&;X:tϪ‘wnw›qžDÊ`IšÀ“%ùЪNƆ¦ jr™qž¦úÏ(š †ÿ@œ¦…š™R:ÄëoöäѬ>ò×qÈÖ:)£H€qÖ€ºÓJt-BI ;ÝA"«d … è@£Œ‚€³®ÕÓgìD!…£üsI¼¸®ï¾;˜¼·Nœk¼Rl!e¼–rJ™±Þ^Çào`Æ£&¤,…”™"š¹D)… ¾˜¤|mÃæÊ*º/V|€@·¶zì½Åæ+!É1ùç&)73)–V¬Z©ÿüëÙªò¾û×·Êë›$ÅÐô¦‰ïL#oÛiÓº¶¸‰&c7 £Õ3²@®¤”]ó?æ¦üµh^ÿLÖ,­¶Šl?7ïAnšÜ¢&›ÌXqÙ ÃÿY”7=ÐÃ{w<¸g…Ÿ[PØ +¾x¾4 ²AÉîIn‘ƒE ,Ÿì nšîy]ºÆ£ª¦­Ñ¬ß8½ ê;ÌR̸ º À¼7ª3žÆ+ÆRòâµçÚcêè¤ ¯0/ª¾™ñ!¼B±·È(£ˆîŠ@=ôË8 ï‹ y`ÏþýȶÀ»¬‰/mŒ3¡TÓ­„´¯ Q*Rֻܰÿ ·*àC66žx­c øÀ/ɨIð›Ù„<ˆ€*A޽ÖG¬’°}Ì¡ŽnG®íïñú–è<ØÙÌKÃ" qˆ%êèTA)ÆAŠÜ¯ˆ¯«áñ2uCƒ&J®VÊÂÍ뎸Eö1ÑL8LZ¬È˜>32±LÉjãtØ(Ç.ÆŽ:*Ÿ‘6 1v!t‹†þè§2®…C„¼ “"¢*ò-‹Ü£Z"©)ý\…’FÁ$€ yIN†Ç“K%U4 ŸN ’G§4¥%UyHT¶…”Jå'_éÊVÖr’Y%Pt)¯F[²2-¼TI0{2L”s'Çü -o L­’™Éq¦/Í\>“šfI¦2§9K\Z“›¡ç(¯ÙÍf~Ó›Ð̦4Ñ™NùlE›6§bÄ9NvbÓë´Ï;íyÏèd‡ŸåTç>Í™O´,òœ'AÏYÍ6ô cI¨BÊP„vE¢’ì§{. Lòxô£ ©H½“¢’šô¤(M©JWÊÒ–ºô¥0©LgJÓšÚô¦8Í©NwÊÓžúô§@ ªP‡JÔ¢õ¨HMªR—ÊÔ¦:õ©" ;myghty-1.1/examples/shoppingcart/htdocs/images/tshirt_python.gif0000644000175000017500000000413010501064120024332 0ustar malexmalexGIF87aÈÈ÷€€€€€€€€€ÀÀÀÀÜÀ¦Êð@ ` €   À à @ @@@`@€@ @À@à@` `@```€` `À`à`€ €@€`€€€ €À€à€   @ ` €   À à À À@À`À€À ÀÀÀàÀà à@à`à€à àÀààà@ @@@`@€@ @À@à@ @ @@ @` @€ @  @À @à @@@ @@@@@`@@€@@ @@À@@à@@`@ `@@`@``@€`@ `@À`@à`@€@ €@@€@`€@€€@ €@À€@à€@ @  @@ @` @€ @  @À @à @À@ À@@À@`À@€À@ À@ÀÀ@àÀ@à@ à@@à@`à@€à@ à@Àà@àà@€ €@€`€€€ €À€à€ € €@ €` €€ €  €À €à €@€ @€@@€`@€€@€ @€À@€à@€`€ `€@`€``€€`€ `€À`€à`€€€ €€@€€`€€€€€ €€À€€à€€ €  €@ €` €€ €  €À €à €À€ À€@À€`À€€À€ À€ÀÀ€àÀ€à€ à€@à€`à€€à€ à€Àà€àà€À À@À`À€À ÀÀÀàÀ À À@ À` À€ À  ÀÀ Àà À@À @À@@À`@À€@À @ÀÀ@Àà@À`À `À@`À``À€`À `ÀÀ`Àà`À€À €À@€À`€À€€À €ÀÀ€Àà€À À  À@ À` À€ À  ÀÀ Àà ÀÀÀ ÀÀ@ÀÀ`ÀÀ€ÀÀ ÀÀÿûð  ¤€€€ÿÿÿÿÿÿÿÿÿÿÿÿ!ù,ÈÈÿÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳ§ÏŸ@ƒ J´¨Ñ£H“*]Ê´©Ó§P£JJµªÕ«X³jÝʵ«×¯`ÊK¶¬Ù³hÓª]˶­Û·pãÊK·®Ý»xóêÝË·¯ß¿€EL¸°áÂK^̸±ãlj!.8deÊ"/Œ9³ÉË9k&úŸè•§C{N\Zàj—¯UÇÖ›ÚàlÔ k×mm;æmÌ»uçþ­˜¸mãg…;ämYùfçb™K”Ž‘úCè\­W\<‘{GìVµ{$|Ìø¤ø§à½¦Wzìz¢íɾÿ9m|œ÷ÙÖ—™îþ–ÿÑÿàgÚÕŸ`ÈfZ‚æ)¸ƒ$Aèà‚/I8!…,Yx¡†q¨ ‡¨™ˆ‘Èl.WaŠ ™X‹ ¹x‘Œ½íÀy´Å&RðxÏ@[H±)R¢XH8n7^’p˜lFT`n7~gšeIüca?r¶å`F v¥•Uj$“m&aY°‰@I &ÅHž1‰fw%¾ö)ŽÅ‰Ø=‡Í¹ Fžy˜=NÆ©(~:F¥kúö"em!ç`qJa˜¦Ø³É¥Œ†ÉéœlášrzŸ¥¢ê*aœºÊé éi& ·çm_zf)a´òYP¯Œºö£@¬‚©å²¿¹ÿ¥=ËÆŠPlSŽY&žÓVúÛ¯}Z™“Ç fê?R@+Мnqì@÷hzìkøi.¥5*)étÆmëÙ=;I+¸ÿü:´€ª‹l£¨Dd˜[Œ»p‘p†©¥§–zÐm"ÞI‡ þö©@÷$¤ÿî+¤‚šð „[q·ã&ÄçVæ§ ôÖ+åL¦§/B£JLÊ1o*Ð]te4Ç*¨Ÿ.“V+¶÷Æ(¡Ç¼†3ANZ<¨€ *sÀK‹–ÄšlVuh^GƒþüOÈR$+›®&Að`o ²ØˆýCJßcÓ¬uÅ[³Ýv‹o?7BÂþã-c«V²œçnBÿkÀY²ì« h®¹kÎŽÝòÅ1ºÍóâ’½ockÿƒ0›ÿÓõ¸#;Xí—ðîéäb®óÔ©7~1ˆÄñ ­ÀŒŽÝäa¦BËjç†AËé›ÐZÏ·í¦o?<ñ¬_+µ½Í^y觯¾ð9>®¤™H\¢ÎÏìXÝæ/–úæ ­¸ŠÙªÉjªe­iÕ¯f”ëú<§>Rt-mßs\WTÀ-¤]ƒÛDß R$u탱GI1$ )dÁ*á&HÈ/jp…l´48$’¡îqíãÿ7äO€ã ù4f"Ñ^F4^UH´Ãž$Q‰»ÊWç–)¯-V¬š~¤ø–<,Šo-^¤ ¹XE2ŽQˆeDã—ØE3nQo„£ZÂ(F0b…Žð¹cå˜<hlŒ#““?Å´Zã Í‚HÜ2vÔã#©ÒHŸTR%—ÜI&Q²Éœtò3“¤d({8J©|ò&§ŒP)£’Ê#JrŽZi%)ŸX®*²¤â+ûhËHÒ—»$d/y9Lûl%—Zt£0IÌb22;°÷ÉÏ~úóŸ ¨@JЂô M¨BÊІ:ô¡¨D'JÑŠZô¢ͨF7ÊÑŽz40;myghty-1.1/examples/shoppingcart/htdocs/images/tshirt_snake.gif0000644000175000017500000000425110501064120024116 0ustar malexmalexGIF87aÈÈ÷€€€€€€€€€ÀÀÀÀÜÀ¦Êð@ ` €   À à @ @@@`@€@ @À@à@` `@```€` `À`à`€ €@€`€€€ €À€à€   @ ` €   À à À À@À`À€À ÀÀÀàÀà à@à`à€à àÀààà@ @@@`@€@ @À@à@ @ @@ @` @€ @  @À @à @@@ @@@@@`@@€@@ @@À@@à@@`@ `@@`@``@€`@ `@À`@à`@€@ €@@€@`€@€€@ €@À€@à€@ @  @@ @` @€ @  @À @à @À@ À@@À@`À@€À@ À@ÀÀ@àÀ@à@ à@@à@`à@€à@ à@Àà@àà@€ €@€`€€€ €À€à€ € €@ €` €€ €  €À €à €@€ @€@@€`@€€@€ @€À@€à@€`€ `€@`€``€€`€ `€À`€à`€€€ €€@€€`€€€€€ €€À€€à€€ €  €@ €` €€ €  €À €à €À€ À€@À€`À€€À€ À€ÀÀ€àÀ€à€ à€@à€`à€€à€ à€Àà€àà€À À@À`À€À ÀÀÀàÀ À À@ À` À€ À  ÀÀ Àà À@À @À@@À`@À€@À @ÀÀ@Àà@À`À `À@`À``À€`À `ÀÀ`Àà`À€À €À@€À`€À€€À €ÀÀ€Àà€À À  À@ À` À€ À  ÀÀ Àà ÀÀÀ ÀÀ@ÀÀ`ÀÀ€ÀÀ ÀÀÿûð  ¤€€€ÿÿÿÿÿÿÿÿÿÿÿÿ!ù,ÈÈÿÿ H° Áƒ*\Ȱ¡Ã‡#JœH±¢Å‹3jÜȱ£Ç CŠI²¤É“(Sª\ɲ¥Ë—0cÊœI³¦Í›8sêÜɳ§ÏŸ@ƒ J´¨Ñ£H“*]Ê´©Ó§P£JJµªÕ«X³jÝʵ«×¯`ÊK¶¬Ù³hÓª]˶­Û·pãÊK·®Ý»xóêÝË·¯ß¿€EL¸°áÂK^̸±ãlj!.8deÊ"/Œ9³ÉË9k&úŸè•§C{N\Zàj—¯UÇÖ›ÚàlÔ k×mm;æmÌ»uçþ­˜¸mãg…;ämYùfçb™K”Ž‘úCè\­W\<‘{GìVµ{$|Ìø¤ø§à½¦Wzìz¢íɾÿ9m|œ÷ÙÖ—™îþ–ÿÑÿàgÚÕŸ`ÈfZ‚æ)¸ƒ$Aèà‚/I8!…,Y¨b r•†ˆ‡‡I%âF'FÀ,0X‹-¨SŠш Œ/Âè¢9ñh‘ß‘·bŒ@ÎT$EGj$ä,ÊhS’Ó= À>Tî3X•UæèdMPFÔ%Wf9%–V¹¥‘0}™b˜üðÃf›nšÙåf!Í)Ùx`ŽÙæ›{ʹáœv^÷i?ê'•pÆÙd e%£Ë¡èYžˆºO¢ZBÚ¡!úVãk”ŠIf™‹ž©k¦YݦHŽIe˜XfjjC *É_¡ÈX©„é¸c‰uw‘¦´n—®‹ñš#ŽÞY–ÿë°·Ngœ……ùjíµ;úêaª”ít^×+¶ä.ªíá²ê%šâK+“åÆk®µ³rúl”ß>'è»òö›-½IN».»éž:œ¿ÿ{.Š3D¬Á‚˜à² ³¨Ïës1‘ך:ñ¾ùª îÁ —ìkƧ¬ñ¼ Ûê°» ‹ïs,›Ü"Ê*çló¢ Ã|,—÷"ÉoÂ(–³>· -ÄA‡,#ƒI—‹³aGGÍl#‹ì©k>B @Ƈ öØ)½±Æƒ­Œí– ÷ð¾Ez=¶ØtçLõÙH­6ÀXÏš”GÊ}ôàhfqÙz«Üq×Bÿýäxï>x²v¯x4ß«6nÿïT¾87Ê¿Ž{xµ7ï}uæ¿ ›¸ñ^µè›N¤Ë­^Õ¶aùäž»á±Îuï)cŽºæTÝN˜î»“^9׃ ¿t[Æ€|>ã¾NÚŠë8«êF?}õÖ«vøÆÚþÖíÓã­ò¶žë¬tßÐ{™>Î냖¶ûï¿–†óWžZÒÙ+äÜb¡þùTðŠÝÚu¾‡ Ï1Ç^ðœÇ-ý8ä{9Nóç,6DwÿÈÃÀüyI`dˆC˜êh‚Ü ã‘'G¤à ¡Ã½¢ð¯…¹êUu(êô>Ÿ:Y ˬë±ÏO¹Iá°Myghty Demo Store

Welcome to the Myghty Shopping Cart demo. The purpose of this demo is to provide a reasonably functional web application which illustrates the major features of Myghty, advanced examples of usage, as well as suggested practices to achieve a straightforward and maintainable application.

The store itself is a pretty routine application, with its inner workings being the more interesting thing to see. To that end, the source of all application files is available via the links in the left-hand sidebar.

While all products are fictional, users are free to download the five minute microsoft paint images in return for a small fee :) .

on to the store!
architectural introduction myghty-1.1/examples/shoppingcart/htdocs/shopping.css0000644000175000017500000000313310501064120022023 0ustar malexmalexbody, td { font-family: arial, sans-serif; font-size: 12px; } body { background-color: #FDFBFC; margin:10px 10px 10px 10px; } p { margin-top:10px; margin-bottom:10px; } a {font-weight:normal; text-decoration:underline;} a:link {color:#0000FF;} a:visited {color:#0000FF;} a:active {color:#0000FF;} a:hover {color:#700000;} .header { border:1px solid; height:100px; width:500px; margin-bottom:15px; } .headertext1 { font-size:24px; font-weight:bold; } .headertext2 { font-size:16px; font-weight:bold; } .headertext3 { font-size:14px; } .sidebar { border:1px solid; /*float:left;*/ background-color:#bfbfbf; width:135px; height:100%; padding:0px 0px 0px 5px; } .container { } .main { /*margin-left:150px;*/ margin-left:10px; } .logo { float:left; } .titletext { font-family:arial, sans-serif; font-style:italic; font-weight:bold; font-size:32px; text-align:center; padding:20px; } .toolbar { float:right; margin-right:10px; } .breadcrumb { margin:10px 10px 20px 10px; } .boldbreadcrumblink { font-weight:bold; } .breadcrumblink { } .categorylist { } .categorylink { } .categoryitemcell { } .itemborder { background-color:#BFBFBF; padding:10px; margin:10px; } .itemimage { border:none; } .categoryitemname { text-align:center; } .itemdescription { } .itemname { font-weight:bold; } .variantname { padding:2px; } .variantoptions { padding:2px; } .cartheader { font-weight:bold; } .checkoutbox { margin:5px 5px 25px 5px; } .borderbox { margin:2px; padding:5px; border:1px solid; } .smallbutton { font-size:10px; } .smalllink { font-size:11px; } myghty-1.1/examples/shoppingcart/htdocs/syntaxhighlight.css0000644000175000017500000000103510501064120023411 0ustar malexmalex .substitution, .compcall { color: #DF2020; } .controlline { color: #10109E; } .doctag_text, .python_comment, .doctag { color: #109010; } .argstag_text { color: #10109E; } .blocktag, .python_keyword, .deftag, .argstag { #color: #1010FF; color: #0908CE; } .blocktag_text { color: #10109E; } .python_literal, .python_number { color: #804049; } .text { color: #807079; } .python_operator { color: #EF0005; } .python_enclosure { color: #0000FF; } .compname { color: #272767; } .python_name, name { color: #070707; } myghty-1.1/examples/shoppingcart/lib/0000755000175000017500000000000010501065764016763 5ustar malexmalexmyghty-1.1/examples/shoppingcart/lib/form.py0000644000175000017500000003261210501064121020266 0ustar malexmalex""" a set of classes that represent an HTML form, including its field names, the values of those fields corresponding to their application setting as well as the value pulled from an HTTP request, and validation rules for the fields. "hierarchical forms" can be created as well, allowing a form to be grouped into "subforms", which may live on the same HTML page or across several HTML pages. """ from myghty.util import * import inspect, datetime, types class FormElement: def __init__(self, name, **params): self.name = name self.description = '' def set_form(self, form):pass def set_request(self, req, validate = True):pass def is_valid(self):return None def unvalidate(self):pass def get_valid_message(self):return '' class Form(FormElement): def __init__(self, name, elements, **params): FormElement.__init__(self, name) self.isvalid = None self.elements = OrderedDict() for elem in elements: self.elements[elem.name] = elem elem.set_form(self) def set_form(self, form):pass def show_default(self):[elem.show_default() for elem in self.elements.values()] def show_value(self):[elem.show_value() for elem in self.elements.values()] def show_request_value(self):[elem.show_request_value() for elem in self.elements.values()] def is_valid(self): return self.isvalid def get_valid_message(self): if self.isvalid: return "Form is valid" elif self.isvalid is False: return "Some fields could not be validated" def unvalidate(self): self.isvalid = None for elem in self.elements.values(): elem.unvalidate() def get_field(self, name): try: return self.elements[name] except KeyError: return None def set_request(self, req, validate = True): self.isvalid = True for elem in self.elements.values(): elem.set_request(req, validate) if not elem.is_valid(): self.isvalid = False def _fieldname(self, formfield): return formfield.name def reflect_from(self, object): for elem in self.elements.values(): elem.reflect_from(object) def reflect_to(self, object): for elem in self.elements.values(): elem.reflect_to(object) class SubForm(Form): def _fieldname(self, formfield): return self.name + "_" + formfield.name class FormField(FormElement): def __init__(self, name, description = None, required = False, default = None, textsize = None, options = None, **params): FormElement.__init__(self, name) if description is None: self.description = name else: self.description = description self.required = required self.options = options self.textsize = textsize # default value (any type) self.default = default # programmatically set value (any type) self.value = None # value taken from the request (any type) self.request_value = None # current used value (any type) self.currentvalue = default # string display value if self.currentvalue is None: self.displayvalue = '' else: self.displayvalue = str(self.currentvalue) self.displayname = None self.valid_message = '' self.isvalid = None def set_form(self, form): self.displayname = form._fieldname(self) def set_value(self, value): self.value = value self.currentvalue = value if self.currentvalue is None: self.displayvalue = '' else: self.displayvalue = str(self.currentvalue) def show_default(self):self.displayvalue = self.default def show_value(self):self.displayvalue = self.value def show_request_value(self):self.displayvalue = self.request_value def reflect_to(self, object): if hasattr(object, self.name): setattr(object, self.name, self.displayvalue) def reflect_from(self, object): if hasattr(object, self.name): self.set_value(getattr(object, self.name)) def is_valid(self): """returns whether or not this field is valid. note that the third state of None indicates this field has not been validated.""" return self.isvalid def get_valid_message(self): return self.valid_message def set_request(self, request, validate = True): """sets the request for this form. if validate is True, also validates the value.""" try: self.request_value = request[self.displayname] except KeyError: self.request_value = None if validate: if self.required and not self.request_value: self.isvalid = False self.valid_message = 'required field "%s" missing' % self.description else: self.isvalid = True if self.request_value is None: self.displayvalue = '' else: self.displayvalue = str(self.request_value) self.currentvalue = self.request_value def unvalidate(self): """resets the isvalid state of this form to None""" self.isvalid = None self.valid_message = '' class IntFormField(FormField): def __init__(self, *args, **params): FormField.__init__(self, *args, **params) self.currentvalue = None def set_request(self, request, validate = True): FormField.set_request(self, request, validate) try: if self.currentvalue == '': self.currentvalue = None if self.currentvalue is not None: self.currentvalue = int(self.currentvalue) except ValueError: if self.isvalid and validate: self.valid_message = 'field "%s" must be an integer number' % self.description self.isvalid = False self.currentvalue = None class CompoundFormField(SubForm): """ a SubForm that acts like a single formfield in that it contains a single value, but also contains subfields that comprise elements of that value. examples: a date with year, month, day fields, corresponding to a date object more exotic examples: a credit card field with ccnumber, ccexpiration fields corresponding to a CreditCard object, an address field with multiple subfields corresopnding to an Address object, etc. """ def __init__(self, name, elements, description = None, **params): SubForm.__init__(self, name, elements, **params) self.value = None self.request_value = None self.displayvalue = None if description is None: self.description = name else: self.description = description def set_value(self, value): self.value = value self.currentvalue = value self.displayvalue = str(value) self.set_compound_values(value) def reflect_to(self, object): if hasattr(object, self.name): setattr(object, self.name, self.currentvalue) def reflect_from(self, object): if hasattr(object, self.name): self.set_value(getattr(object, self.name)) def show_default(self): self.displayvalue = self.default [elem.show_default() for elem in self.elements.values()] def show_value(self): self.displayvalue = self.value [elem.show_value() for elem in self.elements.values()] def show_request_value(self): self.displayvalue = self.request_value [elem.show_request_value() for elem in self.elements.values()] class CCFormField(FormField): def set_request(self, request, validate = True): FormField.set_request(self, request, validate) if ( self.currentvalue and self.isvalid and validate and not self.luhn_mod_ten(self.currentvalue)): self.isvalid = False self.valid_message = 'invalid credit card number' def luhn_mod_ten(self, ccnumber): """ checks to make sure that the card passes a luhn mod-10 checksum. courtesy: http://aspn.activestate.com """ sum = 0 num_digits = len(ccnumber) oddeven = num_digits & 1 for count in range(0, num_digits): digit = int(ccnumber[count]) if not (( count & 1 ) ^ oddeven ): digit = digit * 2 if digit > 9: digit = digit - 9 sum = sum + digit return ( (sum % 10) == 0 ) class DateFormField(CompoundFormField): def __init__(self, name, fields, yeardeltas = range(-5, 5), required = False, *args, **params): elements = {} for field in fields: if field == 'ampm': elements[field] = FormField(field, required = required) else: elements[field] = IntFormField(field, required = required) for key in ['year', 'month', 'day']: if elements.has_key(key): self.hasdate = True break else: self.hasdate = False for key in ['hour', 'minute', 'second']: if elements.has_key(key): self.hastime = True break else: self.hastime = False assert (self.hasdate or self.hastime) CompoundFormField.__init__(self, name, elements.values(), **params) self.valid_message = "" self.required = required if self.hasdate: today = datetime.datetime.today() year = today.year self.yearrange = [year + i for i in yeardeltas] def set_compound_values(self, value): if self.hasdate: self.elements['year'].set_value(value.year) self.elements['month'].set_value(value.month) self.elements['day'].set_value(value.day) if self.hastime: if self.elements.has_key('ampm'): v = value.hour % 12 if v == 0: v = 12 self.elements['hour'].set_value(v) else: self.elements['hour'].set_value(value.hour) self.elements['minute'].set_value(value.minute) self.elements['second'].set_value(value.second) if self.elements.has_key('ampm'): self.elements['ampm'].set_value(value.hour > 12 and 'pm' or 'am') def get_valid_message(self): return self.valid_message def set_request(self, request, validate = True): CompoundFormField.set_request(self, request, validate) if validate: for elem in self.elements.values(): if elem.is_valid() is False: self.valid_message = 'field "%s": %s' % (self.description, elem.get_valid_message()) return args = {} has_value = False if self.hasdate: dummy = datetime.date.min for key in ['year', 'month', 'day']: if self.elements.has_key(key): args[key] = self.elements[key].currentvalue if args[key] is not None: has_value = True else: args[key] = getattr(dummy, key) if self.hastime: dummy = datetime.time.min for key in ['hour', 'minute', 'second']: if self.elements.has_key(key): args[key] = self.elements[key].currentvalue if args[key] is not None: has_value = True else: args[key] = getattr(dummy, key) if self.elements.has_key('ampm'): if self.elements['ampm'] == 'pm': args['hour'] += 12 elif args['hour'] == 12: args['hour'] = 0 if not has_value: self.request_value = None return try: if self.hasdate and self.hastime: value = datetime.datetime(**args) elif self.hasdate: value = datetime.date(**args) else: value = datetime.time(**args) self.request_value = value self.currentvalue = value self.isvalid = True except TypeError, e: self.isvalid = False self.currentvalue = None self.valid_message = 'field "%s" does not contain a valid date/time (%s)' % (self.description, str(e)) except ValueError, e: self.isvalid = False self.currentvalue = None self.valid_message = 'field "%s" does not contain a valid date/time (%s)' % (self.description, str(e)) myghty-1.1/examples/shoppingcart/lib/sample_httpd.conf0000644000175000017500000000314210501064121022300 0ustar malexmalex# plain document roots Alias /examples/shoppingcart/docs/ "/path/to/site/examples/shoppingcart/htdocs/" Alias /examples/shoppingcart/store/source/ "/path/to/site/examples/shoppingcart/" # match store URIs to Myghty handler # set apache handler SetHandler python-program PythonHandler myghty.ApacheHandler::handle # set Python import path PythonPath "[\ '/path/to/site/examples/shoppingcart/lib'\ '/path/to/myghty/doc/lib'\ ] + sys.path" # strip off path prefix PythonOption MyghtyPathTranslate "[(r'/examples/shoppingcart/store/', '/')]" # resolve URIs into the 'shoppingcontroller.py' module first # here, we do it directly by path. But it can also be # broken down into regular expressions for each component # via the 'module_components' parameter (aka MyghtyModuleComponents). PythonOption MyghtyModuleRoot "['shoppingcontroller']" # else, resolve URI's into one of three template file directories PythonOption MyghtyComponentRoot "[\ {'components':'/path/to/site/examples/shoppingcart/components'},\ {'templates':'/path/to/site/examples/shoppingcart/templates'},\ {'htdocs':'/path/to/site/examples/shoppingcart/htdocs'},\ ]" # store cache files PythonOption MyghtyDataDir "r'/path/to/site/cache/'" # some attributes for the store PythonOption MyghtyAttributes "{\ 'store_uri' : '/examples/shoppingcart/store/',\ 'store_document_uri' : '/examples/shoppingcart/docs/'\ 'store_path' : '/examples/shoppingcart/store/'\ }" # set some session parameters PythonOption MyghtySessionTimeout 1800 PythonOption MyghtySessionKey r"myghty_store_id" myghty-1.1/examples/shoppingcart/lib/shoppingcontroller.py0000644000175000017500000003510110501064121023252 0ustar malexmaleximport posixpath as unixpath import myghty.component as component import myghty.escapes as escapes import re, os import shoppingdata, shoppingmodel, form from statemachine import * import highlight # shopping cart commands CMD_ADD = 'add' CMD_UPDATE = 'update' CMD_REMOVE = 'remove' CMD_NEXT = 'next' CMD_PREVIOUS = 'previous' # checkout process states # the numbers are so they can be compared for ordering CHECKOUT_START = '1_start' CHECKOUT_BILLING = '2_billing' CHECKOUT_SHIPPING = '3_shipping' CHECKOUT_PAYMENT = '4_payment' CHECKOUT_CONFIRM = '5_confirm' CHECKOUT_DONE = '6_done' ## module components ## class _Store(object): def do_component_init(self, component): self.store_path = component.interpreter.attributes.get('store_path') def catalog(self, m): """catalog component, parses the path and displays product list pages""" app = _RequestHelper(m, self) match = re.match(r".*/catalog/(.*/)?$", m.request_path) if not match: m.abort(404) path = match.group(1) if path is not None: path = escapes.url_unescape(path) category = shoppingdata.store if path: try: category = category.get_category('/' + path) except KeyError: m.abort(404) if len(category.items) > 0: items = category.items else: items = shoppingdata.featureditems.items app.show_template("catalog.myt", category = category, items = items) def item(self, m): """item component, parses the path and displays individual item pages""" match = re.match(r".*/item/(.*)/?$", m.request_path) itemname = match.group(1) if itemname is not None: itemname = escapes.url_unescape(itemname) try: item = shoppingdata.store.get_item(itemname) except KeyError: m.abort(404) _RequestHelper(m, self).show_template("item.myt", category = item.primarycategory, item = item) def cart(self, m, ARGS, cmd = None, qty = None, itemname = None, index = None): """cart component, displays the shopping cart and handles updates""" app = _RequestHelper(m, self) save = False user = app.get_user() if user.cart is None: user.cart = shoppingmodel.Cart() save = True if itemname is not None: item = shoppingdata.store.get_item(itemname) variants = [] for category in item.get_variant_categories(): variants.append(item.get_variant(category, ARGS['variant_' + category])) if cmd == CMD_ADD: try: user.cart.add_item(item, int(qty), variants) save = True except ValueError: pass elif cmd == CMD_UPDATE: for i in range(0, len(user.cart.items)): try: user.cart.items[i].quantity = int(ARGS['qty_' + str(i)]) except ValueError: pass save = True elif cmd == CMD_REMOVE: try: user.cart.remove_item(int(index)) save = True except ValueError: pass if save: app.save_session() app.show_template("cart.myt", cart=user.cart) def checkout(self, m, ARGS, cmd = None, **params): """manages the checkout process, including form field handling and state transition. Delegates most of the work onto a CheckoutTransitions object which handles state transitions.""" app = _RequestHelper(m, self) smachine = _CheckoutTransitions(app) smachine.do_transition(cmd) if smachine.template is not None: app.save_session() app.show_template(smachine.template, ck_state = smachine.state, form = smachine.form, invoice = smachine.invoice) def source(self, m, r): """view source component, opens the source file up and displays. clearly, one should be careful with the configuration of this type of component lest it be too flexible in what it shows. """ filename = r.filename if os.path.isdir(filename): m.abort(403) try: f = file(filename) except IOError: m.abort(404) r.content_type = 'text/html' s = f.read() s = highlight.highlight(s, filename = filename) _RequestHelper(m, self).show_template("viewsource.myt", name = m.request_path, source = s) index = _Store() class _RequestHelper(object): """a per-request helper object that performs common functions using the request object as well as the session.""" def __init__(self, m, handler): self.m = m self.handler = handler session = m.get_session() if not session.has_key('shopping_user'): user = shoppingmodel.User() session['shopping_user'] = user session.save() def get_user(self): return self.m.get_session()['shopping_user'] def save_session(self): self.m.get_session().save() def show_template(self, template, **params): self.m.subexec(self.handler.store_path + '/' + template, **params) class _CheckoutTransitions(StateMachine): """ a StateMachine implementation storing all the possible transitions for the shopping cart. the state itself is stored in the session and is also matched against a hidden form variable. if the two don't match, no transition occurs. this approach insures that the state of the checkout is determined by the server and cannot be overridden by a fabricated HTTP request. It also renders any errors related to the "reload" button irrelevant. users can go back/next/reload till the sun comes down on any checkout page and it wont screw up their state/submit twice/etc. """ def __init__(self, app): self.app = app self.ARGS = app.m.request_args self.user = app.get_user() self.invoice = None self.session = app.m.get_session() def create_form(self): """returns a Form object representing all the fields we are going to collect. the Form is stored in the users session.""" return form.Form('checkout', [ form.FormField('ck_state', default=CHECKOUT_START), form.FormField('currentform'), form.SubForm('billing', [ form.IntFormField('useaddress', options = []), form.FormField('firstname', description = "First Name", required=True), form.FormField('lastname', description = "Last Name", required=True), form.FormField('street1', description = "Street", required=True), form.FormField('street2', description = "Street"), form.FormField('city', description = "City", required=True), form.FormField('state', description = "State", required=True), form.FormField('zipcode', description = "Zip Code", required=True), form.FormField('country', descriptinon = "Country", required=True, default='USA'), ]), form.SubForm('shipping', [ form.IntFormField('useaddress', options = []), form.FormField('firstname', description = "First Name", required=True), form.FormField('lastname', description = "Last Name", required=True), form.FormField('street1', description = "Street", required=True), form.FormField('street2', description = "Street"), form.FormField('city', description = "City", required=True), form.FormField('state', description = "State", required=True), form.FormField('zipcode', description = "Zip Code", required=True), form.FormField('country', descriptinon = "Country", required=True, default='USA'), ]), form.SubForm('payment', [ form.FormField('ccname', description="Credit Card Name", required = True, textsize=40), form.FormField('cctype', description = "Credit Card Type", required = True, options=( ('amex', 'American Express'), ('visa', 'Visa'), ('mastercard', 'Master Card'), )), form.CCFormField('ccnumber', description ="Credit Card Number", required = True, textsize=20, default = '371449635398431'), form.DateFormField('ccexp', description ="Credit Card Expiration Date", required = True, fields = ['month', 'year'], yeardeltas = range(0, 9)), ]), ]) def do_transition(self, transition): if not self.session.has_key('invoice_form') or transition is None: self.state = CHECKOUT_START self.invoice_form = self.create_form() self.session['invoice_form'] = self.invoice_form else: self.invoice_form = self.session['invoice_form'] self.state = self.invoice_form.elements['ck_state'].displayvalue self.set_current_form(self.invoice_form.elements['currentform'].displayvalue) if self.state >= CHECKOUT_CONFIRM: self.template = 'confirm.myt' self.invoice = self.user.invoice else: self.template = 'checkout.myt' if self.state != self.ARGS['ck_state'] or self.state == CHECKOUT_DONE: return if transition is None: transition = CMD_NEXT try: self.state = StateMachine.do_transition(self, self.state, transition) self.invoice_form.elements['ck_state'].set_value(self.state) except AbortTransition: pass def set_current_form(self, name): self.invoice_form.elements['currentform'].set_value(name) self.form = self.invoice_form.elements[name] def set_address_dropdowns(self): addresses = [(i, self.user.addresses[i].street1) for i in range(0, len(self.user.addresses))] if len(addresses): addresses.insert(0, ('', 'select an address')) self.invoice_form.elements['billing'].elements['useaddress'].options = addresses self.invoice_form.elements['shipping'].elements['useaddress'].options = addresses def extract_address(self): address = shoppingmodel.Address() self.form.reflect_to(address) for a in self.user.addresses: if address == a: break else: self.user.addresses.append(address) self.set_address_dropdowns() return address def fill_address(self): address = self.user.addresses[self.form.elements['useaddress'].currentvalue] self.form.elements['useaddress'].set_value(None) self.form.reflect_from(address) self.form.unvalidate() return address def start_to_billing(self): self.set_address_dropdowns() self.set_current_form('billing') self.template = 'checkout.myt' def billing_to_start(self): self.app.show_template("cart/") self.template = None def billing_to_shipping(self): self.form.set_request(self.ARGS) if self.form.elements['useaddress'].currentvalue is not None: address = self.fill_address() self.set_current_form('shipping') self.form.unvalidate() elif self.form.is_valid(): self.extract_address() self.set_current_form('shipping') self.form.unvalidate() else: raise AbortTransition() def shipping_to_billing(self): self.set_current_form('billing') self.form.unvalidate() def shipping_to_payment(self): self.form.set_request(self.ARGS) if self.form.elements['useaddress'].currentvalue is not None: address = self.fill_address() ccname = address.firstname + " " + address.lastname self.set_current_form('payment') self.form.elements['ccname'].set_value(ccname) self.form.unvalidate() elif self.form.is_valid(): address = self.extract_address() ccname = address.firstname + " " + address.lastname self.set_current_form('payment') self.form.elements['ccname'].set_value(ccname) self.form.unvalidate() else: raise AbortTransition() def payment_to_shipping(self): self.set_current_form('shipping') self.form.unvalidate() def payment_to_confirm(self): self.form.set_request(self.ARGS) if self.form.is_valid(): billing= shoppingmodel.Address() self.invoice_form.elements['billing'].reflect_to(billing) shipping = shoppingmodel.Address() self.invoice_form.elements['shipping'].reflect_to(shipping) cc = shoppingmodel.CreditCard() self.invoice_form.elements['payment'].reflect_to(cc) invoice = shoppingmodel.Invoice(self.user.cart.items, self.user, billing, shipping, cc) self.user.invoice = invoice self.invoice = invoice self.template = 'confirm.myt' else: raise AbortTransition() def confirm_to_payment(self): self.set_current_form('payment') self.template = 'checkout.myt' self.form.unvalidate() def confirm_to_done(self): self.invoice = self.user.invoice self.user.cart.items = [] self.template = 'confirm.myt' transitions = dict([ Transition(CHECKOUT_START, CHECKOUT_BILLING, CMD_NEXT, start_to_billing), Transition(CHECKOUT_BILLING, CHECKOUT_START, CMD_PREVIOUS, billing_to_start), Transition(CHECKOUT_BILLING, CHECKOUT_SHIPPING, CMD_NEXT, billing_to_shipping), Transition(CHECKOUT_SHIPPING, CHECKOUT_BILLING, CMD_PREVIOUS, shipping_to_billing), Transition(CHECKOUT_SHIPPING, CHECKOUT_PAYMENT, CMD_NEXT, shipping_to_payment), Transition(CHECKOUT_PAYMENT, CHECKOUT_SHIPPING, CMD_PREVIOUS, payment_to_shipping), Transition(CHECKOUT_PAYMENT, CHECKOUT_CONFIRM, CMD_NEXT, payment_to_confirm), Transition(CHECKOUT_CONFIRM, CHECKOUT_PAYMENT, CMD_PREVIOUS, confirm_to_payment), Transition(CHECKOUT_CONFIRM, CHECKOUT_DONE, CMD_NEXT, confirm_to_done), ]) myghty-1.1/examples/shoppingcart/lib/shoppingdata.py0000644000175000017500000000517010501064121022003 0ustar malexmalexfrom shoppingmodel import * store = Category('store', 'The Myghty Store', []) tshirts = Category("tshirts", "T Shirts", [ Item("Myghty T Shirt", "A lovely T-shirt made with genuine Microsoft Paint, featuring the Myghty logo", "tshirt_myghty.gif", None, variants = [ Variant('size', 'small', 9.99), Variant('size', 'medium', 10.99), Variant('size', 'large', 11.99), Variant('size', 'xtra large', 12.99), Variant('color', 'white'), Variant('color', 'black'), Variant('color', 'blue') ] ), Item("Python T Shirt", "A lovely T-shirt, carefully line drawn in less than five minutes, featuring the Python logo", "tshirt_python.gif", 10.99, variants = [ Variant('size', 'small'), Variant('size', 'medium'), Variant('size', 'large'), Variant('size', 'xtra large'), ] ), Item("Snake T Shirt", "Our finest shirt, features more pixels and colors than our other shirts.", "tshirt_snake.gif", 10.99, variants = [ Variant('size', 'small'), Variant('size', 'medium'), Variant('size', 'large'), Variant('size', 'xtra large'), ] ) ], store) mugs = Category("mugs", "Mugs", [ Item("Myghty Mug", "Get your caffeine on with this mediocre line drawing", "mug_myghty.gif", 5.99), Item("Python Mug", "Get your caffeine on with this mediocre line drawing", "mug_python.gif", 5.99, [ Variant('color', 'white'), Variant('color', 'black'), Variant('color', 'blue') ] ), Item("Snake Mug", "Get your caffeine on with this mediocre line drawing", "mug_snake.gif", 5.99), ], store) hats = Category("hats", "Hats", [ Item("Myghty Hat", "This hat I could barely even draw, but you'll look cool.", "hat_myghty.gif", 8.99, [ Variant('size', 'small'), Variant('size', 'medium'), Variant('size', 'large'), ]), Item("Python Hat", "This hat I could barely even draw, but you'll look cool.", "hat_python.gif", 6.75), Item("Snake Hat", "This hat I could barely even draw, but you'll look cool.", "hat_snake.gif", 6.75), ], store) featureditems = Category("featureditems", "Featured Items", [ hats.items[2], mugs.items[1], tshirts.items[0] ], None) myghty-1.1/examples/shoppingcart/lib/shoppingmodel.py0000644000175000017500000001504510501064121022174 0ustar malexmalex__all__ = ['Item', 'Variant', 'Category', 'User', 'Cart', 'Address', 'Invoice'] import re from myghty.util import * class ModelSupertype(object):pass class Item(ModelSupertype): def __init__(self, name, description, image, price, variants = None): self.name = name self.description = description self.image = image self.price = price self.variants = {} if variants is not None: for v in variants: list = self.variants.setdefault(v.categoryname, OrderedDict()) list[v.name] = v def get_variant_categories(self): return self.variants.keys() def get_variant(self, categoryname, name): try: return self.variants[categoryname][name] except KeyError: return None def __eq__(self, other): return other.name == self.name def __ne__(self, other): return not self.__eq__(other) class Variant(ModelSupertype): def __init__(self, categoryname, name, price = None): self.categoryname = categoryname self.name = name self.price = price def __eq__(self, other): return ( self.categoryname == other.categoryname and self.name == other.name ) def __ne__(self, other): return not self.__eq__(other) class Category(ModelSupertype): def __init__(self, name, description, items, parent = None): self.name = name self.description = description self.items = items self.subcategories = OrderedDict() self.parent = parent self.path = '/%s' % self.name if self.parent is not None: self.path = parent.path + self.path self.parent.subcategories[self.name] = self self.root = self.parent.root else: self.allitems = {} self.root = self for item in items: if not hasattr(item, 'primarycategory'): item.primarycategory = self self.root.allitems[item.name] = item def get_item(self, name): return self.root.allitems[name] def get_path(self): return self.path def get_category(self, path): match = re.match(r'/([^/]*)(/.*)?', path) (token, remainder) = match.group(1, 2) if not token: return self c = self.subcategories[token] if not remainder: return c else: return c.get_category(remainder) def get_categories(self): return iter(self.subcategories) def __str__(self): return self.name class User(ModelSupertype): def __init__(self, username = None, email = None): self.username = username self.email = email self.addresses = [] self.cart = None self.invoice = None class Cart(ModelSupertype): def __init__(self): self.items = [] def add_item(self, item, quantity, variants = []): for i in self.items: if i.equals(item, variants): i.quantity += quantity return self.items.append(CartItem(item, quantity, variants)) def remove_item(self, index): self.items[index:index + 1] = [] def get_total(self): sum = 0 for i in self.items: sum += i.get_total_price() return sum def is_empty(self): return len(self.items) == 0 class CartItem(ModelSupertype): def __init__(self, item, quantity, variants): self.item = item self.variants = dict([(v.categoryname, v) for v in variants]) self.quantity = quantity self.price = item.price for v in self.variants.values(): if v.price is not None: self.price = v.price def get_variant_categories(self): return self.variants.keys() def get_variant(self, categoryname): try: return self.variants[categoryname] except KeyError: return None def get_item_price(self): return self.price def get_total_price(self): return self.quantity * self.price def equals(self, item, variants): if self.item != item: return False for variant in variants: try: if self.variants[variant.categoryname] != variant: return False except KeyError: return False return True def __eq__(self, other): return self.equals(other.item, other.variants.values()) def __ne__(self, other): return not self.__eq__(other) class Address(ModelSupertype): def __init__(self, firstname = None, lastname = None, street1 = None, street2 = None, city = None, state = None, zipcode = None, country = None): self.street1 = street1 self.street2 = street2 self.firstname = firstname self.lastname = lastname self.city = city self.state = state self.zipcode = zipcode self.country = country def __eq__(self, other): for key in ('firstname', 'lastname', 'street1', 'street2', 'city', 'state', 'zipcode', 'country'): if (not hasattr(other, key) or getattr(other, key) != getattr(self, key)): return False return True def __ne__(self, other): return not self.__eq__(other) class CreditCard(ModelSupertype): def __init__(self, ccname = None, cctype = None, ccnumber = None, ccexp = None): self.ccname = ccname self.cctype = cctype self.ccnumber = ccnumber self.ccexp = ccexp def _hiddencc(self): return ((len(self.ccnumber) - 5) * 'X') + self.ccnumber[-4:len(self.ccnumber)] hiddencc = property(_hiddencc) class Invoice(ModelSupertype): def __init__(self, items, user, billingaddress = None, shippingaddress = None, creditcard = None): self.items = items self.user = user self.billingaddress = billingaddress self.shippingaddress = shippingaddress self.creditcard = creditcard def get_total(self): sum = 0 for i in self.items: sum += i.get_total_price() return sum myghty-1.1/examples/shoppingcart/lib/statemachine.py0000644000175000017500000000137510501064121021772 0ustar malexmalex__all__ = ['StateMachine', 'StateTransition', 'AbortTransition', 'Transition'] class StateMachine: def do_transition(self, state, transition): trans = self.__class__.transitions[state + "_" + transition] trans.function(self) return trans.tostate class StateTransition: def __init__(self, fromstate, tostate, transition, function): self.fromstate = fromstate self.tostate = tostate self.transition = transition self.function = function self.key = fromstate + "_" + transition class AbortTransition(Exception): pass def Transition(fromstate, tostate, transition, function): trans = StateTransition(fromstate, tostate, transition, function) return (trans.key, trans) myghty-1.1/examples/shoppingcart/run_cart.py0000644000175000017500000000357510501064121020400 0ustar malexmalex"""shopping cart runner.""" import os, sys, re [sys.path.insert(0, path) for path in ['../../lib', '../../doc/lib', './lib']] import myghty.http.HTTPServerHandler as HTTPServerHandler # determine port number try: port = int(sys.argv[1]) except: port = 8000 # create local cache directory to store generated files + sessions if not os.access('./cache', os.F_OK): os.mkdir('./cache') # now set up standalone server httpd = HTTPServerHandler.HTTPServer( # port num port = port, handlers = [ # serve all URIs that do not start with /docs/ with an HTTPHandler. {r'(?!/docs/)' : HTTPServerHandler.HSHandler( data_dir = './cache', # resolve URIs in the 'shoppingcontroller' module first. # here, we do it directly by path. But it can also be # broken down into regular expressions for each component # via the 'module_components' parameter. module_root = ['shoppingcontroller'], # else, resolve URIs to one of three component roots. component_root = [ {'store_comp':'./components'}, {'store_templ':'./templates'}, {'store_htdocs':'./htdocs'}, ], # interpreter attributes, custom config settings used by the # shopping cart controller. attributes = { 'store_uri' : '/', 'store_document_uri' : '/docs/', 'store_path' : '/', }, ) }, # docroots, used to determine r.filename, and # also will be served directly if Interpreter rules did not match. {r'/source/(.*)' : './'}, {r'/docs/(.*)' : './htdocs'}, ] ) print "HTTPServer listening on port %d" % httpd.port httpd.serve_forever() myghty-1.1/examples/shoppingcart/templates/0000755000175000017500000000000010501065764020213 5ustar malexmalexmyghty-1.1/examples/shoppingcart/templates/cart.myt0000644000175000017500000000345510501064121021670 0ustar malexmalex<%args> cart <%global> import shoppingcontroller % if cart.is_empty():
There are no items in your shopping cart.
Keep shopping! % return % <&| forms.myc:form, action="cart/", name="cartform" &>
Your Shopping Cart
% index = 0 % for cartitem in cart.items: % index +=1 %
Quantity Item Style Price Per Item Total Price
<& forms.myc:text, value=cartitem.quantity, size=2, name="qty_" + str(index) &> <& common.myc:item_link, item = cartitem.item &> % for variant in cartitem.variants.values(): <% variant.categoryname %>: <% variant.name %> % <& common.myc:price, price = cartitem.get_item_price() &> <& common.myc:price, price = cartitem.get_total_price() &> (remove)
<& forms.myc:button, value="update", cssclass="smallbutton", onclick="cartform.submit()" &>
Total: <& common.myc:price, price = cart.get_total() &>
<<< Continue Shopping Go To Checkout >>>
myghty-1.1/examples/shoppingcart/templates/catalog.myt0000644000175000017500000000137510501064121022350 0ustar malexmalex <%args> category items columns = 2 <%python scope="init"> # cache the contents of the component, keyed off of category path. # component will be executed at most every 120 seconds for a given # category. if m.cache_self(key=category.get_path(), cache_expiretime=120): return <& breadcrumb.myc, category = category &> % index = 0 % while index < len(items): % for i in range(0, columns): % if index >= len(items): break % else: item = items[index] % index +=1 % %
<& common.myc:item_image, item=item &>
<& common.myc:item_link, item=item &>
<% item.price %>
myghty-1.1/examples/shoppingcart/templates/checkout.myt0000644000175000017500000000437010501064121022541 0ustar malexmalex<%python scope="global"> import shoppingmodel, shoppingcontroller <%args> form ck_state Checkout <& breadcrumb, form = form, ck_state=ck_state &> <&| forms.myc:form, name="checkout", action="checkout/" &> % if ck_state==shoppingcontroller.CHECKOUT_BILLING or ck_state==shoppingcontroller.CHECKOUT_SHIPPING: <& existingaddress, form = form &>
<& address.myc, form=form &> <& go &>
% elif ck_state == shoppingcontroller.CHECKOUT_PAYMENT:
<& ccard.myc, form=form &> <& go &>
% else: whoops, todo ! state: <% ck_state %> <& go &> % <%def existingaddress> <%args>form % if len(form.elements['useaddress'].options):
Use existing address: <& forms.myc:select, field=form.elements['useaddress'] &>
<& go &>
% <%method breadcrumb> <%args> form = None ck_state <%method stepname trim="both"> <%args> state ck_state form = None % if ck_state > state: % elif ck_state == state: % if form is not None and form.is_valid() is False: % else: % % else: % <% m.content() %> <%method go>
<& forms.myc:submit, value="<<< Previous", onclick="document.checkout.cmd.value='%s'" % shoppingcontroller.CMD_PREVIOUS &> <& forms.myc:submit, value="Next >>>" &>
myghty-1.1/examples/shoppingcart/templates/confirm.myt0000644000175000017500000000534510501064121022374 0ustar malexmalex<%python scope="global"> import shoppingmodel, shoppingcontroller <%args> invoice ck_state <& checkout.myt:breadcrumb, ck_state = ck_state &> % if ck_state == shoppingcontroller.CHECKOUT_DONE: Order Complete - Thanks for your order ! % <&| forms.myc:form, name="checkout", action="checkout/" &>
Invoice <& staticitems, invoice = invoice &>
Billing Address <& staticaddress, address = invoice.billingaddress &>
Shipping Address <& staticaddress, address = invoice.shippingaddress &>
Payment Info <& staticcc, cc = invoice.creditcard &>
% if ck_state == shoppingcontroller.CHECKOUT_CONFIRM: <& checkout.myt:go &> % <%def staticaddress> <%args>address
Name:<% address.firstname %> <%address.lastname %>
Street:<% address.street1 %>
<% address.street2 %>
City:<% address.city %>
State:<% address.state %>
Zip:<% address.zipcode %>
Country:<% address.country %>
<%def staticcc> <%args>cc
Name:<% cc.ccname %>
Number:<% cc.hiddencc %>
Type:<% cc.cctype %>
Exp:<% cc.ccexp%>
<%def staticitems> <%args> invoice % for item in invoice.items: %
Item Style Price Quantity Total
<% item.item.name %> % for variant in item.variants.values(): <% variant.categoryname %>: <% variant.name %> % <& common.myc:price, price = item.get_item_price() &> <% item.quantity %> <& common.myc:price, price = item.get_total_price() &>
Total: <& common.myc:price, price = invoice.get_total() &>
myghty-1.1/examples/shoppingcart/templates/item.myt0000644000175000017500000000306110501064121021666 0ustar malexmalex<%args> item category <%python scope="init"> import shoppingcontroller # cache the contents of the component, keyed off of item name. # component will be executed at most every 120 seconds for a given # item. if m.cache_self(key=item.name, cache_expiretime=120): return <& breadcrumb.myc, category = category &>
<&| forms.myc:form, action='cart/' &>
<& common.myc:item_image, item=item &>
<% item.name %>
<% item.price %>
% if item.variants is not None: % for key, list in item.variants.iteritems(): %
<% key %>: # the call to create the select box of item variants # is a little involved, which led me to cache this component <& forms.myc:select, name='variant_' + key, options=[(v.name, "%s%s" % (v.name, m.scomp('common.myc:price', price = v.price))) for v in list.values()] &>
%

Add to Cart: <& forms.myc:text, size=2, name="qty", value="1" &> item(s) <& forms.myc:submit, value="go" &>
Back to category '<% category.description %>'
myghty-1.1/examples/shoppingcart/templates/viewsource.myt0000644000175000017500000000036110501064121023123 0ustar malexmalex<%args> name source Source of <% name %>
<% source %>
myghty-1.1/examples/template/0000755000175000017500000000000010501065764015327 5ustar malexmalexmyghty-1.1/examples/template/another.myt0000644000175000017500000000103510501064123017505 0ustar malexmalex<%flags> # specify a layout file to inherit from inherit = 'layout2.myt' <&|SELF:section, name='title' &> Myghty Advanced Layout Demo - Page 3 <&|SELF:section, name='header' &>

Abstracted Layout Demo - Layout 2

<&|SELF:section, name='body' &>

This is another page using a different layout.

<&|SELF:section, name='leftnav' &> this is the left nav

Back to Layout 1 <&|SELF:section, name='footer' &> <& toolbar.myt &> myghty-1.1/examples/template/autohandler0000644000175000017500000000124210501064123017543 0ustar malexmalex<%doc> autohandler - allows dynamic layout handlers <%flags> # specify that we are the base inheriting page inherit= None <%python scope="request"> # each execution gets a context dictionary where # we store stuff context = {} <%method section> <%doc> a method that will be called by the subclass, and will grab content for the specified area <%args> # name of the section to store name <%init> context[name] = m.content() <%python> comp = m.fetch_next() subcomp = m.fetch_next() main = m.scomp(subcomp, **m.get_request_args()) m.comp(comp, **context) myghty-1.1/examples/template/index.myt0000644000175000017500000000244410501064123017161 0ustar malexmalex<%flags> # specify a layout file to inherit from inherit = 'layout.myt' <&|SELF:section, name='title' &> Myghty Really Advanced Layout Demo <&|SELF:section, name='header' &>

Abstracted Layout Demo - Main Layout

<&|SELF:section, name='body' &>

In this example, the autohandler calls upon m to fetch both its immediate subclass and the subclass of that subclass. The "sub-sub-class", which represents the ultimate file being served, is called to populate the content dictionary first. Then the "subclass", which defines the layout, is called with the newly populated content dictionary as its arguments, which it references via the %ARGS section.

This allows pages to select their own layout component, and also makes the creation of new layouts easy as they contain virtually no python code except straight variable substitutions. With layout and content in separate templates, brought together by the autohandler, the design is extremely close to the "model-view-controller" paradigm.

View a page using Layout 2

<&|SELF:section, name='leftnav' &> Left Nav

View Layout 2

<&|SELF:section, name='footer' &> <& toolbar.myt &> myghty-1.1/examples/template/layout.myt0000644000175000017500000000064510501064123017370 0ustar malexmalex<%doc> layout.myt, regular layout <%args> title header leftnav body footer <% title %>
<% header %>
<% leftnav %>
<% body %>
myghty-1.1/examples/template/layout2.myt0000644000175000017500000000056210501064123017450 0ustar malexmalex<%args> title header leftnav body footer <% title %>
<% header %>
<% leftnav %>
<% body %> <% footer %>
myghty-1.1/examples/template/README0000644000175000017500000000136310501064123016176 0ustar malexmalexIn this example, the autohandler calls upon "m" to fetch both its immediate subclass and the subclass of that subclass. The "sub-sub-class", which represents the ultimate file being served, is called to populate the content dictionary first. Then the "subclass", which defines the layout, is called with the newly populated content dictionary as its arguments, which it references via the %ARGS section. This allows pages to select their own layout component, and also makes the creation of new layouts easy as they contain virtually no python code except straight variable substitutions. With layout and content in separate templates, brought together by the autohandler, the design is extremely close to the "model-view-controller" paradigm. myghty-1.1/examples/template/style.css0000644000175000017500000000110510501064123017162 0ustar malexmalex/* allow this stylesheet to compile as a myghty template, if convenient <%flags>inherit=None */ body { font-family: verdana, helvetica, sans-serif; } .header { border: 1px solid; padding: 10px; margin: 5px; text-align: center; } .content-body { margin: 30px 20px 20px 25px; } .left-column { float:left; margin: 5px; padding: 20px; width: 120px; height: 50%; font-size:10px; background-color: #EEEEEE; } .footer { clear: left; border: 1px solid; padding: 20px; margin: 5px; font-size:12px; background-color: #BFBFBF; text-align: center; } myghty-1.1/examples/template/toolbar.myt0000644000175000017500000000051110501064123017505 0ustar malexmalex<%doc>a hypothetical toolbar <%global> import os Demo Server Home   |   Documentation   |   www.myghty.org   |   View Source of This Section myghty-1.1/examples/zblog/0000755000175000017500000000000010501065764014631 5ustar malexmalexmyghty-1.1/examples/zblog/bin/0000755000175000017500000000000010501065764015401 5ustar malexmalexmyghty-1.1/examples/zblog/bin/server.py0000644000175000017500000000136110501064125017250 0ustar malexmalex#!/usr/local/bin/python """myghty HTTPServerHandler runner.""" import myghty.http.HTTPServerHandler as HTTPServerHandler import sys, os, re from myghty.resolver import * root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../')) sys.path.insert(0, os.path.join(root, 'lib')) interpreter_config = { 'root' : root } execfile(os.path.join(root, 'config/server_config.py'), globals(), interpreter_config) interp = HTTPServerHandler.HSHandler(**interpreter_config) port = 8080 httpd = HTTPServerHandler.HTTPServer( port = port, handlers = [ {r'.*(?:/|\.myt)$' : interp}, ], docroot = [{'.*' : os.path.join(root, 'htdocs')}], ) print "Listening on port %d" % port httpd.serve_forever() myghty-1.1/examples/zblog/components/0000755000175000017500000000000010501065764017016 5ustar malexmalexmyghty-1.1/examples/zblog/components/components.myc0000644000175000017500000001121510501064125021703 0ustar malexmalex<%global> import string, re <%method user> <%init> return m.get_session().get('user', None) <%method securehref> <%args> href action <%init> user = m.get_session().get('user', None) % if action.access(user, **ARGS): <% m.content() %> % else: <% m.content() %> % <%method entryform> <%args> name=None action=None onsubmit=None ajaxtarget=None form=None <%init> if form is not None: name = form.name m.attributes[(self.owner.id, 'form')] = form if ajaxtarget is not None: onsubmit = m.comp("SELF:ajaxaction", target=ajaxtarget, form=form, name=name) + ";return false;"
<&|SELF:fields&> <% m.content() %>
<%cleanup> try: del m.attributes[(self.owner.id, 'form')] except KeyError: pass <%method ajaxaction> <%args> target form=None name=None <%init> if form is not None: if name is None: name = form.name formargs = m.scomp('SELF:formargs', form=form) else: formargs = '' return target + "(%s)" % formargs <%method formargs trim="both"> <%args> form name=None getargs(document.<% name or form.name %>, <% string.join(["'%s'" % f.displayname for f in form], ',') %>) <%method fields> <% m.content() %>
<%method field> <%args> type name=None field=None <%init> label = m.content() if field is None: form = m.attributes.get((self.owner.id, 'form'), None) if form is not None and name: field = form.get(name, None) if field is not None: ARGS['name'] = field.displayname ARGS.setdefault('value', field.display) ARGS['data'] = field.data ARGS['field'] = field else: if name is not None: ARGS.setdefault('value', m.root_request_args.get(name, None)) component = m.fetch_component("SELF:%s" % type) % if component.attributes.get('hidden', False) or not label: <% m.comp(component, **ARGS) %> % else: <% label %> <%python>m.comp(component, **ARGS) % if field is not None: <& /form:fieldstatus, field=field &> % % <%method row> <%args> colspan=None > <% m.content() %> <%method text> <%args> size=20 name value <%method password> <%args> size=20 name value <%method textarea> <%args> rows=5 cols=50 name value <%method hidden> <%args> name=None value <%attr> hidden=True <%method dropdown> <%args> name data=() onselect=None value=None <%method submit> <%args> value <%method button> <%args> value onclick > <%method popup> <%args> name
<% m.content() %>
<%method confirm> <%args> yes no
<% m.content() %>
<&/components:field, type="button", value="Yes", onclick=yes &> <&/components:field, type="button", value="No", onclick=no &>
myghty-1.1/examples/zblog/components/data.myc0000644000175000017500000000252110501064124020426 0ustar malexmalex<%doc> defines a set of "data listing" methods, which provide a template-oriented interface to various kinds of list-based information. <%global> from zblog.domain.blog import * <%method bloglist> <%args> loop=True user=None <%init> if user is not None: blogs = Blog.mapper.select_by(owner_id=user.id) else: blogs = Blog.mapper.select() if not m.has_content(): return blogs % if loop: % for b in blogs: <% m.content(blog=b) %> % % else: <% m.content(blogs=blogs) %> % <%method blogurl trim="both"> <%args> blog /blog/<% repr(blog.id) %>/ <%method blogposts> <%args> loop=True blog keyword=False <%init> posts = Post.mapper.select_by(blog_id=blog.id, keyword=keyword) if not m.has_content(): return posts % if loop: % for p in posts: <% m.content(post=p) %> % % else: <% m.content(posts=posts) %> % <%method postcomments> <%args> loop=True post <%init> comments = Comment.find_by_post(post) if not m.has_content(): return comments % if loop: % for c in comments: <% m.content(comment=c) %> % % else: <% m.content(comments=comments) %> % myghty-1.1/examples/zblog/components/form.myc0000644000175000017500000000162210501064125020462 0ustar malexmalex<%doc> method: fieldstatus purpose: displays a blue or red asterisk, given a form.FormField object <%method fieldstatus trim="both"> <%args> field = None <%init>if field is None:return % if field.is_valid() is False: * % elif field.required: * % <%doc> method: formstatus purpose: displays a list of invalid fields, given a form.Form object <%method formstatus> <%args> form <%init>if form is None: return
% for success in form.success: <% success %>
% % if form.is_valid(): % return % % for error in form.errors: <% error %>
% % for element in form: % if element.is_valid() is False: % for error in element.errors: <% error %>
% % %
myghty-1.1/examples/zblog/components/toolbar.myc0000644000175000017500000000225310501064125021162 0ustar malexmalex<%init> s = m.get_session() user = s.get('user', None) quotes = [ "Welcome User #1!", 'We Put the "P" in "Plain"', 'The Best Completely Unfinished Blog App Out There', 'Voted #1 - On Your Desktop', 'Its a Great Idea - While it Lasts', 'Over One Million Downloads - In Backwards Negative UpsideDown Land !', "Has No Graphics - *And* Doesn't Work in Lynx !", "Oh We've Got Monkeys. And They're Typin'....Just You Wait.", ] import random quote = quotes[random.randint(0, len(quotes)-1)]
Zblog - "<% quote %>"
% if user is not None:
you are logged in as <% user.name %>
% Home <&|/components:securehref, href="/blog/my/", action=actions.LoggedIn() &>My Blogs <&|/components:securehref, href="/manage/", action=actions.Manage() &>Manage % if user is not None: Logout % else: <&|/components:securehref, href="/login/", action=actions.Login() &>Login <&|/components:securehref, href="/login/register/", action=actions.Register() &>Register %
myghty-1.1/examples/zblog/config/0000755000175000017500000000000010501065764016076 5ustar malexmalexmyghty-1.1/examples/zblog/config/server_config.py0000644000175000017500000000351310501064125021273 0ustar malexmalex """Myghty server configuration. should be neutral to HTTP environment, i.e. WSGI, standalone, etc. This file should be executed in a context that includes "root" as the path to the application root.""" data_dir=os.path.join(root, 'cache') resolver_strategy = [ # request-level resolution: everything goes to the front controller ConditionalGroup( context="request", rules=[ ResolveModule({r'(.*)' : 'zblog.controller.front:index'}), NotFound(), ] ), # front-controller resolution: front controller forwards requests into this realm # after performing security checks ConditionalGroup( context="frontcontroller", rules=[ ResolvePathModule(os.path.join(root, 'lib/zblog/controller'), path_stringtokens=['index'], path_moduletokens=['index']), NotFound() ] ), # component/template resolution, for subrequests, component calls, inheritance # autohandlers ResolveUpwards(), # for the 'components' directory, we append '.myc' to incoming filenames so templates dont have to # specify ResolveFile({'components':os.path.join(root, 'components')}, adjust=lambda u: re.sub(r'$', '.myc', u)), # page-level resolution; we convert directory names to index.myt PathTranslate((r'/$', '/index.myt')), ResolveFile({'htdocs':os.path.join(root, 'htdocs')}), ] #debug_elements=['resolution'] attributes = { 'config_file' : os.path.join(root, 'config/config.py'), } def preproc(source): """source pre-processor. adds an import to the top of all components.""" return """ <%global> import zblog.domain.actions as actions """ + source python_pre_processor = preproc # startup stuff. import zblog import zblog.database zblog.load_config(attributes['config_file']) myghty-1.1/examples/zblog/htdocs/0000755000175000017500000000000010501065764016115 5ustar malexmalexmyghty-1.1/examples/zblog/htdocs/admin/0000755000175000017500000000000010501065764017205 5ustar malexmalexmyghty-1.1/examples/zblog/htdocs/admin/blog.myt0000644000175000017500000000470510501064124020656 0ustar malexmalex <%method ajax> <%init> return m.comp('/ajax/myghtyjax.myt:init', editblog = { 'handler_uri':'/manage/blog/ajax_editblog/', 'exectype':'writedom', 'dom_id':'opwindow' }, bloglist = { 'handler_uri':'/manage/blog/bloglist/', 'exectype':'writedom', 'dom_id':'opwindow' }, action_editblog = { 'handler_uri':'/manage/blog/edit_blog/', 'exectype':'writedom', 'dom_id':'opwindow' }, action_deleteblog = { 'handler_uri':'/manage/blog/delete_blog/', 'exectype':'writedom', 'dom_id':'opwindow' }, ) <%method links> % for link in [('javascript:editblog()', 'Create Blog'), ('javascript:bloglist()', 'Edit Blog')]: <% m.content(url=link[0], text=link[1]) %> % <%method blogedit> <%args> form <& /form:formstatus, form=form &> <&|/components:entryform, ajaxtarget="action_editblog", form=form, columns=2&> <&/components:field, type="hidden", name='blog_id'&> <&|/components:field, type="text", name='name'&> Blog Name <&|/components:field, type="text", name='description'&> Description <&|/components:field, type="text", name='owner_name'&> Owner <&|/components:row, colspan="2"&> % if form['blog_id'].display: <&/components:field, type="submit", value="Update Blog"&> <&/components:field, type="button", value="Delete Blog", onclick=m.comp('/components:ajaxaction', target="action_deleteblog", form=form)&> % else: <&/components:field, type="submit", value="Create Blog" &> % <%method bloglist> <&|/components:entryform, name="lookupform", onsubmit="editblog({'blog_id':document.lookupform.blog.value});return false", columns=2&> Select a blog: <&/components:dropdown, name="blog", data=[(blog.id, blog.name) for blog in m.comp('/data:bloglist')] &> <&/components:submit, value="Go"&> <%method delete_confirm> <%args> blog <&|/components:confirm, yes="action_deleteblog({'blog_id':'%s','confirm':1})" % blog.id, no="editblog({'blog_id':'%s'})" % blog.id&> Are you sure you want to delete blog '<% blog.name %>'? This will delete all posts and comments within this blog. myghty-1.1/examples/zblog/htdocs/admin/index.myt0000644000175000017500000000065310501064124021040 0ustar malexmalex<%init> if m.comp('blog.myt:ajax') or m.comp('user.myt:ajax'): return <& /ajax/myghtyjax.myt:js &>

ZBlog Management

myghty-1.1/examples/zblog/htdocs/admin/user.myt0000644000175000017500000000476010501064124020712 0ustar malexmalex<%method ajax> <%init> return m.comp('/ajax/myghtyjax.myt:init', useradmin={ 'handler_uri':'/manage/user/ajax_edituser/', 'exectype':'writedom', 'dom_id':'opwindow' }, action_edituser={ 'handler_uri':'/manage/user/edit_user/', 'exectype':'writedom', 'dom_id':'opwindow' }, action_deluser={ 'handler_uri':'/manage/user/delete_user/', 'exectype':'writedom', 'dom_id':'opwindow' } ) <%method links> % for link in [('javascript:useradmin()', 'User Administration')]: <% m.content(url=link[0], text=link[1]) %> % <%method userform> <%args> form <& /form:formstatus, form=form &> <&|/components:entryform, name="lookupform", onsubmit="useradmin({'username':document.lookupform.username.value});return false;", columns=2&> Lookup User: <&/components:field, type="text", name="username"&> <&/components:field, type="submit", value="Find"&> <&|/components:entryform, ajaxtarget="action_edituser", form=form, columns=2&> <&/components:field, type="hidden", name='user_id'&> <&|/components:field, type="text", name='name' &> User Name <&|/components:field, type="text", name='fullname'&> Full Name <&|/components:field, type="dropdown", name='group'&> Group <&|/components:field, type="password", name='password_set'&> Password <&|/components:field, type="password", name='password_repeat'&> Repeat Password <&|/components:row, colspan="2" &> % if form['user_id'].value: <&/components:field, type="submit", value="Update User"&> <&/components:field, type="button", value="Delete User", onclick=m.comp('/components:ajaxaction', target="action_deluser", form=form) &> % else: <&/components:field, type="submit", value="Create User"&> % <%method delete_confirm> <%args> user <&|/components:confirm, yes="action_deluser({'user_id':'%s','confirm':1})" % user.id, no="useradmin({'username':'%s'})" % user.name&> Are you sure you want to delete user '<% user.fullname %>'? This also deletes all blogs and posts by that user. myghty-1.1/examples/zblog/htdocs/ajax/0000755000175000017500000000000010501065764017040 5ustar malexmalexmyghty-1.1/examples/zblog/htdocs/ajax/myghtyjax.js0000644000175000017500000000407410501064124021414 0ustar malexmalex function error(message) { var console = window.open("",'console',"width="+400+",height="+400+",status=yes,dependent=yes,resizable=yes,scrollbars=yes"); console.document.write(message); console.document.close(); } /* receives javascript from the given url + args and evaluates it */ function runRemoteJS(url, args) { doCall(url, function (x) {eval(x);}, args); } function doCall(url, wrapper, args) { var req = openConnection(); if (url.indexOf('?') == -1) { url = url + "?"; } for (var key in args) { if (key=='_mjax_named') { for (var k in args[key]) { url = url + '&' + escape(k) + "=" + escape(args[key][k] || ''); } } else { url = url + '&' + escape(key) + "=" + escape(args[key] || ''); } } url = url + "&_rnd=" + new Date().getTime() req.open("GET", url); req.onreadystatechange = function () { if (req.readyState != 4) { return; } //var s = getResponse(); var s = req.responseText; if (s) { try { wrapper(s); } catch (e) { error(e + "

\n\nArgument:\n
" + s); } } } req.send(null); delete req; } function openConnection () { var conn = null; try { conn = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { conn = new ActiveXObject("Microsoft.XMLHTTP"); } catch (oc) { } } if(!conn && typeof XMLHttpRequest != "undefined") { conn = new XMLHttpRequest(); } if (!conn) { error("XMLHttp connection object not defined on this browser"); } return conn; } /* given the id of a document element, a url, and argument hash, populates the given document element with the results of the url + args */ function populateDOM(id, url, args) { doCall(url, function (x) { var elem = document.getElementById(id); if (elem) { elem.innerHTML = x; } else { error("No such element '" + id + "'"); } }, args); } function writeDOM(id, string) { var elem = document.getElementById(id); if (elem) { elem.innerHTML = string; } } myghty-1.1/examples/zblog/htdocs/ajax/myghtyjax.myt0000644000175000017500000002521010501064124021604 0ustar malexmalex<%flags> # myghtyjax.myt needs to inherit from either None, or an autohandler # that doesnt have any textual output. inherit=None trim="both" <%doc> MyghtyJax - a Myghty AJAX Adapter The "init" method is called to register the names of javascript functions that will map to Myghty components and/or python function definitions. supply key=value arguments, where keys are the names of javascript functions to be generated and the value is one of: a function object, the string address of a Myghty component (path or method name), or a Myghty component object. This method should be called in the %init section of the calling component, usually before anything else has executed. If it returns true it means the XMLHttpRequest action is taking effect, and the calling component should return immediately. If false is returned, the component should continue on normally. Note that the "js" method below must be called within the HTML body of a page to actually write out the javascript functions defined by the init method. components that are called can specify options for how they should be handled via their <%attr> sections. the available options are: type='source' take the HTML source of the component and send it to a supplied javascript callback function. type='writedom' take the HTML source of the component and send it to the DOM element with the id given by either the <%attr> 'dom_id', or the first argument to the javascript function. type='exec' take the output of the component and evaluate it directly as javascript. Example: if m.comp('myghtyjax.myt:init', mypage = 'SELF:mymethod' ): return The above example will create a javascript function 'mypage()', which results in an Ajax call back to the myghtyjax.myt page which will route the request to the <%method mymethod> inside the current page. The results of the method will be processed per the "type" attribute on the method. A second form of init argument allows the values normally inside <%attr> to be specified directly to init: if m.comp('myghtyjax.myt:init', mypage = { 'component' : 'SELF:mymethod', 'exectype' : 'writedom', 'dom_id' : 'leftnav' } ): return The above example will create a javascript function 'mypage()', which results in an Ajax call back to the myghtyjax.myt page which will route the request to the <%method mymethod> inside the current page. The resulting output from the method will be written to the 'leftnav' DOM element on the page. There is also a way to circumvent the usual URL of the myghtyjax controller directly to any URL: if m.comp('myghtyjax.myt:init', mypage = { 'handler_uri' : '/my_ajax_page/', 'exectype' : 'writedom', 'dom_id' : 'leftnav' } ): return The above example will create a javascript function 'mypage()', which results in an Ajax call directly to the uri '/my_ajax_page/', and the resulting HTML will be written into the DOM element 'leftnav'. <%method init> <%init> frame = m.execution_stack.pop() try: ret = init(m, **ARGS) finally: m.execution_stack.append(frame) return ret <%doc> js is called after init has been called, and delivers the javascript "stub" functions that connect page javascript to server side calls via XMLHttpRequest. the scripturi and handleruri arguments are optional arguments that reference the web-accessible URIs of the myghtyjax.js and myghtyjax.myt files respectively. If these parameters are not supplied, they will be searched for in the interpreter attributes "myghtyjax.handleruri" and "myghtyjax.scripturi". if not there either, they will be determined based on the path of the original calling component. <%method js> <%args> scripturi = None handleruri = None <%init> js(m, handleruri = handleruri, scripturi = scripturi) <%args> _mjax_def _mjax_component <%init> m.comp(_mjax_component, **ARGS) <%once> import inspect, string, types, posixpath import myghty.request as request # initialization method, receives javascript function names mapped # to python function pointers, component names, or component objects. # referenced by the "init" myghty method. def init(m, **params): run_func = m.request_args.get('_mjax_def', None) if (run_func is not None): object = params[run_func] callable = _get_callable(m, run_func, object) callable.run(m) return True else: defs = m.attributes.setdefault('__mjax_defs', {}) for (jsname, object) in params.iteritems(): defs[jsname] = _get_callable(m, jsname, object) return False def _get_callable(m, name, object): if isinstance(object, types.FunctionType): return DefCallable(name, object) elif isinstance(object, dict): return ComponentCallable(m, name, **object) else: return ComponentCallable(m, name, object) def js(m, handleruri = None, scripturi = None): try: defs = m.attributes['__mjax_defs'] except KeyError: raise "myghtyjax.js() method requires components and defs to be init()ialized first" path = m.current_component.path if handleruri is None: handleruri = m.interpreter.attributes.get('myghtyjax.handleruri', path) if scripturi is None: scripturi = m.interpreter.attributes.get('myghtyjax.scripturi', None) if scripturi is None: (dir, file) = posixpath.split(path) scripturi = posixpath.join(dir, "myghtyjax.js") m.write("\n" % scripturi) m.write("\n") class JaxComponent: def init_callerpath(self, m): if getattr(self, 'callerpath',None) is None: self.callerpath = m.caller.path def get_function_args(self, leading_comma = False): args = string.join(self.argnames + ['_mjax_named'], ',') if leading_comma and args: return ', ' + args else: return args def get_handler_uri(self, handler_uri): return handler_uri def get_remote_args(self, m): return string.join( [ "'_mjax_def' : '%s'" % self.jsname, "'_mjax_component' : '%s'" % self.callerpath, "'_mjax_named':_mjax_named" ] + ["'%s' : %s" % (name, name) for name in self.argnames], ",\n", ) class ComponentCallable(JaxComponent): def __init__(self, m, jsname, component=None, exectype=None, argnames=None, dom_id=None, handler_uri=None): self.jsname = jsname self.component = component self.handler_uri=handler_uri if type(component) == types.StringType: componentobj = m.fetch_component(component) else: componentobj = component if exectype is None: if componentobj is not None: self.exectype = componentobj.attributes.get('type', 'exec') else: self.exectype = 'exec' else: self.exectype = exectype if dom_id is None and componentobj is not None: self.dom_id = componentobj.attributes.get('dom_id', None) else: self.dom_id = dom_id if argnames is None: if componentobj is not None: self.argnames = [arg.name for arg in componentobj.arguments] else: self.argnames = [] else: self.argnames = argnames def get_handler_uri(self, handler_uri): if self.handler_uri is not None: return self.handler_uri else: return handler_uri def run(self, m): m.comp(self.component, **m.root_request_args) class DefCallable(JaxComponent): def __init__(self, jsname, defobj): self.jsname = jsname if isinstance(defobj, types.MethodType): defobj = defobj.im_func self.defobj = defobj self.argnames = inspect.getargspec(defobj)[0] self.has_named = inspect.getargspec(defobj)[2] is not None self.exectype = 'exec' def run(self, m): if self.has_named: m.write(self.defobj(**m.root_request_args)) else: args = {} for name in self.argnames: args[name] = m.root_request_args[name] m.write(self.defobj(**args)) <%method write_docall> <%args> callable handleruri function <% callable.jsname %>(callback<% callable.get_function_args(leading_comma = True) %>) { doCall('<% callable.get_handler_uri(handleruri) %>', callback, { <% callable.get_remote_args(m) %> }); } <%method write_remotejs> <%args> callable handleruri function <% callable.jsname %>(<% callable.get_function_args() %>) { runRemoteJS('<% callable.get_handler_uri(handleruri) %>', { <% callable.get_remote_args(m) %> }); } <%method write_writedom> <%args> callable handleruri % if callable.dom_id is None: function <% callable.jsname %>(domid <% callable.get_function_args(leading_comma = True) %>) { populateDOM(domid, '<% callable.get_handler_uri(handleruri) %>', { <% callable.get_remote_args(m) %> }); } % else: function <% callable.jsname %>(<% callable.get_function_args() %>) { populateDOM('<% callable.dom_id %>', '<% callable.get_handler_uri(handleruri) %>', { <% callable.get_remote_args(m) %> }); } % myghty-1.1/examples/zblog/htdocs/autohandler0000644000175000017500000000064510501064124020340 0ustar malexmalex <&SELF:title&> <&/toolbar&>
<&SELF:status&> % m.call_next()
<%method title> ZBlog <%method status> <%args scope="subrequest"> status=None % if status is not None:

<% status %>

% myghty-1.1/examples/zblog/htdocs/blog/0000755000175000017500000000000010501065764017040 5ustar malexmalexmyghty-1.1/examples/zblog/htdocs/blog/forms.myt0000644000175000017500000000654210501064124020715 0ustar malexmalex<%method postform> <%args> form blog post=None preview=None % if preview is not None:

Post Preview

<& views.myt:view_post, post=preview &>
% % if post is not None:

Editing '<% post.headline %>'

% else:

Posting to <% blog.name %>

% <&|/components:entryform, form=form, action="/blog/post/" &> <&/components:field, type="hidden", name="preview", value="0"&> <&/components:field, type="hidden", name="blog_id"&> <&/components:field, type="hidden", name="post_id"&> <&|/components:field, type="text", name="headline", size="50"&> Headline <&|/components:field, type="text", name="topic_keywords", size="50"&> Topics
(separated by spaces) <&|/components:field, type="textarea", name="summary", rows="3", cols="50"&> Summary <&|/components:field, type="textarea", name="body", rows="10", cols="50"&> Body <&|/components:row, colspan="2"&> % if post is not None: <&/components:field, type="submit", value="Update Post"&> <&/components:field, type="button", value="Delete Post", onclick="delete_post({'post_id':'%s'})" % post.id &> % else: <&/components:field, type="submit", value="Post"&> % <&/components:field, type="button", value="Preview", onclick="document.%s.preview.value=1;document.%s.submit()" % (form.name, form.name)&> <%method status> <%args> form <& /form:formstatus, form=form &> <%method delete_confirm> <%args> post <&|/components:confirm, yes="delete_post({'post_id':'%s','confirm':1})" % post.id, no="edit_post({'post_id':'%s'})" % post.id&>

Delete post '<% post.headline %>'

Are you sure you want to delete this post ? This will also delete all comments related to this post. <%method commentform> <%args> form parent=None post <%init> user = m.comp('/components:user') % if parent is None:

Post a comment for '<% post.headline %>'

% else:

Reply to '<% parent.subject %>'

% % if not actions.CreateComment().access(user, **ARGS): Please <&|/components:securehref, href="/login/", action=actions.Login() &>login to post a comment ! % return % <&|/components:entryform, form=form, action="/blog/comments/post/"&> <&/components:field, type="hidden", name="preview", value="0"&> <&/components:field, type="hidden", name="post_id"&> <&/components:field, type="hidden", name="parent_comment_id"&> <&|/components:field, type="text", name="subject", size="50"&> Subject <&|/components:field, type="textarea", name="body", rows="10", cols="50"&> Body <&|/components:row, colspan="2"&> <&/components:field, type="submit", value="Post Comment"&> <&/components:field, type="button", value="Preview", onclick="document.%s.preview.value=1;document.%s.submit()" % (form.name, form.name)&> myghty-1.1/examples/zblog/htdocs/blog/index.myt0000644000175000017500000000335610501064124020676 0ustar malexmalex<%args> blog loadcomponent = None form = None commentform = None keyword=False <%init> if m.comp('/ajax/myghtyjax.myt:init', post = { 'handler_uri':'/blog/ajax_post/', 'exectype':'writedom', 'dom_id':'opwindow' }, edit_post = { 'handler_uri':'/blog/ajax_edit_post/', 'exectype':'writedom', 'dom_id':'opwindow' }, delete_post = { 'handler_uri':'/blog/ajax_delete_post/', 'exectype':'writedom', 'dom_id':'opwindow' }, ): return <& /ajax/myghtyjax.myt:js &> % if loadcomponent: % else:
<&|/components:securehref, href="/blog/new_post/?blog_id=%s" % blog.id, action=actions.CreatePost(), blog=blog &>New Post

<% blog.name %>

<% blog.description %>

% if keyword: Keyword: <% keyword %>     all posts %
% <& /form:formstatus, form=form &>
% if loadcomponent: % m.comp(loadcomponent, **ARGS) % else: <&SELF:postlist, blog=blog, keyword=keyword&> %
<%method postlist> <%args> blog form=None keyword=False % if form: <& /form:formstatus, form=form &> % <&|/data:blogposts, blog=blog, loop=False, keyword=keyword&> % posts = m.content_args['posts']
    % for post in posts:
  • <& views.myt:post_summary, blog=blog, post=post &>
  • %
myghty-1.1/examples/zblog/htdocs/blog/post.myt0000644000175000017500000000211210501064124020541 0ustar malexmalex<%args> post viewpost=True viewcomments=True <%global> from zblog.domain.blog import * <%init> commentcount = post.comment_count <&views.myt:view_post, post=post&> % if not viewcomments: <% str(commentcount) %> Comments % else: <% str(commentcount) %> Comments % reply % if viewcomments:
<& SELF:postcomments, post=post &> <&forms.myt:commentform, **ARGS&> % <%method postcomments> <%args> post <&|/data:postcomments, loop=False, post=post &> % comments = m.content_args['comments'] % if len(comments): <& SELF:commentlist, comments=comments &> % else: No comments ! % <%method commentlist> <%args> comments % for comment in comments: <& views.myt:view_comment, comment=comment &>
<&SELF:commentlist, comments=comment.replies&>
% myghty-1.1/examples/zblog/htdocs/blog/postcomment.myt0000644000175000017500000000074310501064124022134 0ustar malexmalex<%args> comment=None post form preview=None <& /form:formstatus, form=form &>
% if comment is not None: <&views.myt:view_comment, comment=comment, showpost=True&> % % if preview is not None:

Preview New Comment

<&views.myt:view_comment, comment=preview, showpost=True&>
% <&forms.myt:commentform, parent=comment, **ARGS&> myghty-1.1/examples/zblog/htdocs/blog/views.myt0000644000175000017500000000403010501064124020712 0ustar malexmalex<%global> from zblog.domain.blog import * <%method post_summary> <%args> post blog

<% post.headline %>

<% post.summary %>

<&SELF:post_topics, post=post, blog=blog &>
Posted <% post.datetime %> by <% post.user.fullname %>
More...
Comments (<% str(post.comment_count) %>) <%method post_topics> <%args> post blog % sep = '' % for topic in post.topics: <% sep %><% topic.topic.keyword %>\ % sep = ', ' % <%method view_post> <%args> post
<% post.headline %>     <&views.myt:post_controls, post=post&>
<& SELF:post_topics, post=post, blog=post.blog&>

<% post.summary %>

<% post.body %>
<%method post_controls> <%args> post <&|/components:securehref, href="javascript:edit_post({'post_id':'%s'})" % post.id, action=actions.EditPost(), post=post &>Edit <&|/components:securehref, href="javascript:delete_post({'post_id':'%s'})" % post.id, action=actions.EditPost(), post=post &>Delete <%method view_comment> <%args> comment showpost=False <% comment.subject %> % if showpost:     posted to <% comment.post.headline %> % % if comment.parent:
in reply to <% comment.parent.subject %> %
<% comment.body %>
Posted by <% comment.user.fullname %> on <% comment.datetime %> reply myghty-1.1/examples/zblog/htdocs/bootstrap/0000755000175000017500000000000010501065764020132 5ustar malexmalexmyghty-1.1/examples/zblog/htdocs/bootstrap/complete.myt0000644000175000017500000000022010501064124022454 0ustar malexmalex<%args> log error

Bootstrap <% error and 'Failed!' or 'Complete!' %>

Application log:

<% log %>
myghty-1.1/examples/zblog/htdocs/bootstrap/index.myt0000644000175000017500000000525210501064124021765 0ustar malexmalex<%doc> bootstrap/index.myt . This template is designed to interact with the zblog.controller.bootstrap controller module and is used to get initial database config information. <%init> # AJAX call definitions. both ajax calls in this doc go to controllers. if m.comp('/ajax/myghtyjax.myt:init', dboptions = { 'component':m.fetch_component('/bootstrap/ajax_dboptions', resolver_context='frontcontroller'), 'handler_uri':'/bootstrap/ajax_dboptions/', 'exectype':'writedom', 'dom_id':'dbtype' }, testconnect = { 'component':m.fetch_component('/bootstrap/ajax_testconnect', resolver_context='frontcontroller'), 'handler_uri':'/bootstrap/ajax_testconnect/', 'exectype':'writedom', 'dom_id':'dbtype' }, ): return <%args> form <& /ajax/myghtyjax.myt:js &>

ZBLOG Bootstrap System

This page is only viewed when you first install ZBLOG.

<& /form:formstatus, form=form &> <&|/components:entryform, action="/bootstrap/bootstrap/", columns=2, form=form&> <&|/components:field, type="text", name='adminuser'&> Administrator Username <&|/components:field, type="text", name='adminpw'&> Administrator Password <&|/components:field, type="dropdown", name='dbtype', onselect='dboptions'&> Database Type <&|/components:popup, name="dbtype"&> <& SELF:dboptions, **ARGS &> <%method dboptions> <%doc>Displays the entry fields corresponding to a specific type of database, if the given form contains a 'dbform' element, which is set by the controller. else just returns. <%args scope="dynamic"> form dbtype=None connected=False error=None <%init> if not dbtype: return subform = form['dbform'] formargs = m.scomp('/components:formargs', form=subform, name=form.name) <&|/components:fields&>

<% subform.description %> Options

% for field in subform: <&|/components:field, type="text", field=field &> <% field.description %> % % if error: <% str(error) %>
% % if not connected: <&/components:button, value="Connect", onclick="testconnect('%s', %s)" % (dbtype, formargs) &> % elif connected: Connected! <&/components:submit, value="Write Config File" &> % myghty-1.1/examples/zblog/htdocs/favicon.ico0000644000175000017500000000000110501064124020212 0ustar malexmalex myghty-1.1/examples/zblog/htdocs/global.js0000644000175000017500000000033310501064124017677 0ustar malexmalexfunction getargs(form) { var list = {}; for (var i=1; i blog_user = None % if blog_user:

My Blogs

% else:

All Blogs

% <&|/data:bloglist, loop=False, user=blog_user&> % blogs = m.content_args['blogs'] % if len(blogs): % elif blog_user: You have no blogs ! % else: There are no blogs in this system. Use the 'Manage' function to create blogs. % myghty-1.1/examples/zblog/htdocs/login.myt0000644000175000017500000000105510501064124017746 0ustar malexmalex<%args> form

ZBlog Login

<&|/components:entryform, name="mainform", columns=2, action="/login/login/"&> <&|/components:field, type="text", field=form['username']&> Username <&|/components:field, type="password", field=form['password']&> Password <&|/components:row&> <&/components:field, type="submit", value="Login"&> <%method status> <%args scope="subrequest"> form <&PARENT:status&>

<& /form:formstatus, form=form, &>

myghty-1.1/examples/zblog/htdocs/register.myt0000644000175000017500000000144210501064124020462 0ustar malexmalex<%args> form

Register for ZBlog!

<&|/components:entryform, form=form, columns=2&> <&/components:field, type="hidden", name="validate", value="1"&> <&|/components:field, type="text", name='name' &> User Name <&|/components:field, type="text", name='fullname'&> Full Name <&|/components:field, type="password", name='password_set'&> Password <&|/components:field, type="password", name='password_repeat'&> Repeat Password <&|/components:row, colspan="2" &> <&/components:field, type="submit", value="Register"&> <%method status> <%args scope="subrequest"> form <&PARENT:status&>

<& /form:formstatus, form=form, &>

myghty-1.1/examples/zblog/htdocs/style.css0000644000175000017500000000145110501064124017755 0ustar malexmalexbody, td { font-family: verdana, sans-serif; font-size: 12px; } body { background-color: #FDFBFC; margin:20px 20px 20px 20px; } p { margin-top:10px; margin-bottom:10px; } a {font-weight:normal; text-decoration:underline;} a:link {color:#0000FF;} a:visited {color:#0000FF;} a:active {color:#0000FF;} a:hover {color:#700000;} .large { font-size:24px; font-weight:bold; } .medium { font-size:14px; font-weight:bold; } .slogan { font-size:16px; font-weight:normal; font-style:italic; } .small { font-size:12px; } .grey { color:#BFBFBF; } .header { margin:10px; } .toolbar { margin:10px; padding:10px; border:1px solid; } .loginstatus { float:right; } .rightcontrols { float:right; padding-right:50px; text-align:right; } .main { margin:10px; } .confirm { width:300px; }myghty-1.1/examples/zblog/lib/0000755000175000017500000000000010501065764015377 5ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/0000755000175000017500000000000010501065764016514 5ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/__init__.py0000644000175000017500000000176510501064125020624 0ustar malexmalex"""zblog general module. Defines global application constants and helper functions.""" import os """application configuration. the runtime environment should establish configuration details in this dictionary.""" config = {} """startup list. when configuration is loaded, callables in this list will be executed.""" startup =[] """indicates if config file needs to be loaded.""" need_config = True def load_config(file): """called by application starting point (such as a front controller or command line script) to initialize configuration with given filename.""" global need_config if not need_config: return False if len(config) == 0: if os.access(file, os.F_OK): execfile(file, config) need_config=False for callable_ in startup: callable_() return need_config def reset_config(): """reset config - used by bootstrapper when an error occurs""" config.clear() global need_config need_config=True myghty-1.1/examples/zblog/lib/zblog/controller/0000755000175000017500000000000010501065764020677 5ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/controller/__init__.py0000644000175000017500000000134510501064125023001 0ustar malexmalex"""controller package - defines Myghty application controllers""" __all__ = ['Controller', 'access_control'] from sqlalchemy import * class Controller(object): def template(self, m, template, **kwargs): m.subexec(template, **kwargs) def get_user(self, m): s = m.get_session() u = s.get('user', None) # import_instance assures that the user deserialized from the session # is properly present in the current thread's unit of work context. return objectstore.import_instance(u) def access_control(action=None, login=False): def decorator(func): func.action = action func.login = login func.public = True return func return decoratormyghty-1.1/examples/zblog/lib/zblog/controller/blog/0000755000175000017500000000000010501065764021622 5ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/controller/blog/__init__.py0000644000175000017500000000000010501064125023707 0ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/controller/blog/comments.py0000644000175000017500000000677010501064125024021 0ustar malexmalexfrom zblog.controller import * import re from zblog.domain.blog import * import zblog.domain.actions as actions import zblog.util.form as formutil import zblog.database.mappers as mapper class Comments(Controller): @access_control() def __call__(self, m, ARGS): """produces a listing of comments for a post""" match = re.search(r'/(\w+)/$', m.request_path) if match: post_id = match.group(1) else: m.abort(404) post = Post.mapper.get(post_id) if post is None: m.abort(404) form = self.commentform(m, ARGS, post) self.template(m, '/blog/comments.myt', post=post, form=form) @access_control() def view(self, m, ARGS): """views a specific comment""" match = re.search(r'/(\w+)/$', m.request_path) if match: comment_id = match.group(1) else: m.abort(404) comment = Comment.mapper.get(comment_id) if comment is None: m.abort(404) form = self.commentform(m, ARGS, comment.post, comment) self.template(m, '/blog/postcomment.myt', post=comment.post, comment=comment, form=form) @access_control(action=actions.CreateComment()) def post(self, m, ARGS, post_id, parent_comment_id, confirm=False, preview=False): """posts a comment, or provides a preview display.""" post = Post.mapper.get(post_id) if post is None: m.abort(404) if parent_comment_id: parentcomment = Comment.mapper.get(parent_comment_id) else: parentcomment = None form = self.commentform(m, ARGS, post, comment=parentcomment) form.set_request(ARGS, validate=True) if not form.is_valid(): self.template(m, '/blog/postcomment.myt', post=post, form=form, comment=parentcomment) return if int(preview): comment = self.createcomment(m, post, form, parentcomment) self.template(m, '/blog/postcomment.myt', post=post, form=form, comment=parentcomment, preview=comment) return mapper.begin() comment = self.createcomment(m, post, form, parentcomment) mapper.commit() form = self.commentform(m, ARGS, post, comment) form.append_success("Comment posted!") self.template(m, '/blog/postcomment.myt', post=post, comment=comment, form=form) def createcomment(self, m, post, form, parentcomment): """creates a new comment from a given form""" comment = Comment() comment.id = None form.reflect_to(comment) comment.user = self.get_user(m) comment.post = post if parentcomment is not None: comment.parent = parentcomment return comment def commentform(self, m, ARGS, post, comment=None): """creates a blank comment form""" if comment is not None: subject = "Re: %s" % comment.subject else: subject = None form=formutil.Form('comment', [ formutil.IntFormField('post_id'), formutil.IntFormField('preview'), formutil.IntFormField('parent_comment_id'), formutil.FormField('subject', required=True, default=subject), formutil.FormField('body', required=True) ]) form['post_id'].value = post.id if comment is not None: form['parent_comment_id'].value = comment.id return form index = Comments() myghty-1.1/examples/zblog/lib/zblog/controller/blog/index.py0000644000175000017500000001340510501064125023274 0ustar malexmalexfrom zblog.controller import * import re, string from zblog.domain.blog import * import zblog.domain.actions as actions import zblog.util.form as formutil import zblog.database.mappers as mapper from zblog.controller.blog.comments import index as commentcontroller class BlogHome(Controller): @access_control() def __call__(self, m, ARGS): """shows all the posts within a blog""" match = re.search(r'/(\w+)/(?:topic/(\w+)/)?$', m.request_path) if match: blog_id = match.group(1) if blog_id == 'my': self.template(m, '/index.myt', blog_user=self.get_user(m)) return keyword = match.group(2) or False else: m.abort(404) blog = Blog.mapper.get(blog_id) if blog is None: m.abort(404) self.template(m, '/blog/index.myt', blog=blog, keyword=keyword) @access_control(action=actions.CreatePost()) def post(self, m, ARGS, blog_id, post_id=None, preview=False): """submits a post, or previews it.""" blog = Blog.mapper.get(blog_id) form = self.postform(m, ARGS) form.set_request(ARGS, validate=True) if post_id: post = Post.mapper.get(post_id) if post is None: form.append_error("no post found for id '%s'" % post_id) self.template(m, '/blog/index.myt', blog=blog, loadcomponent='/blog/forms.myt:postform', form=form) return else: post = None if not form.is_valid(): self.template(m, '/blog/index.myt', blog=blog, loadcomponent='/blog/forms.myt:postform', form=form, post=post) elif int(preview): if post is None: preview = Post() else: preview = post self.reflect_to(m, form, preview, blog) self.template(m, '/blog/index.myt', blog=blog, loadcomponent='/blog/forms.myt:postform', form=form, preview=preview, post=post) else: mapper.begin() if post is None: post = Post() self.reflect_to(m, form, post, blog) mapper.commit() if not post_id: form.append_success("Post added") else: form.append_success("Post updated") self.template(m, '/blog/index.myt', blog=blog, post=post, loadcomponent='/blog/post.myt', viewcomments=False, form=form ) def reflect_to(self, m, form, post, blog): form.reflect_to(post) primary = True post.topics = [] for keyword in form['topic_keywords'].value.split(' '): if not keyword: continue topic = Topic.mapper.get_by(keyword=keyword) if topic is None: topic = Topic(keyword=keyword, description=keyword) post.topics.append(TopicAssociation(post=post, topic=topic, is_primary=False)) print repr([(t, t.post, t.topic) for t in post.topics.records.keys()]) post.user = self.get_user(m) post.blog = blog def reflect_from(self, form, post): form.reflect_from(post) form['topic_keywords'].value = string.join([t.topic.keyword for t in post.topics]) @access_control(action=actions.CreatePost()) def new_post(self, m, ARGS, blog_id): """provides a blank "submit a post" screen""" blog = Blog.mapper.get(blog_id) form = self.postform(m, ARGS) self.template(m, '/blog/index.myt', loadcomponent='/blog/forms.myt:postform', form=form, blog=blog) @access_control(action=actions.EditPost()) def ajax_edit_post(self, m, ARGS, post_id): """produces an "edit post" screen, given the id of the post""" post = Post.mapper.get(post_id) form = self.postform(m, ARGS) self.reflect_from(form, post) m.comp('/blog/forms.myt:postform', form=form, blog=post.blog, post=post) @access_control(action=actions.EditPost()) def ajax_delete_post(self, m, ARGS, post_id, confirm=False): """deletes a post, given its ID.""" post = Post.mapper.get(post_id) blog = post.blog if not confirm: m.comp('/blog/forms.myt:delete_confirm', post=post) return form = self.postform(m, ARGS) mapper.begin() mapper.delete(post) mapper.commit() form.append_success("Post deleted") m.comp('/blog/index.myt:postlist', blog=blog, form=form) @access_control() def view(self, m, ARGS): """views a specific post within a blog as well as its comments""" match = re.search(r'/(\w+)/$', m.request_path) if match: post_id = match.group(1) else: m.abort(404) #post = Post.mapper.options(nodefer('body')).get(post_id) post = Post.mapper.get(post_id) if post is None: m.abort(404) commentform = commentcontroller.commentform(m, ARGS, post=post) self.template(m, '/blog/index.myt', blog=post.blog, post=post, showcomments=True, form=commentform, loadcomponent='/blog/post.myt' ) def postform(self, m, ARGS): form=formutil.Form('post', [ formutil.IntFormField('blog_id'), formutil.IntFormField('post_id'), formutil.FormField('preview'), formutil.FormField('headline', required=True), formutil.FormField('topic_keywords'), formutil.FormField('summary'), formutil.FormField('body', required=True) ]) if ARGS.has_key('post_id'): form['post_id'].value = ARGS['post_id'] if ARGS.has_key('blog_id'): form['blog_id'].value = ARGS['blog_id'] return form index = BlogHome() myghty-1.1/examples/zblog/lib/zblog/controller/bootstrap.py0000644000175000017500000001057610501064125023265 0ustar malexmaleximport zblog.database import zblog.util.form as form import StringIO, os from zblog.controller import * import zblog.domain.actions as actions class BootstrapController(Controller): """bootstrap controller, is used to receive configuration details and create config.py file. this controller is only used when first configuring the application.""" @access_control(action=actions.Bootstrap()) def __call__(self, m, ARGS, dbtype='sqlite'): form = self.form(ARGS, post=False) self.template(m, '/bootstrap/', form=form, dbtype=dbtype) @access_control(action=actions.Bootstrap()) def bootstrap(self, m, ARGS, **kwargs): form = self.form(ARGS, post=True) if not form.is_valid(): self.template(m, '/bootstrap/', form=form, **kwargs) else: (logstring, error) = self.bootstrap_app(m, form) self.template(m, '/bootstrap/complete.myt', log=logstring, error=error) @access_control(action=actions.Bootstrap()) def ajax_dboptions(self, m, ARGS, dbtype): m.comp('/bootstrap/index.myt:dboptions', dbtype=dbtype, form=self.form(ARGS)) @access_control(action=actions.Bootstrap()) def ajax_testconnect(self, m, r, ARGS, dbtype): dbform = self.form(ARGS)['dbform'] dbform.set_request(ARGS) kwargs = {} for e in dbform: kwargs[e.name] = e.value try: connect = zblog.database.test_connection(dbtype, kwargs) error = None except Exception, e: error = e m.comp('/bootstrap/index.myt:dboptions', dbtype=dbtype, form=self.form(ARGS), connected=(error is None), error=error) def form(self, ARGS, post=False): db_descriptors = zblog.database.dbtypes() opt = [(None, '(select)')] + [(desc['name'], desc['description']) for desc in db_descriptors] f = form.Form('bootstrap',[ form.FormField('adminuser', required=True, default='Administrator'), form.FormField('adminpw', required=True), form.FormField('dbtype', required=True, default='sqlite', data=opt) ]) dbform = form.SubForm('dbform', []) f.append(dbform) dbtype = ARGS.get('dbtype', 'sqlite') desc = zblog.database.get_descriptor(dbtype) if desc is not None: dbform.description = desc['description'] f['dbtype'].value=dbtype for field in desc['arguments']: if dbtype == 'sqlite' and field[0] == 'filename': default = './data/zblog.db' else: default = field[2] dbform.append(form.FormField(field[0], description=field[1], default=default, required=True)) f.set_request(ARGS, validate=post) return f def bootstrap_app(self, m, form): """when all the arguments are assembled, this method writes out a config file and calls the appropriate modules to create the database schema.""" s = StringIO.StringIO() dbtype = form['dbtype'].value desc = zblog.database.get_descriptor(dbtype) s.write(""" database = dict( driver='%s', echo=True, """ % dbtype) subform = form['dbform'] for arg in desc['arguments']: s.write(" %s='%s',\n" % (arg[0], subform[arg[0]].value)) s.write(""")""") logger =StringIO.StringIO() # write configuration file logger.write("Creating configuration file '%s'\n" % m.interpreter.attributes['config_file']) f = file(m.interpreter.attributes['config_file'], 'w') f.write(s.getvalue()) f.close() try: # initialize application config zblog.load_config(m.interpreter.attributes['config_file']) # create database logger.write("Creating application tables\n") zblog.database.init_database(admin_username=form['adminuser'].value, admin_password=form['adminpw'].value, logger=logger) error = False except Exception, e: # boom - delete config file logger.write("Error occurred: '%s'\ndeleting config file\n" % str(e)) zblog.reset_config() os.remove(m.interpreter.attributes['config_file']) error = True return (logger.getvalue(), error) index = BootstrapController() myghty-1.1/examples/zblog/lib/zblog/controller/front.py0000644000175000017500000000441210501064125022370 0ustar malexmaleximport zblog import zblog.controller import zblog.domain.actions as actions import re import myghty.exception as exception class FrontController(zblog.controller.Controller): """initial controller accessed for all pages. Is used for access control, as well as initial application configuration is loaded via Interpreter attributes. If configuration file doesnt exist, bounces to "bootstrap" page to create config file. """ def check_bootstrap(self, m): """checks if we are in "bootstrap mode", which means theres no config or database setup yet.""" bootstrap = m.resolution.match.group(1).startswith('/bootstrap/') if zblog.need_config and not bootstrap: m.send_redirect('/bootstrap/', hard=True) elif not zblog.need_config and bootstrap: m.abort(403) def __call__(self, m, ARGS): """main handling method. does coarse-grained access control check and forwards on to the requested controller.""" self.check_bootstrap(m) if not zblog.need_config: # start mapper session. clears out previous identitymaps and units of # work, within the current thread zblog.database.mappers.start_session() uri = m.resolution.match.group(1) try: controller = m.fetch_component(uri, resolver_context="frontcontroller", enable_dhandler=True) except exception.ComponentNotFound, cfound: raise cfound.create_toplevel() public = getattr(controller.component_source.callable_, 'public', False) if not public: m.abort(404) return user = self.get_user(m) login = getattr(controller.component_source.callable_, 'login', False) if login and user is None: login = m.fetch_component('/login/', resolver_context="frontcontroller", enable_dhandler=True) m.subexec(login) return action = getattr(controller.component_source.callable_, 'action', None) if action is not None: if not action.access(user, **ARGS): m.abort(403) return m.comp(controller, **m.request_args) index = FrontController() myghty-1.1/examples/zblog/lib/zblog/controller/index.py0000644000175000017500000000026110501064125022345 0ustar malexmalexfrom zblog.controller import * class HomePage(Controller): @access_control() def __call__(self, m, r, ARGS): self.template(m, '/index.myt') index = HomePage() myghty-1.1/examples/zblog/lib/zblog/controller/login.py0000644000175000017500000000613310501064125022352 0ustar malexmaleximport zblog.controller import zblog.database.mappers as mapper from zblog.domain.user import User import zblog.util.form as form import StringIO, os import zblog.domain.actions as actions from zblog.controller import access_control class LoginController(zblog.controller.Controller): def form(self): f = form.Form('login',[ form.FormField('username', required=True), form.FormField('password', required=True), ]) return f @access_control() def __call__(self, m, **kwargs): f = self.form() self.template(m, '/login.myt', form=f) @access_control(action=actions.Login()) def login(self, m, r, ARGS, **kwargs): f = self.form() f.set_request(ARGS) u = User.mapper.get_by(name=f['username'].display) if u is None or not u.checkpw(f['password'].display): u = None if u is not None: s = m.get_session() s['user'] = u s.save() m.send_redirect("/", hard=True) else: f.append_error("Login failed") self.template(m, '/login.myt', form=f) @access_control(action=actions.Logout()) def logout(self, m, **kwargs): s = m.get_session() if s.has_key('user'): del s['user'] s.save() self.template(m, '/login.myt', form=self.form(), status="You have been logged out") else: self.template(m, '/login.myt', form=self.form(), status="You are not logged in") @access_control(login=False, action=actions.Register()) def register(self, m, ARGS, validate=False): form = self.registerform(m) form.set_request(ARGS, validate=validate) if not validate: self.template(m, '/register.myt', form=form) return if form['password_set'].value and form['password_set'].value != form['password_repeat'].value: form.append_error("Passwords do not match") form.isvalid=False existing = User.mapper.get_by(name=form['name'].value) if existing is not None: form['name'].append_error("Username '%s' already exists" % form['name'].value) form.isvalid=False if not form.is_valid(): self.template(m, '/register.myt', form=form) return mapper.begin() user = User() form.reflect_to(user) user.group=zblog.domain.user.user mapper.commit() form = self.form() form.append_success("Thanks for registering, %s!" % (user.name)) self.template(m, '/login.myt', form=form) def registerform(self, m): f = form.Form('user',[ form.IntFormField('user_id', attribute='id'), form.FormField('name', required=True), form.FormField('fullname', required=True), form.FormField('password_set', attribute='password', required=True), form.FormField('password_repeat', attribute=None, required=True) ]) return f index = LoginController() myghty-1.1/examples/zblog/lib/zblog/controller/manage/0000755000175000017500000000000010501065764022127 5ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/controller/manage/__init__.py0000644000175000017500000000000010501064125024214 0ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/controller/manage/blog.py0000644000175000017500000000560610501064125023421 0ustar malexmaleximport zblog.controller import zblog.util.form as formutil from zblog.domain.user import User from zblog.domain.blog import Blog import zblog.database.mappers as mapper import zblog.domain.actions as actions from zblog.controller import access_control class ManageBlog(zblog.controller.Controller): @access_control(login=True, action=actions.EditBlog()) def ajax_editblog(self, m, ARGS, blog_id=None, form=None): if form is None: form = self.form(m) if blog_id: blog = Blog.mapper.get(blog_id) form.reflect_from(blog) m.comp('/admin/blog.myt:blogedit', form=form) @access_control(login=True, action=actions.EditBlog()) def bloglist(self, m): m.comp('/admin/blog.myt:bloglist') @access_control(login=True, action=actions.EditBlog()) def edit_blog(self, m, ARGS, blog_id=None): form = self.form(m) form.set_request(ARGS, validate=True) if not form.is_valid(): self.ajax_editblog(m, ARGS, form=form, blog_id=blog_id) return owner = User.mapper.get_by(name=form['owner_name'].value) if owner is None: form.append_error("Could not locate user %s" % form['owner_name'].value) self.ajax_editblog(m, ARGS, form=form, blog_id=blog_id) return mapper.begin() if blog_id: created = False blog = Blog.mapper.get(blog_id) else: created = True blog = Blog() form.reflect_to(blog) blog.owner = owner mapper.commit() form.append_success("Blog '%s' %s" % (blog.name, created and 'created' or 'updated')) form.reflect_from(blog) m.comp('/admin/blog.myt:blogedit', form=form) @access_control(login=True, action=actions.EditBlog()) def delete_blog(self, m, ARGS, blog_id=None, confirm=False): form= self.form(m) blog = Blog.mapper.get(blog_id) if blog is None: form.append_error("No blog found for id '%s'" % blog_id) self.ajax_editblog(m, ARGS, form=form) return if not confirm: m.comp('/admin/blog.myt:delete_confirm', blog=blog) return name = blog.name mapper.begin() mapper.delete(blog) mapper.commit() form.append_success("Blog '%s' deleted" % name) form.clear() m.comp('/admin/blog.myt:blogedit', form=form) def form(self, m): u = self.get_user(m) f = formutil.Form('blog',[ formutil.IntFormField('blog_id', attribute='id'), formutil.FormField('name', required=True), formutil.FormField('description', required=True), formutil.FormField('owner_name', required=True, default=u.name, getattribute="owner.name") ]) return f index = ManageBlog()myghty-1.1/examples/zblog/lib/zblog/controller/manage/index.py0000644000175000017500000000063610501064125023603 0ustar malexmaleximport zblog.controller import zblog.util.form as formutil import zblog.database.mappers as mapper import zblog.domain.actions as actions import zblog.controller.manage.blog as manageblog from zblog.controller import access_control class Manage(zblog.controller.Controller): @access_control(login=True) def __call__(self, m, r, ARGS): self.template(m, '/admin/index.myt') index = Manage() myghty-1.1/examples/zblog/lib/zblog/controller/manage/user.py0000644000175000017500000000653310501064125023454 0ustar malexmaleximport zblog.controller import zblog.util.form as formutil from zblog.domain.user import User from zblog.domain.blog import Blog import zblog.database.mappers as mapper import zblog.domain.actions as actions from zblog.controller import access_control class ManageUser(zblog.controller.Controller): @access_control(login=True, action=actions.AdminUsers()) def ajax_edituser(self, m, ARGS, user_id=None, username=None, form=None): if form is None: form = self.form(m) if username is not None: user = User.mapper.get_by(name=username) if user is not None: form.reflect_from(user) form['password_set'].required = False form['password_repeat'].required = False else: form.append_error("Username '%s' not found" % username) m.comp('/admin/user.myt:userform', form=form) @access_control(login=True, action=actions.AdminUsers()) def edit_user(self, m, ARGS, user_id=None): form = self.form(m) if user_id: form['password_set'].required = False form['password_repeat'].required = False form.set_request(ARGS, validate=True) if form['password_set'].value and form['password_set'].value != form['password_repeat'].value: form.append_error("Passwords do not match") if not user_id: existing = User.mapper.get_by(name=form['name'].value) if existing is not None: form['name'].append_error("Username '%s' already exists" % form['name'].value) if not form.is_valid(): self.ajax_edituser(m, ARGS, form=form) return mapper.begin() if user_id: created = False user = User.mapper.get(user_id) else: created = True user = User() form.reflect_to(user) mapper.commit() form.append_success("User '%s' %s" % (user.name, created and 'created' or 'updated')) form.reflect_from(user) m.comp('/admin/user.myt:userform', form=form) @access_control(login=True, action=actions.AdminUsers()) def delete_user(self, m, ARGS, user_id, confirm=False): form = self.form(m) user = User.mapper.get(user_id) if user is None: form.append_error("Userid %d not found" % user_id) self.ajax_edituser(m, ARGS, form=form) return if not confirm: m.comp('/admin/user.myt:delete_confirm', user=user) return name = user.name mapper.begin() mapper.delete(user) mapper.commit() form.append_success("User '%s' deleted" % name) form.clear() m.comp('/admin/user.myt:userform', form=form) def form(self, m): f = formutil.Form('user',[ formutil.IntFormField('user_id', attribute='id'), formutil.FormField('name', required=True), formutil.FormField('fullname', required=True), formutil.FormField('group', required=True, data=[(x,x) for x in zblog.domain.user.groups]), formutil.FormField('password_set', attribute='password', required=True), formutil.FormField('password_repeat', attribute=None, required=True) ]) return f index=ManageUser() myghty-1.1/examples/zblog/lib/zblog/database/0000755000175000017500000000000010501065764020260 5ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/database/__init__.py0000644000175000017500000000353110501064125022361 0ustar malexmalex"""global database stuff. includes functions for finding out about database drivers, as well as the global SQLEngine used by the application.""" import zblog import sqlalchemy.engine import sys def dbtypes(): """returns a listing of SQLEngine descriptors for usage in bootstrap system""" return sqlalchemy.engine.engine_descriptors() def get_descriptor(name): """returns the named SQLEngine descriptor for usage in bootstrap system.""" for desc in dbtypes(): if desc['name'] == name: break else: desc = None return desc def test_connection(name, kwargs): """given a SQLEngine name and keyword arguments, creates a connection, or propigates whatever exceptions occur.""" e = sqlalchemy.engine.create_engine(name, kwargs) e.connection() def init_engine(): global engine conf = zblog.config['database'].copy() driver = conf.pop('driver') engine = sqlalchemy.engine.create_engine(driver, conf, echo=conf.pop('echo', False)) # load mapper module to intialize mapper attributes on domain classes __import__('zblog.database.mappers') # add init_engine to startup callables zblog.startup.append(init_engine) def init_database(admin_username, admin_password, logger): """creates database tables and inserts administrative user upon installation.""" import zblog.database.tables if zblog.database.tables.db != engine: reload(zblog.database.tables) e = engine.echo engine.echo=True engine.logger=logger zblog.database.tables.create_tables() import zblog.domain.user as user import zblog.database.mappers as mapper mapper.begin() try: u = user.User(admin_username, 'Administrator', admin_password, user.administrator) mapper.commit() finally: engine.echo = e engine.logger = sys.stdoutmyghty-1.1/examples/zblog/lib/zblog/database/mappers.py0000644000175000017500000001560310501064125022274 0ustar malexmalex"""mapper.py - defines mappers for domain objects, mapping operations""" import zblog.database.tables as tables import zblog.domain.user as user from zblog.domain.blog import * from sqlalchemy import * import sqlalchemy.util as util # User mapper. Here, we redefine the names of some of the columns # to different property names. normally the table columns are all # sucked in automatically. user.User.mapper = mapper(user.User, tables.users, properties={ 'id':tables.users.c.user_id, 'name':tables.users.c.user_name, 'group':tables.users.c.groupname, 'crypt_password':tables.users.c.password, }) # blog mapper. this contains a reference to the user mapper, # and also installs a "backreference" on that relationship to handle it # in both ways. this will also attach a 'blogs' property to the user mapper. Blog.mapper = mapper(Blog, tables.blogs, properties={ 'id':tables.blogs.c.blog_id, 'owner':relation(user.User, lazy=False, backref='blogs'), }, is_primary=True) # override the 'blogs' property on the user mapper to be a "private" relation, # which means the blogs only exist as children of that user. remove the blog # from the user's list, it gets deleted; delete the user, the blogs get deleted. user.User.mapper.add_property('blogs', relation(Blog.mapper, private=True, lazy=True, backref='owner')) # topic mapper. map all topic columns to the Topic class. Topic.mapper = mapper(Topic, tables.topics) # TopicAssocation mapper. This is an "association" object, which is similar to # a many-to-many relationship except extra data is associated with each pair # of related data. because the topic_xref table doesnt have a primary key, # the "primary key" columns of a TopicAssociation are defined manually here. TopicAssociation.mapper = mapper(TopicAssociation,tables.topic_xref, primary_key=[tables.topic_xref.c.post_id, tables.topic_xref.c.topic_id], properties={ 'topic':relation(Topic.mapper, lazy=False), }) # Post mapper, these are posts within a blog. # since we want the count of comments for each post, create a select that will get the posts # and count the comments in one query. posts_with_ccount = select( [c for c in tables.posts.c if c.key != 'body'] + [ func.count(tables.comments.c.comment_id).label('comment_count') ], from_obj = [ outerjoin(tables.posts, tables.comments) ], group_by=[ c for c in tables.posts.c ] ) .alias('postswcount') # then create a Post mapper on that query. # we have the body as "deferred" so that it loads only when needed, # the user as a Lazy load, since the lazy load will run only once per user and # its usually only one user's posts is needed per page, # the owning blog is a lazy load since its also probably loaded into the identity map # already, and topics is an eager load since that query has to be done per post in any # case. Post.mapper = mapper(Post, posts_with_ccount, properties={ 'id':posts_with_ccount.c.post_id, 'body':deferred(tables.posts.c.body), 'user':relation(user.User, lazy=True, backref='posts'), 'blog':relation(Blog, lazy=True, backref='posts'), 'topics':relation(TopicAssociation, lazy=False, private=True, association=Topic) }, is_primary=True, order_by=[desc(posts_with_ccount.c.datetime)]) # override 'posts' property on Blog to be private, so that posts get deleted when the blog does. Blog.mapper.add_property('posts', relation(Post.mapper, private=True, lazy=True, backref='blog')) # override 'posts' property on User to be private, so all user posts in all blogs get # removed when the user does. user.User.mapper.add_property('posts', relation(Post.mapper, private=True, lazy=True, backref='user')) # comment mapper. This mapper is handling a hierarchical relationship on itself, and contains # a lazy reference both to its parent comment and its list of child comments. Comment.mapper = mapper(Comment, tables.comments, properties={ 'id':tables.comments.c.comment_id, 'post':relation(Post.mapper, lazy=True, backref='comments'), 'user':relation(user.User.mapper, lazy=False, backref='comments'), 'parent':relation(Comment, primaryjoin=tables.comments.c.parent_comment_id==tables.comments.c.comment_id, foreignkey=tables.comments.c.comment_id, lazy=True, uselist=False), 'replies':relation(Comment,primaryjoin=tables.comments.c.parent_comment_id==tables.comments.c.comment_id, lazy=True, uselist=True, private=True), }, is_primary=True) # override the "post" and "user" backreference-generated properties to be lazy properties Post.mapper.add_property('comments', relation(Comment.mapper, private=True, lazy=True, backref='post')) user.User.mapper.add_property('comments', relation(Comment.mapper, private=True, lazy=True, backref='user')) # we define one special find-by for the comments of a post, which is going to make its own "noload" # mapper and organize the comments into their correct hierarchy in one pass. hierarchical # data normally needs to be loaded by separate queries for each set of children, unless you # use a proprietary extension like CONNECT BY. def find_by_post(post): """returns a hierarchical collection of comments based on a given criterion. uses a mapper that does not lazy load replies or parents, and instead organizes comments into a hierarchical tree when the result is produced. """ mapper = Comment.mapper.options(noload('replies'), noload('parent')) comments = mapper.select_by(post_id=post.id) result = [] d = {} for c in comments: d[c.id] = c if c.parent_comment_id is None: result.append(c) c.parent=None else: parent = d[c.parent_comment_id] parent.replies.append(c) c.parent = parent return result Comment.find_by_post = staticmethod(find_by_post) # define a bunch of convenience methods on the objectstore. def start_session(): """clears the objectstore, so that when a new user request is handled, all data will be loaded from the database completely, and anything left over from the previous session is removed. Clearing the objectstore is a thread-local operation.""" objectstore.clear() # keep track of transaction token in a thread local. # this is a compatibility hack since SQLAlchemy recently # changed its begin/commit style to return this tranasctional token # and the code is not keeping track of it, so we track it here # within our own begin/commit trans = util.ThreadLocal() def begin(): """begins a transaction with the objectstore.""" trans.t = objectstore.begin() def commit(): """commits a transaction with the objectstore. everything modified since the last begin() is updated in the database.""" print "\n\n------------------------------\n\n" trans.t.commit() def delete(*obj): """marks an object (or objects) to be deleted upon the next commit().""" objectstore.delete(*obj) myghty-1.1/examples/zblog/lib/zblog/database/tables.py0000644000175000017500000000421310501064125022072 0ustar malexmalexfrom sqlalchemy import * from zblog.database import engine as db """application table metadata objects are described here.""" users = Table('users', db, Column('user_id', Integer, primary_key=True), Column('user_name', String(30), nullable=False), Column('fullname', String(100), nullable=False), Column('password', String(30), nullable=False), Column('groupname', String(20), nullable=False), ) blogs = Table('blogs', db, Column('blog_id', Integer, primary_key=True), Column('owner_id', Integer, ForeignKey('users.user_id'), nullable=False), Column('name', String(100), nullable=False), Column('description', String(500)) ) posts = Table('posts', db, Column('post_id', Integer, primary_key=True), Column('blog_id', Integer, ForeignKey('blogs.blog_id'), nullable=False), Column('user_id', Integer, ForeignKey('users.user_id'), nullable=False), Column('datetime', DateTime, nullable=False), Column('headline', String(500)), Column('summary', String), Column('body', String), ) topics = Table('topics', db, Column('topic_id', Integer, primary_key=True), Column('keyword', String(50), nullable=False), Column('description', String(500)) ) topic_xref = Table('topic_post_xref', db, Column('topic_id', Integer, ForeignKey('topics.topic_id'), nullable=False), Column('is_primary', Boolean, nullable=False), Column('post_id', Integer, ForeignKey('posts.post_id'), nullable=False) ) comments = Table('comments', db, Column('comment_id', Integer, primary_key=True), Column('user_id', Integer, ForeignKey('users.user_id'), nullable=False), Column('post_id', Integer, ForeignKey('posts.post_id'), nullable=False), Column('datetime', DateTime, nullable=False), Column('parent_comment_id', Integer, ForeignKey('comments.comment_id')), Column('subject', String(500)), Column('body', String), ) def create_tables(): """creates all application tables, used when the application is run for the first time.""" users.create() blogs.create() posts.create() topics.create() topic_xref.create() comments.create()myghty-1.1/examples/zblog/lib/zblog/domain/0000755000175000017500000000000010501065764017763 5ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/domain/__init__.py0000644000175000017500000000000010501064125022050 0ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/domain/actions.py0000644000175000017500000000575510501064125021777 0ustar malexmalex"""a simple access-control ruleset. Each class defines a different kind of user "action", along with a function containing all necessary logic to determine if the action is allowed for the currently logged in user (if any). These objects can be used both to disable/hide links and buttons, as well as when the message comes into a controller to determine whether to perform the action or produce a 403 Forbidden exception.""" import re, string import zblog from zblog.domain.blog import * actions = {} class ActionSingleton(type): def __call__(self): try: name = string.lower(re.sub(r"(\w)([A-Z])", r"\1_\2", self.__name__)) return actions[name] except KeyError: return actions.setdefault(name, type.__call__(self, name)) class Action(object): __metaclass__ = ActionSingleton def __init__(self, name): self.name = name def access(self, user, **kwargs): return False class Bootstrap(Action): """defines the initial config file creation and database creation. only allowed if the application has no configuration defined.""" def access(self, user, **kwargs): return zblog.need_config class Login(Action): def access(self, user, **kwargs): return True class Logout(Action): def access(self, user, **kwargs): return True class Manage(Action): def access(self, user, **kwargs): return user is not None and user.is_administrator() class AdminUsers(Action): def access(self, user, **kwargs): return user is not None and user.is_administrator() class LoggedIn(Action): def access(self, user, **kwargs): return user is not None class EditBlog(Action): def access(self, user, blog_id=None, **kwargs): if user is None: return False if blog_id and not user.is_administrator(): blog = Blog.mapper.get(blog_id) if blog is None: return False return user.id==blog.owner.id else: return user.is_administrator() class CreatePost(Action): def access(self, user, blog_id=None, blog=None, **kwargs): if user is None: return False if blog is None: blog = Blog.mapper.get(blog_id) if blog is None: return False return blog.owner.id==user.id or user.is_administrator() class EditPost(Action): def access(self, user, post_id=None, post=None, **kwargs): if user is None: return False if post is None: post = Post.mapper.get(post_id) if post is None: return False return post.user.id==user.id or user.is_administrator() or user.id==post.blog.owner.id class Register(Action): def access(self, user, **kwargs): return user is None class CreateComment(Action): def access(self, user, **kwargs): return user is not None class DeleteComment(Action): pass myghty-1.1/examples/zblog/lib/zblog/domain/blog.py0000644000175000017500000000176410501064125021256 0ustar malexmalex__all__ = ['Blog', 'Post', 'Topic', 'TopicAssociation', 'Comment'] import datetime class Blog(object): def __init__(self, owner=None): self.owner = owner class Post(object): def __init__(self, user=None, headline=None, summary=None): self.user = user self.datetime = datetime.datetime.today() self.headline = headline self.summary = summary self.comments = [] self.comment_count = 0 class Topic(object): def __init__(self, keyword=None, description=None): self.keyword = keyword self.description = description class TopicAssociation(object): def __init__(self, post=None, topic=None, is_primary=False): self.post = post self.topic = topic self.is_primary = is_primary class Comment(object): def __init__(self, subject=None, body=None): self.subject = subject self.datetime = datetime.datetime.today() self.body = body myghty-1.1/examples/zblog/lib/zblog/domain/user.py0000644000175000017500000000222110501064125021276 0ustar malexmalex"""user.py - handles user login and validation""" import random, string try: from crypt import crypt except: try: from fcrypt import crypt except: raise "Need fcrypt module on non-Unix platform: http://home.clear.net.nz/pages/c.evans/sw/" administrator = 'admin' user = 'user' groups = [user, administrator] def cryptpw(password, salt=None): if salt is None: salt = string.join([chr(random.randint(ord('a'), ord('z'))), chr(random.randint(ord('a'), ord('z')))],'') return crypt(password, salt) def checkpw(password, dbpw): return cryptpw(password, dbpw[:2]) == dbpw class User(object): def __init__(self, name=None, fullname=None, password=None, group=user): self.name = name self.fullname = fullname self.password = password self.group = group def is_administrator(self): return self.group == administrator def _set_password(self, password): if password: self.crypt_password=cryptpw(password) password = property(lambda s: None, _set_password) def checkpw(self, password): return checkpw(password, self.crypt_password)myghty-1.1/examples/zblog/lib/zblog/util/0000755000175000017500000000000010501065764017471 5ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/util/__init__.py0000644000175000017500000000000010501064125021556 0ustar malexmalexmyghty-1.1/examples/zblog/lib/zblog/util/form.py0000644000175000017500000002752610501064125021010 0ustar malexmalex""" a set of classes that represent an HTML form, including its field names, the values of those fields corresponding to their application setting as well as the value pulled from an HTTP request, and validation rules for the fields. "hierarchical forms" can be created as well, allowing a form to be grouped into "subforms", which may live on the same HTML page or across several HTML pages. """ from myghty.util import * import inspect, datetime, types class FormElement(object): def __init__(self, name, **params): self.name = name self.description = '' self.errors = [] self.isvalid = None def set_form(self, form):pass def set_request(self, req, validate = True):pass def append_error(self, error): self.errors.append(error) self.isvalid=False def clear(self):pass def is_valid(self):return self.isvalid def unvalidate(self):pass class Form(FormElement): def __init__(self, name, elements, **params): FormElement.__init__(self, name) self.success = [] self.elements = OrderedDict() for elem in elements: self.elements[elem.name] = elem elem.set_form(self) def append_success(self, success): self.success.append(success) def set_form(self, form):pass def append(self, element): self.elements[element.name] = element element.set_form(self) def __iter__(self): return iter(self.elements.values()) def get(self, key, *args, **kwargs): return self.elements.get(key, *args, **kwargs) def __getitem__(self, key): return self.elements[key] def __setitem__(self, key, elem): self.elements[elem.name] = elem elem.set_form(self) def unvalidate(self): self.isvalid = None for elem in self.elements.values(): elem.unvalidate() def set_request(self, req, validate = True): self.isvalid = True for elem in self.elements.values(): if elem is None: continue elem.set_request(req, validate) if not elem.is_valid(): self.isvalid = False def _fieldname(self, formfield): return formfield.name def reflect_from(self, object): for elem in self.elements.values(): elem.reflect_from(object) def reflect_to(self, object): for elem in self.elements.values(): elem.reflect_to(object) def clear(self): for elem in self.elements.values(): elem.clear() class SubForm(Form): def _fieldname(self, formfield): return self.name + "_" + formfield.name class FormField(FormElement): def __init__(self, name, description=None, required=False, default=None, data=None, attribute=False, setattribute=False, getattribute=False, disabled=False): FormElement.__init__(self, name) if description is None: self.description = name else: self.description = description self.required = required if setattribute is not False: self.setattribute = setattribute elif attribute is not False: self.setattribute = attribute else: self.setattribute = name if getattribute is not False: self.getattribute = getattribute elif attribute is not False: self.getattribute = attribute else: self.getattribute = name self.default = default self.value = default self.displayname = None self.data = data self.disabled = disabled def clear(self): self.value = self.default def set_form(self, form): self.displayname = form._fieldname(self) def _set_value(self, value): self._value = value if value is None: self.display = '' else: self.display = str(value) value = property(lambda s: s._value, lambda s, v: s._set_value(v)) def reflect_to(self, object): if self.disabled or self.setattribute is None: return elif callable(self.setattribute): self.setattribute(self, object) else: attrs = self.setattribute.split('.') o = object for attr in attrs[0:-2]: o = getattr(o, attr) setattr(o, attrs[-1], self.value) def reflect_from(self, object): if self.getattribute is None: return elif callable(self.getattribute): self.value = self.getattribute(object) else: for attr in self.getattribute.split('.'): try: object = getattr(object, attr) except AttributeError: return self.value = object def set_request(self, request, validate = True): """sets the request for this form. if validate is True, also validates the value.""" if request.has_key(self.displayname): self.display = request[self.displayname] if validate: if self.required and (not request.has_key(self.displayname) or not self.display): self.isvalid = False self.append_error('required field "%s" missing' % self.description) else: self.isvalid = True self.value = self.display def unvalidate(self): """resets the isvalid state of this form to None""" self.isvalid = None self.errors = [] class IntFormField(FormField): def _set_value(self, value): if not value: self._value = None self.display = '' else: try: self._value = int(value) except ValueError: self.append_error('field "%s" must be an integer number' % self.description) self.isvalid = False self.display = str(value) class CompoundFormField(SubForm): """ a SubForm that acts like a single formfield in that it contains a single value, but also contains subfields that comprise elements of that value. examples: a date with year, month, day fields, corresponding to a date object more exotic examples: a credit card field with ccnumber, ccexpiration fields corresponding to a CreditCard object, an address field with multiple subfields corresopnding to an Address object, etc. """ def _set_value(self, value): SubForm._set_value(self, value) self.set_compound_values(value) def set_compound_values(self, value): pass class CCFormField(FormField): def _set_value(self, value): if not self.luhn_mod_ten(value): self.isvalid = False self.append_error('invalid credit card number') else: self._value = value def luhn_mod_ten(self, ccnumber): """ checks to make sure that the card passes a luhn mod-10 checksum. courtesy: http://aspn.activestate.com """ sum = 0 num_digits = len(ccnumber) oddeven = num_digits & 1 for count in range(0, num_digits): digit = int(ccnumber[count]) if not (( count & 1 ) ^ oddeven ): digit = digit * 2 if digit > 9: digit = digit - 9 sum = sum + digit return ( (sum % 10) == 0 ) class DateFormField(CompoundFormField): # TODO: fixy def __init__(self, name, fields, yeardeltas = range(-5, 5), required = False, *args, **params): elements = {} for field in fields: if field == 'ampm': elements[field] = FormField(field, required = required) else: elements[field] = IntFormField(field, required = required) for key in ['year', 'month', 'day']: if elements.has_key(key): self.hasdate = True break else: self.hasdate = False for key in ['hour', 'minute', 'second']: if elements.has_key(key): self.hastime = True break else: self.hastime = False assert (self.hasdate or self.hastime) CompoundFormField.__init__(self, name, elements.values(), **params) self.required = required if self.hasdate: today = datetime.datetime.today() year = today.year self.yearrange = [year + i for i in yeardeltas] def set_compound_values(self, value): if self.hasdate: self.elements['year'].value = value.year self.elements['month'].value = value.month self.elements['day'].value = value.day if self.hastime: if self.elements.has_key('ampm'): v = value.hour % 12 if v == 0: v = 12 self.elements['hour'].value = v else: self.elements['hour'].value = value.hour self.elements['minute'].value = value.minute self.elements['second'].value = value.second if self.elements.has_key('ampm'): self.elements['ampm'].value = (value.hour > 12 and 'pm' or 'am') def set_request(self, request, validate = True): CompoundFormField.set_request(self, request, validate) if validate: for elem in self.elements.values(): if elem.is_valid() is False: self.append_error('field "%s": %s' % (self.description, string.join([elem.errors]))) return args = {} has_value = False if self.hasdate: dummy = datetime.date.min for key in ['year', 'month', 'day']: if self.elements.has_key(key): args[key] = self.elements[key].currentvalue if args[key] is not None: has_value = True else: args[key] = getattr(dummy, key) if self.hastime: dummy = datetime.time.min for key in ['hour', 'minute', 'second']: if self.elements.has_key(key): args[key] = self.elements[key].currentvalue if args[key] is not None: has_value = True else: args[key] = getattr(dummy, key) if self.elements.has_key('ampm'): if self.elements['ampm'] == 'pm': args['hour'] += 12 elif args['hour'] == 12: args['hour'] = 0 if not has_value: self.request_value = None return try: if self.hasdate and self.hastime: value = datetime.datetime(**args) elif self.hasdate: value = datetime.date(**args) else: value = datetime.time(**args) self.request_value = value self.currentvalue = value self.isvalid = True except TypeError, e: self.isvalid = False self.currentvalue = None self.append_error('field "%s" does not contain a valid date/time (%s)' % (self.description, str(e))) except ValueError, e: self.isvalid = False self.currentvalue = None self.append_error('field "%s" does not contain a valid date/time (%s)' % (self.description, str(e))) myghty-1.1/examples/zblog/README0000644000175000017500000001374610501064125015512 0ustar malexmalexZblog Demo Application Yes, its a blog. Probably not a blog app you'd want to use anywhere, although it could be taken as a starting point to building a more featured application. Its pretty much a plain white interface that lets you create users, blogs, blog posts, and comments, with everything reading fresh from the database, i.e. no file generation or caching or anything. The point of it is to illustrate a fairly powerful application layout, which includes Ajax functionality, basic SQLAlchemy integration, a rudimentary security framework, and form-handling paradigms that work across regular and Ajax-style layouts. ARCHITECTURE ------------ The blog application is basically providing a management interface to the following graph of objects: Users | +----Blogs <-TopicAssociations-> Topics | +----Posts | +---Comments<--+ + | | | +-------+ The application is laid out something like this: Server Application Persistence ------ --------------------- ------------ bin/server.py ---> lib/zblog/__init__.py --> lib/zblog/database/tables.py config/server_config.py config/config.py lib/zblog/database/mappers.py | v View Controller Domain ----- ----------- -------- htdocs/*.myt <-- lib/zblog/controller/__init__.py <-- lib/zblog/domain/user.py components/*.myc lib/zblog/controller/front.py lib/zblog/domain/blog.py lib/zblog/controller/*.py lib/zblog/domain/actions.py lib/zblog/util/form.py Not included above are a few modules in the "bootstrap" category, which are involved with auto-configuring the application and creating its database tables when it is run for the first time. Server ------ The server modules deal with running the application. bin/server.py is a basic Myghty HTTPServerHandler runner, which reads the interpreter configuration from config/server_config.py. Other runners, such as WSGI, Paste, mod_python, etc. can be used instead with server_config.py. Application ----------- The base application includes the zblog __init__.py module, which provides some initialization functions and the ability for other packages to add themselves to the overall startup process, and the config.py configuration which includes application-wide config. This file is generated by the initial "bootstrap" runner. Persistence ----------- tables.py defines the SQLAlchemy table metadata for all tables, which is also used to create the tables, and mappers.py defines the datamapping relationships from the tables to the domain objects. Domain ------ this is the center of the app, user.py includes a User object as well as some basic crypt() functions for passwords, and blog.py includes all the rest of the objects represented. actions.py defines a set of Action objects which correspond to specific user actions and states. A single method access() provides a yes/no response indicating if a particular action is allowed. Controller ---------- The base controller class is in __init__.py, and an application-wide "front" controller is in front.py. All HTTP requests, including Ajax calls, go through the front controller. The front controller then locates the actual controller to be called, examines its security attributes which may also include an actions.Action object, and either passes along or raises a 403 return code. The security attributes are set via a decorator, and this decorator is the primary method of marking a particular controller method as publically accessible. The front controller uses its own "resolver context" in order to locate the secondary controller object; this context is configured in config/server_config.py. Each controller then handles its appropriate section, and makes heavy use of the Form objects defined in util/form.py in order to send information about form status to views. View ---- All the Myghty templates, HTML, JS, CSS, including the Myghtyjax files. Form handling is largely assisted by the components/components.myc package which provides formfield rendering methods that integrate with the util/form.py objects. RUNNING THE DEMO ---------------- 1. We're using decorators, so you should use Python 2.4 or greater. 2. insure that SQLAlchemy is installed (http://www.sqlalchemy.org/). 3. insure that this latest source tree of Myghty is either installed or in the PYTHONPATH. 4. insure that either pysqlite, psygopg/Postgres, mysqldb/mysql is installed (or Oracle/cx_Oracle, but not tested as much). 5. run the server: python ./bin/server.py 6. Go to the URL: http://localhost:8080/ 7. A "bootstrap" page should appear. enter an admin username, password, and set up database parameters (foolproof defaults are provided for SQLite), then press "connect" to try connecting to the database. 8. when connect is successful, press "create config file". this should bounce to a textual screen of table create statements. it also writes a config file in ./config/config.py . 9. to login, press "login" and login as the administrator user. 10. create a few test users, and a few "blogs". Then maybe a few posts inside the blogs. 11. the console where the server.py is running should be dumping all the HTTP requests as well as all the SQL calls and their results. The logging of SQL calls can be switched off in the config/config.py file. 12. watch what happens when you create a user, then create some blogs for that user. then delete the user. All the blogs, posts, comments owned by that user (and comment replies) get deleted too ! amazing. the mapping relationships are completely defined within lib/zblog/database/mappers.py. myghty-1.1/ez_setup.py0000644000175000017500000001714510501064125014104 0ustar malexmalex#!python """Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import sys DEFAULT_VERSION = "0.6c1" DEFAULT_URL = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3] md5_data = { 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', } import sys, os def _validate_md5(egg_name, data): if egg_name in md5_data: from md5 import md5 digest = md5(data).hexdigest() if digest != md5_data[egg_name]: print >>sys.stderr, ( "md5 validation of %s failed! (Possible download problem?)" % egg_name ) sys.exit(2) return data def use_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15 ): """Automatically find/download setuptools and make it available on sys.path `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where setuptools will be downloaded, if it is not already available. If `download_delay` is specified, it should be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. """ try: import setuptools if setuptools.__version__ == '0.0.1': print >>sys.stderr, ( "You have an obsolete version of setuptools installed. Please\n" "remove it from your system entirely before rerunning this script." ) sys.exit(2) except ImportError: egg = download_setuptools(version, download_base, to_dir, download_delay) sys.path.insert(0, egg) import setuptools; setuptools.bootstrap_install_from = egg import pkg_resources try: pkg_resources.require("setuptools>="+version) except pkg_resources.VersionConflict: # XXX could we install in a subprocess here? print >>sys.stderr, ( "The required version of setuptools (>=%s) is not available, and\n" "can't be installed while this script is running. Please install\n" " a more recent version first." ) % version sys.exit(2) def download_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay = 15 ): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. """ import urllib2, shutil egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) url = download_base + egg_name saveto = os.path.join(to_dir, egg_name) src = dst = None if not os.path.exists(saveto): # Avoid repeated downloads try: from distutils import log if delay: log.warn(""" --------------------------------------------------------------------------- This script requires setuptools version %s to run (even to display help). I will attempt to download it for you (from %s), but you may need to enable firewall access for this script first. I will start the download in %d seconds. (Note: if this machine does not have network access, please obtain the file %s and place it in this directory before rerunning this script.) ---------------------------------------------------------------------------""", version, download_base, delay, url ); from time import sleep; sleep(delay) log.warn("Downloading %s", url) src = urllib2.urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = _validate_md5(egg_name, src.read()) dst = open(saveto,"wb"); dst.write(data) finally: if src: src.close() if dst: dst.close() return os.path.realpath(saveto) def main(argv, version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" try: import setuptools except ImportError: import tempfile, shutil tmpdir = tempfile.mkdtemp(prefix="easy_install-") try: egg = download_setuptools(version, to_dir=tmpdir, delay=0) sys.path.insert(0,egg) from setuptools.command.easy_install import main return main(list(argv)+[egg]) # we're done here finally: shutil.rmtree(tmpdir) else: if setuptools.__version__ == '0.0.1': # tell the user to uninstall obsolete version use_setuptools(version) req = "setuptools>="+version import pkg_resources try: pkg_resources.require(req) except pkg_resources.VersionConflict: try: from setuptools.command.easy_install import main except ImportError: from easy_install import main main(list(argv)+[download_setuptools(delay=0)]) sys.exit(0) # try to force an exit else: if argv: from setuptools.command.easy_install import main main(argv) else: print "Setuptools version",version,"or greater has been installed." print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' def update_md5(filenames): """Update our built-in md5 registry""" import re from md5 import md5 for name in filenames: base = os.path.basename(name) f = open(name,'rb') md5_data[base] = md5(f.read()).hexdigest() f.close() data = [" %r: %r,\n" % it for it in md5_data.items()] data.sort() repl = "".join(data) import inspect srcfile = inspect.getsourcefile(sys.modules[__name__]) f = open(srcfile, 'rb'); src = f.read(); f.close() match = re.search("\nmd5_data = {\n([^}]+)}", src) if not match: print >>sys.stderr, "Internal error!" sys.exit(2) src = src[:match.start(1)] + repl + src[match.end(1):] f = open(srcfile,'w') f.write(src) f.close() if __name__=='__main__': if len(sys.argv)>2 and sys.argv[1]=='--md5update': update_md5(sys.argv[2:]) else: main(sys.argv[1:]) myghty-1.1/lib/0000755000175000017500000000000010501065764012444 5ustar malexmalexmyghty-1.1/lib/myghty/0000755000175000017500000000000010501065764013765 5ustar malexmalexmyghty-1.1/lib/myghty/__init__.py0000644000175000017500000000064110501064120016060 0ustar malexmalex# $Id: __init__.py 2013 2005-12-31 03:19:39Z zzzeek $ # __init__.py # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # import posixpath as unixpath # insert the "http" path into the search path for this module __path__.insert(0, unixpath.join(__path__[0], 'http')) myghty-1.1/lib/myghty/args.py0000644000175000017500000000444710501064120015265 0ustar malexmalex# $Id: args.py 2013 2005-12-31 03:19:39Z zzzeek $ # args.py - component argument descriptor classes for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # __all__ = ['ComponentArg', 'RequestArg', 'SubRequestArg', 'LocalArg', 'DynamicArg'] import myghty.exception as exception class ComponentArg: def __init__(self, name, required = True, default = None, linenumber = None): self.name = name if default is None: self.required = required elif not default: self.default = None self.required = True else: self.default = default self.required = False self.linenumber = linenumber def do_get_arg(self, request, **params): raise NotImplementedError() def __repr__(self): return "%s(%s, required = %s)" % ( self.__class__.__name__, repr(self.name), repr(self.required), ) def get_arg(self, request, dictionary, **params): try: dictionary[self.name] = self.do_get_arg(request, **params) except KeyError: if self.required: raise exception.MissingArgument("required %s argument %s not found" % (self.__class__.type, self.name)) class RequestArg(ComponentArg): type = "request" def do_get_arg(self, request, **params): return request.root_request_args[self.name] class SubRequestArg(ComponentArg): type = "subrequest" def do_get_arg(self, request, **params): return request.request_args[self.name] class LocalArg(ComponentArg): type = "component" def do_get_arg(self, request, **params): return params[self.name] class DynamicArg(ComponentArg): type = "dynamic" def do_get_arg(self, request, **params): def find(request, params): yield params while request is not None: yield request.request_args request = request.parent_request for d in find(request, params): if d.has_key(self.name): return d[self.name] raise KeyError(self.name) myghty-1.1/lib/myghty/buffer.py0000644000175000017500000000353510501064120015577 0ustar malexmalex# $Id: buffer.py 2133 2006-09-06 18:52:56Z dairiki $ # buffer.py - string buffering functions for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # """Buffer is an output handling object which corresponds to the Python file object interface.""" from myghty.util import * import string class BufferDecorator(object): """allows flexible combinations of buffers. """ def __init__(self, buffer): self.buffer = buffer def __getattr__(self, name): return getattr(self.buffer, name) def __repr__(self): return "BufferDecorator, enclosing %s." % repr(self.buffer) class FunctionBuffer(BufferDecorator): def __init__(self, func): self.func = func def write(self, s): self.func(s) class LinePrinter(BufferDecorator): def write(self, s): self.buffer.write(s + "\n") def writelines(self, list): self.buffer.writelines([s + "\n" for s in list]) class LogFormatter(BufferDecorator): def __init__(self, buffer, identifier, id_threads = False, autoflush = True): BufferDecorator.__init__(self, buffer) self.identifier = identifier self.id_threads = id_threads self.autoflush = autoflush def _formatline(self, s): if self.id_threads: return "[%s] [pid:%d tid:%d] %s" % (self.identifier, pid(), thread_id(), string.rstrip(s)) else: return "[%s] %s" % (self.identifier, string.rstrip(s)) def write(self, s): self.buffer.write(self._formatline(s)) if self.autoflush: self.flush() def writelines(self, lines): for line in lines: self.buffer.write(self._formatline(line)) myghty-1.1/lib/myghty/cache.py0000644000175000017500000001071110501064120015363 0ustar malexmalex# $Id: cache.py 2062 2006-05-03 05:40:30Z dairiki $ # cache.py - cache API and implementation for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # from myghty.container import * from myghty.util import * import myghty.buffer import re class Cache: """a front-end to the containment API implementing a data cache. This is a per-request object and is not synchronized against other threads or processes. (the containment API it talks to, is).""" def __init__(self, component, type = None, container_class = None , debug_file = None, **params): self.params = CacheArgs() self.params.set_params(**params) self.type = type if component is not None: self.namespace = component.id else: self.namespace = None self.dict = {} # attach a ContainerContext to the component, which will hold # onto the namespacemanager(s) corresponding to that component. # the scope of the context is the same as that of the component, # so if a component is unused and gets garbage collected, its # namespacemanagers will get collected as well. if component is not None: if hasattr(component, "_container_context"): self.context = component._container_context else: self.context = ContainerContext(log_file = debug_file) component._container_context = self.context if type is not None: self.container_class = container_registry(type, 'Container') elif container_class is None: if params.setdefault('data_dir', None) is not None: # DBMContainer is definitely faster than FileContainer # for caching self.container_class = DBMContainer else: self.container_class = MemoryContainer else: self.container_class = container_class def _get_container(self, key, **params): if not self.dict.has_key(key): self.dict[key] = self._create_container(self.namespace, key, **params) return self.dict[key] def _create_container(self, namespace, key, type = None, container_class = None, **params): if container_class is None: if type is not None: container_class = container_registry(type, 'Container') else: container_class = self.container_class cparams = self.params.get_params(**params) return container_class(context = self.context, namespace = namespace, key = key, **cparams) def set_container(self, key, **params): self.dict[key] = self._create_container(self.namespace, key, **params) return self.dict[key] def get_container(self, key, **params): return self._get_container(key, **params) def get_value(self, key, **params): return self._get_container(key, **params).get_value() def set_value(self, key, value, **params): self._get_container(key, **params).set_value(value) def remove_value(self, key): if self.dict.has_key(key): self.dict[key].clear_value() del self.dict[key] def clear(self): nm = self.get_container(None).get_namespace_manager() nm.remove() self.dict = {} # public dict interface def __getitem__(self, key): return self.get_value(key) def __contains__(self, key): container = self._get_container(key) return container.has_current_value() def has_key(self, key): container = self._get_container(key) return container.has_current_value() def __delitem__(self, key): self.remove_value(key) def __setitem__(self, key, value): self.set_value(key, value) class CacheArgs(PrefixArgs): def __init__(self, **params): PrefixArgs.__init__(self, 'cache_') self.set_prefix_params(**params) def clone(self, **params): p = self.get_params(**params) arg = CacheArgs() arg.params = p return arg def get_cache(self, component, **params): return Cache(component, **self.get_params(**params)) myghty-1.1/lib/myghty/compiler.py0000644000175000017500000005427510501064120016147 0ustar malexmalex# $Id: compiler.py 2133 2006-09-06 18:52:56Z dairiki $ # compiler.py - compiles parsed files into a parse tree for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # Original Perl code and documentation copyright (c) 1998-2003 by Jonathan Swartz. # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # """ Middle tier between a Lexer and an ObjectGenerator. receives parse events from Lexer and constructs a parse tree, then calls an ObjectGenerator to generate an object file from the parse tree. the parse tree structure is language-neutral and contains no python code. Compiler is currently stateful and is not thread-safe. the clone() method can be used to create copies of this object for use in multiple threads. """ from myghty.util import * from myghty import exception import myghty.lexer import myghty.objgen as objgen import myghty.args as args import string, re, sys, warnings # blocks that can only be in the top level top_level_only_block = dict(map (lambda x:[x ,True], ['once', 'shared', 'threadonce'])) # valid flags in use valid_comp_flag = dict(map (lambda x:[x ,True], ['inherit'])) # map of block names pointing to compile method names BLOCKS = { 'python' : 'python_block', } argscopes = { 'request' : args.RequestArg, 'subrequest' : args.SubRequestArg, 'dynamic' : args.DynamicArg, 'component' : args.LocalArg } class Compiler: # compiled component modules must have this defined as their # _MAGIC_NUMBER or an IncompatibleCompiler exception will be raised MAGIC_NUMBER = 5 class BlockFlyweight: """an object that represents a construct in the original source file. maintains a reference to a list of source file items and uses a context to act upon the list entry in question, so that very few objects need to be created """ def __init__(self):pass def get_line(self, context): return context.entry[2] def get_line_number(self, context): return context.entry[1] def append(self, list, line, linenumber): list.append([self, linenumber, line]) def accept_visitor(self, visitor, context):raise NotImplementedError class CodeBlock(BlockFlyweight): """represents a <%python> section""" def get_lines(self, context): block = context.entry[2] lines = re.split(r"\n", block) return lines def accept_visitor(self, visitor, context): visitor.visit_code_block(self, context) class Substitution(BlockFlyweight): """represents a <% %> line """ def accept_visitor(self, visitor, context): visitor.visit_substitution(self, context) def append(self, list, line, escape, linenumber): list.append([self, linenumber, line, escape]) def get_escapes(self, context): return context.entry[3] class TextLine(BlockFlyweight): """ represents a line of plain text""" def accept_visitor(self, visitor, context): visitor.visit_text_line(self, context) class CodeSingleLine(BlockFlyweight): """ represents a % line """ def accept_visitor(self, visitor, context): visitor.visit_code_single_line(self, context) class ComponentCall(BlockFlyweight): """ represents a component call without content <& &>""" def append(self, list, component, args, linenumber): list.append([self, linenumber, component, args]) def get_component(self, context): return context.entry[2] def get_args(self, context): return context.entry[3] def accept_visitor(self, visitor, context): visitor.visit_component_call(self, context) class ComponentContentCall(ComponentCall): """ represents a component call with content <&| &>""" def accept_visitor(self, visitor, context): visitor.visit_component_content_call(self, context) class ComponentContentCallEnd(ComponentCall): """ represents a component call with content end tag """ def accept_visitor(self, visitor, context): visitor.visit_component_content_call_end(self, context) class Closure(BlockFlyweight): def append(self, list, compiled, linenumber): list.append([self, linenumber, compiled]) def get_compiled(self, context): return context.entry[2] def accept_visitor(self, visitor, context): visitor.visit_closure_block(self, context) class BlockFlyweightIterator: """an iterator that loops through an array of BlockFlyweight references and their associated data, and acts as the context to send to their get() methods""" def __init__(self, list): self.list = list self.index = -1 self.entry = None def __iter__(self): return self def next(self): self.index += 1 if self.index >= len(self.list): raise StopIteration() self.entry = self.list[self.index] return self.entry[0] # singleton instances of BlockFlyweights codeblock = CodeBlock() textline = TextLine() codesingleline = CodeSingleLine() substituteline = Substitution() componentcall = ComponentCall() componentcontentcall = ComponentContentCall() componentcontentcallend = ComponentContentCallEnd() closure = Closure() def __init__(self, python_pre_processor = None, python_post_processor = None, text_post_processor = None, lexer = None, generator = None, default_escape_flags = [], use_source_line_numbers = True, allow_globals = [], disable_unicode = False, **params): self.current_compile = None if lexer: self.lexer = lexer else: self.lexer = myghty.lexer.Lexer(**params) if generator: self.generator = generator else: self.generator = objgen.PythonGenerator() self.default_escape_flags = default_escape_flags self.python_pre_processor = python_pre_processor self.python_post_processor = python_post_processor self.text_post_processor = text_post_processor self.use_source_line_numbers = use_source_line_numbers self.allow_globals = allow_globals self.disable_unicode = bool(disable_unicode) def get_object_id(self): """identification string placed at the top of compiled files""" return "%s|%s|%s" % (self.lexer.get_object_id(), "Myghty.Compiler", repr(self.get_magic_number())) def get_magic_number(self): return Compiler.MAGIC_NUMBER, {'disable_unicode': self.disable_unicode} def clone(self, **params): """creates a clone of this Compiler. allow the Prototype pattern to be used in creating compilers for use in other threads.""" params['lexer'] = self.lexer.clone() params['generator'] = self.generator.clone() clone = ConstructorClone(self, **params) return clone.clone() class Compiled: "Stores information about the currently compiling block" def __init__(self, compiler, fileid, name, in_main = True, parent = None, block_type = None, flags = None): self.compiler = compiler self.lexer = compiler.lexer self.parent = parent self.in_main = in_main self.block_type = block_type self.fileid = fileid self.name = name self.component_content_call_stack = [] self.in_block = None self.args = OrderedDict() if flags is not None: self.flags = OrderedDict([flags]) else: self.flags = OrderedDict() self.attr = OrderedDict() self.named_blocks = {'def': {}, 'method': {}} self.blocks = { 'cleanup' : [], 'filter' : [], 'init' : [], 'once' : [], 'threadonce' : [], 'shared' : [], 'body' : [] } self.start_blocks = { 'args' : -1, 'flags' : -1, 'attr': - 1, } self.encoding = None def accept_visitor(self, visitor): if self.in_main: visitor.visit_component_block(self) elif self.block_type == 'def': visitor.visit_def_block(self) elif self.block_type == 'method': visitor.visit_method_block(self) def add_argument(self, name, default, linenumber, scope): if self.args.has_key(name): self.lexer.raise_syntax_error("component argument %s already defined" % name) try: self.args[name] = argscopes[scope or 'component'](name, default = default, linenumber = linenumber) except KeyError: self.lexer.raise_syntax_error("Unknown %%args scope '%s'" % scope) def has_named_block(self, block_type, name): if block_type is None: for value in self.named_blocks.values(): if value.has_key(name): return True else: return False else: return self.named_blocks.has_key(block_type) and self.named_blocks[block_type].has_key(name) def get_named_block(self, block_type, name): if block_type is None: for value in self.named_blocks.values(): if value.has_key(name): return value[name] else: raise KeyError(name) else: return self.named_blocks[block_type][name] def add_named_block(self, block_type, name, block): self.named_blocks[block_type][name] = block def get_named_blocks(self, block_type = None): if block_type is not None: return self.named_blocks[block_type].values() else: list = [] for d in self.named_blocks.values(): list += d.values() return list def get_body(self): return self.blocks['body'] def get_block_list(self, block_type): return self.blocks[block_type] def block_has_code(self, block_type): return len(self.blocks[block_type]) > 0 def get_block_iterator(self, block_type): block = self.get_block_list(block_type) return Compiler.BlockFlyweightIterator(block) def set_encoding(self, encoding): if self.parent is not None: self.parent.set_encoding(encoding, ignore_if_already_set) elif self.encoding: self.lexer.raise_syntax_error( 'multiple file encoding specifiers') else: self.encoding = encoding def get_encoding(self): if self.parent is not None: return self.parent.get_encoding() return self.encoding def __str__(self): return self._to_string() def _to_string(self, indent = ''): """dumps out the structure attempting to recreate the original source file, more or less.""" list = [] if self.encoding is not None: list.append("# -*- encoding: %s -*-" % self.encoding) list.append("<%args>") for arg in self.args.keys(): list.append(arg + " = " + repr(self.args[arg])) list.append("") for key in ('once', 'init', 'shared', 'body', 'cleanup'): block = self.get_block_list(key) if key != 'body': list.append("<%%%s>" % key) else: list.append("# body code begin") iter = Compiler.BlockFlyweightIterator(block) for code in iter: list.append(code.get_line(iter)) if key != 'body': list.append("\n" % key) else: list.append("# body code end\n") for blockname, blocks in self.named_blocks.iteritems(): for key, value in self.def_blocks.iteritems(): ind = indent + "\t" list.append(ind + "<%%%s %s>" % (blockname, key)) list.append(value._to_string(ind + "\t")) list.append(ind + "" % (blockname, key)) return indent + string.join(list, "\n" + indent) def compile(self, source, name, file, input_file = None): """compiles a source file. source - a string representing the source of the file name - a name to give to the compiled object file - a file object that the compiled output will be streamed to.""" self.current_compile = Compiler.Compiled(compiler = self, fileid = name, name = 'top_level') # Preprocess the source. The preprocessor routine is handed a # reference to the entire source. if self.python_pre_processor: try: source = self.python_pre_processor(source) except Exception, e: raise exception.Compiler(e.args) self.lexer.lex(source, name, self, input_file) self.compiled_component(file) def start_block(self, block_type, attributes = None): """called by Lexer to indicate a <%block> tag """ compile = self.current_compile if top_level_only_block.has_key(block_type) and not compile.in_main: self.lexer.raise_syntax_error("Cannot define a %s section inside a method or subcomponent" % block_type) if compile.start_blocks.has_key(block_type): compile.start_blocks[block_type] = self.lexer.line_number() if compile.in_block: self.lexer.raise_syntax_error("Cannot nest a %s inside a %s block" % (block_type, c.in_block)) compile.in_block = block_type def end_block(self, block_type): """called by Lexer to indicate a tag""" compile = self.current_compile if compile.in_block != block_type: self.lexer.raise_syntax_error("End of %s encountered while in %s block" % (block_type, compile.in_block)) compile.in_block = None def text_block(self, block, **params): Compiler.textline.append(self.current_compile.get_block_list('body'), block, self.lexer.line_number()) # comment - discard def doc_block(self, **params):pass def magic_encoding_comment(self, encoding, **params): """A magic -*- encoding: foo -*- style comment.""" assert encoding and type(encoding) is str self.current_compile.set_encoding(encoding) def post_process_text(self, code): if self.text_post_processor: try: return self.text_post_processor(code) except Exception, e: raise exception.Compiler(e.args) else: return code def post_process_python(self, code): if self.python_post_processor: try: return self.python_post_processor(code) except Exception, e: raise exception.Compiler(e.args) else: return code def raw_block(self, **params): # see if we have a method corresponding to this block and call it if so if BLOCKS.has_key(params['block_type']): method = BLOCKS[params['block_type']] # call method dynamically return getattr(self, method)(**params) else: Compiler.codeblock.append(self.current_compile.get_block_list(params['block_type']), params['block'], self.lexer.line_number()) def variable_declaration(self, block_type, name, default, scope): """Inserts a variable declaration from the C<< <%args> >> section into the component.""" if block_type != 'args': self.lexer.raise_syntax_error("Variable Declaration called inside a %s block" % block_type) self.current_compile.add_argument(name, default, self.lexer.line_number(), scope) def key_value_pair(self, block_type, key, value): try : dict = {"flags": self.current_compile.flags, "attr": self.current_compile.attr}[block_type] if dict.has_key(key): self.lexer.raise_syntax_error("%s %s already defined" % (key, block_type)) dict[key] = value if block_type == "flags" and key == "encoding": #warnings.warn( # "The 'encoding' flag has been deprecated, " # "use an encoding magic comment instead.", # category=DeprecationWarning) self.current_compile.set_encoding(eval(value, {})) except KeyError: exception.Compiler("key_value_pair called inside a %s block" % block_type) def python_line(self, line): Compiler.codesingleline.append(self.current_compile.get_body(), line, self.lexer.line_number()) def python_block(self, block, **params): Compiler.codeblock.append(self.current_compile.get_body(), block, self.lexer.line_number()) def substitution(self, line, escape): if escape or len(self.default_escape_flags): dict = OrderedDict() if escape: escapes = re.split(r"\s*,\s*", escape) for esc in escapes: dict[esc] = 1 if self.default_escape_flags and not dict.has_key('n'): for esc in self.default_escape_flags: dict[esc] = 1 if dict.has_key('n'): del dict['n'] escapes = dict.keys() else: escapes = None Compiler.substituteline.append(self.current_compile.get_body(), line, escapes, self.lexer.line_number()) def start_component(self): pass def end_component(self): compile = self.current_compile if len(compile.component_content_call_stack) > 0: self.lexer.raise_syntax_error("Not enough component-with-content ending tags found") def compiled_component(self, file): self.generator.generate(self, self.current_compile, file) def start_named_block(self, block_type, name, attributes): compile = self.current_compile # Error if defining one def or method inside another if block_type != 'closure' and not compile.in_main: self.lexer.raise_syntax_error("Cannot define a %s block inside a method or subcomponent" % block_type) if re.search(r"[^.\w-]", name): self.lexer.raise_syntax_error("Invalid %s name: %s" % (block_type, name)) # error if we have a named block with this name already if block_type != 'closure' and compile.has_named_block(None, name): block = compile.get_named_block(None, name) self.lexer.raise_syntax_error("%s block %s already exists" % (block.block_type, name)) # make a new compile object, set it to be our current newcompile = Compiler.Compiled(compiler = self, parent = self.current_compile, in_main = False, fileid = self.current_compile.fileid, name = name, block_type = block_type, flags = attributes) if block_type != 'closure': compile.add_named_block(block_type, name, newcompile) else: Compiler.closure.append(self.current_compile.get_body(), newcompile, self.lexer.line_number()) self.current_compile = newcompile def end_named_block(self, block_type): # reset current compile object to be its parent if self.current_compile.parent: self.current_compile = self.current_compile.parent else: raise exception.Compiler("end_named_block found no parent block") def _generic_component_call(self, call, iscontent = False, isclose = False): if isclose: try: call = self.current_compile.component_content_call_stack.pop() except IndexError: self.lexer.raise_syntax_error("found component with content ending tag but no beginning tag") else: call = string.strip(call) (component, args) = (re.split(r",", call, 1) + [''])[0:2] component = string.strip(component) args = string.strip(args) if not iscontent: Compiler.componentcall.append(self.current_compile.get_body(), component, args, self.lexer.line_number()) else: if not isclose: self.current_compile.component_content_call_stack.append(call) Compiler.componentcontentcall.append(self.current_compile.get_body(), component, args, self.lexer.line_number()) else: Compiler.componentcontentcallend.append(self.current_compile.get_body(), component, args, self.lexer.line_number()) def component_call(self, call): self._generic_component_call(call, False, False) def component_content_call(self, call): self._generic_component_call(call, True, False) def component_content_call_end(self): self._generic_component_call(None, True, True) def get_encoding(self): return self.current_compile.get_encoding() myghty-1.1/lib/myghty/component.py0000644000175000017500000005145010501064120016327 0ustar malexmalex# $Id: component.py 2133 2006-09-06 18:52:56Z dairiki $ # component.py - component base classes for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # Original Perl code and documentation copyright (c) 1998-2003 by Jonathan Swartz. # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # """the component package defines the Component class and several subclasses. FileComponent serves as the base class for template style-components, and ModuleComponent the base for module-based components.""" from myghty import exception from myghty.util import * from myghty.args import * import myghty.request import myghty.csource import weakref, string import posixpath as unixpath import sys, inspect, types __all__ = ['Component', 'ModuleComponent', 'HTTPModuleComponent', 'FileComponent', 'SubComponent'] argtypes = ( (RequestArg, True, 'required_request_args'), (RequestArg, False, 'request_args'), (SubRequestArg, True, 'required_subrequest_args'), (SubRequestArg, False, 'subrequest_args'), (LocalArg, True, 'required_args'), (LocalArg, False, 'args'), (DynamicArg, True, 'required_dynamic_args'), (DynamicArg, False, 'dynamic_args'), ) class Component(object): """Base class for all components. the primary method is the run() method which executes the Component in the context of a particular request. A Component instance is usually cached in memory based on its component ID and can execute many requests simultaneously.""" def __init__(self, interpreter, component_source, **params): """initializes a new Component. this method is called by the Interpreter.""" self.filter = None self.owner = self.parent_component = None self.flags = {} self.attr = {} self.arguments = [] self.component_source = component_source self.is_method = self.is_file = self.is_subcomponent = self.is_module = False self.size = 0 self.use_count = 0 self.interpreter = interpreter self.threads_init = ThreadLocal() id = property(lambda self: self.component_source.id, doc="unique identifier for this component") source_id = property(lambda self: self.component_source.id, doc="unique identifier for the ComponentSource of this component") name = property(lambda self: self.component_source.name, doc="name of this component") dir_name = property(lambda self: self.component_source.dir_name, doc="relative directory name of the component") path = property(lambda self: self.component_source.path, doc="relative full path of the component") file = property(lambda self: self.component_source.file_path, doc="the actual filename of the component, if one exists") def _parentattr(self): if self.parent_component is not None: return self.parent_component.attributes else: return None attributes = property(lambda self: InheritedDict(self.attr, lambda: self._parentattr())) def component_init(self): """initializes the component after construction. Calls the do_component_init() method before setting up the component's arguments.""" self.do_component_init() self._convert_component_args() def _convert_component_args(self): # the args system has been overhauled to be just a list of # ComponentArgs objects, which come straight from the compiled # component file. # but try to do some compatibility, for older compiled components # and module components that have all the separate ***_args arrays # attached to them. we generate the _args if it doesnt exist, # and then recreate the ***_args arrays in all cases. for argtype in argtypes: self.arguments += [argtype[0](arg, argtype[1]) for arg in getattr(self, argtype[2], [])] # TODO: try to use property() for this setattr(self, argtype[2], [arg.name for arg in self.arguments if arg.__class__ == argtype[0] and arg.required == argtype[1]]) def do_component_init(self): """overridden by subclasses to perform per-instance initialization steps.""" raise NotImplementedError() def run(self, request, **params): """runs this component in the context of the given request. **params are the arguments sent to the component, which become ARGS in the context of the component's runtime environment.""" self.use_count += 1 compparams = {} for arg in self.arguments: arg.get_arg(request, compparams, **params) compparams.update(request.global_args) if not self.threads_init.exists(): self.do_thread_init() self.threads_init.assign(True) return self.do_run_component(m = request, ARGS = params, **compparams) def has_filter(self): """returns True if this component defines a <%filter> section.""" return self.filter != None def _init_filter_func(self, d = None): if self.flags.has_key('trim'): # the 'trim' flag implies that the component is # buffered as well self.flags['autoflush'] = False try: func = {'left':string.lstrip, 'right':string.rstrip, 'both':string.strip}[self.flags['trim']] if d is not None: return lambda f, **kwargs: d(func(f), **kwargs) else: return lambda f, **kwargs: func(f) except KeyError, e: raise exception.Interpreter("invalid 'trim' argument '%s'" % e) elif d is not None: return d def get_flag(self, key, inherit = False): """returns a flag defines in this component's <%flags> section.""" if self.flags.has_key(key): return self.flags[key] elif inherit: parent = self.parent_component if parent is not None: return parent.get_flag(key, True) return None def get_sub_component(self, name): """returns a SubComponent of the given name, represented within the source of this Component.""" return None def do_run_component(self, m, ARGS, **params): """overridden by subclasses to provide the main execution body of the component.""" raise NotImplementedError() def do_thread_init(self): """overridden by subclasses to provide a per-thread initialization routine.""" pass def locate_inherited_method(self, method_name): """returns a method SubComponent of the given name, represented either within the source of this Component or within the source of an inherited Component, i.e. the inheritance hierarchy will be searched for the approrpriate method call.""" raise NotImplementedError() def call_method(self, method_name, **params): """calls a method SubComponent of the given name on this component. This amounts to locating it via locate_inherited_method, retrieving the current request via request.instance(), and then calling execute_component on that request. See also m.comp("component:methodname") """ method_component = self.locate_inherited_method(method_name) return myghty.request.instance().execute_component(method_component, args = params, base_component = self) def scall_method(self, method_name, **params): """same as call_method, but returns the component output as a string See also m.scomp("component:methodname") """ method_component = self.locate_inherited_method(method_name) buffer = StringIO() myghty.request.instance().execute_component(method_component, args = params, base_component = self, store = buffer) return buffer.getvalue() def use_auto_flush(self): """returns True if this component defines the "autoflush" flag as True, or if an inherited component defines this flag.""" return self.get_flag('autoflush', inherit = True) # these are all deprecated def get_attribute(self, key, inherit = True): return self.attributes(key) def set_attribute(self, key, value): self.attributes(key, value) def get_attributes(self): return self.attributes def attribute_exists(self, key): return self.attributes.has_key(key) def get_owner(self): return self.owner def get_parent_component(self): return self.parent_component def get_id(self): return self.id def get_name(self): return self.name def get_source_id(self): return self.source_id def get_file(self): return self.file def get_path(self): return self.path def get_dir_name(self): return self.dir_name def is_method_component(self): return self.is_method def is_module_component(self): return self.is_module def is_file_component(self): return self.is_file def is_sub_component(self): return self.is_subcomponent class FileComponent(Component): "a component that corresponds to a Myghty template file" def __init__(self, interpreter, component_source, **params): Component.__init__(self, interpreter, component_source, **params) self.defs = None self.methods = None self.inherit_path = None self.inherit_start_path = None self.is_file = True self.size = component_source.modulesize self.component_init() if self.uses_thread_local(): self.thread_local = ThreadLocal(creator = self.thread_local_initializer) if self.uses_request_local(): self.request_local = weakref.WeakKeyDictionary() self.subcomponents = self.defs.copy() self.subcomponents.update(self.methods) self._determine_inheritance() def uses_thread_local(self):raise NotImplementedError() def uses_request_local(self):raise NotImplementedError() def is_file_component(self):return True def get_sub_component(self, name): if self.subcomponents.has_key(name): return self.subcomponents[name] else: return None def _call_dynamic(self, key, m, ARGS, **params): """handles components with %threadlocal and/or %requestlocal sections""" context = None if self.uses_thread_local(): if self.uses_request_local(): if self.request_local.has_key(m): context = self.request_local[m] else: initializer = self.thread_local.get(self, m, ARGS, **params) context = initializer(self, m, ARGS, **params) self.request_local[m] = context else: context = self.thread_local.get(self, m, ARGS, **params) elif self.uses_request_local(): if self.request_local.has_key(m): context = self.request_local[m] else: context = self.request_local_initializer(self, m, ARGS, **params) self.request_local[m] = context if context is None: raise exception.Error("dynamic method context not found") else: return context[key](m, ARGS, **params) def locate_inherited_method(self, method_name): component = self while component: if component.methods.has_key(method_name): return component.methods[method_name] component = component.parent_component raise exception.MethodNotFound("no such method '%s' found" % method_name) def _determine_inheritance(self): interpreter = self.interpreter source = self.component_source if self.flags.has_key('inherit'): if self.flags['inherit'] is None: pass else: self.inherit_path = unixpath.normpath(unixpath.join(source.dir_name, self.flags['inherit'])) elif interpreter.use_auto_handlers: if source.name == interpreter.auto_handler_name: if source.dir_name and source.dir_name != '/': self.inherit_start_path = unixpath.dirname(source.dir_name) else: # we are the autohandler in the root directory pass else: self.inherit_start_path = self.component_source.dir_name def _parent_component(self): """returns the parent component of this component, taking into account the component's inherit flag as well as the interpreter's autohandler properties""" try: request = myghty.request.instance() # optional - cache results of this function on the request #try: # return request.attributes['_parent_comp_%s' % self.id] #except KeyError: pass except KeyError: # no current request for whatever reason, so call # load() off the interpreter request = None if self.inherit_path is not None: if request is not None: component = request.load_component(self.inherit_path, resolver_context = 'inherit') else: component = self.interpreter.load(self.inherit_path, resolver_context = 'inherit') elif self.inherit_start_path is not None: if request is not None: component = request.load_component(unixpath.join(self.inherit_start_path, self.interpreter.auto_handler_name), search_upwards = True, raise_error = False, resolver_context = 'inherit') else: component = self.interpreter.load(unixpath.join(self.inherit_start_path, self.interpreter.auto_handler_name), search_upwards = True, raise_error = False, resolver_context = 'inherit') else: component = None #if request is not None: # request.attributes['_parent_comp_%s' % self.id] = component return component parent_component = property(_parent_component, lambda s, p: None) class SubComponent(Component): """a component that corresponds to a <%def> or <%method> tag inside a file-based component.""" def __init__(self, name, owner, is_method, **params): Component.__init__(self, owner.interpreter, owner.component_source, **params) self.__name = name self.__owner = owner self.is_method = is_method self.is_subcomponent = True self.component_init() def _call_dynamic(self, key, m, ARGS, **params): return self.owner._call_dynamic(key, m, ARGS, **params) id = property(lambda self: self.owner.id + ":" + self.name) parent_component = property(lambda self: self.owner.parent_component, lambda s, p:None) name = property(lambda self: self.__name, lambda s,p:None) owner = property(lambda self: self.__owner, lambda s,p:None) def get_sub_component(self, name):return self.owner.get_sub_component(name) def locate_inherited_method(self, method_name):return self.owner.locate_inherited_method(method_name) def call_method(self, *args, **params):return self.owner.call_method(*args, **params) def scall_method(self, *args, **params):return self.owner.scall_method(*args, **params) class ModuleComponent(Component): """A component that is a regular Python class inside of a plain Python module.""" def __init__(self, interp, component_source, owner = None, do_init = True): Component.__init__(self, interp, component_source) self.__name = self.__class__.__name__ self.is_method = True self.creationtime = component_source.last_modified self.methods = Registry() if owner is None: self.__owner = self else: self.__owner = owner if do_init: self.component_init() owner = property(lambda self: self.__owner, lambda s, p:None) name = property(lambda self: self.__name, lambda s, p:None) def _inspect_args(self, function): csource = self.component_source argspec = inspect.getargspec(function) argnames = argspec[0] or [] defaultvalues = argspec[3] or [] d = dict([(a, True) for a in self.interpreter.compiler().allow_globals + ['m', 'ARGS', 'self']]) (self.required_args, self.args) = ( [a for a in argnames[0:len(argnames) - len(defaultvalues)] if not d.has_key(a)], [a for a in argnames[len(argnames) - len(defaultvalues):] if not d.has_key(a)] ) self._allargs = argnames self._has_params = argspec[2] is not None def component_init(self): self.do_component_init() # if no new-style argument list has been set up if len(self.arguments) == 0: # and no old-style argument lists have been added for argtype in argtypes: if hasattr(self, argtype[2]): break else: # then inspect the do_component_init function and figure out the args # that way. self._inspect_args(self.do_run_component.im_func) # tell base class to set up arguments list self._convert_component_args() def do_component_init(self):pass def do_run_component(self, m, ARGS, **params):raise NotImplementedError() def is_module_component(self):return True def _find_method(self, method_name): if self.__class__.__name__ == method_name: return self return self.interpreter.load_module_component(self.__module__ + ":" + method_name) def locate_inherited_method(self, method_name): if self.owner is self: return self.methods.get(method_name, createfunc = lambda: ModuleComponent._find_method(self, method_name)) else: return self.owner.locate_inherited_method(method_name) class HTTPModuleComponent(ModuleComponent): """A ModuleComponent that contains methods specific to a generic HTTP request.""" def do_post(self, m, r, **params): # 'method not allowed', unless this is overridden m.abort(405) def do_get(self, m, r, **params): # 'method not allowed', unless this is overridden m.abort(405) def do_run_component(self, m, r, **params): methods = { 'GET': HTTPModuleComponent.do_get, 'POST': HTTPModuleComponent.do_post, } try: f = methods[r.method] return f(self, m, r, **params) except KeyError: m.abort(405) class FunctionComponent(ModuleComponent): """a module component that "wraps" a regular Python function or method, including introspection of its signature to automatically produce the required_args and args lists """ def do_component_init(self): csource = self.component_source self._inspect_args(csource.callable_) # if we are linked to a method off of an object instance, # see if the object instance has a do_component_init method and if the object instance # does not have a _component_init attribute. call do_component_init() and # set the _component_init attribute to True if self.component_source.has_method: target = self.component_source.callable_.im_self if hasattr(target, 'do_component_init') and not hasattr(target, '_component_init'): try: getattr(target, 'do_component_init')(self) finally: target._component_init = True def do_run_component(self, **params): if self._has_params: return self.component_source.callable_(**params) else: d = {} for a in self._allargs: if params.has_key(a): d[a] = params[a] return self.component_source.callable_(**d) def get_method_csource(self, name): if not self.component_source.has_method: return None obj = self.component_source.callable_.im_self meth = getattr(obj, name) return myghty.csource.ModuleComponentSource(module=self.component_source.module, callable_=meth, name="method:" + meth.im_func.__name__) def get_method(self, m, name): cs = self.get_method_csource(name) return m.interpreter.load_component(cs) myghty-1.1/lib/myghty/container.py0000644000175000017500000006210310501064120016304 0ustar malexmalex# $Id: container.py 2133 2006-09-06 18:52:56Z dairiki $ # container.py - file/memory data containment API and implementation for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # import os.path, re, string, time, weakref, sys from myghty.util import * from myghty import exception from myghty.synchronization import * import cPickle __all__ = ['NamespaceContext', 'ContainerContext', 'Container', 'MemoryContainer', 'DBMContainer', 'NamespaceManager', 'MemoryNamespaceManager', 'DBMNamespaceManager', 'FileContainer', 'FileNamespaceManager', 'CreationAbortedError', 'container_registry'] def container_registry(name, classtype): if name.startswith('ext:'): name = name[4:] modname = "myghty.ext." + name mod = getattr(__import__(modname).ext, name) else: mod = sys.modules[__name__] cname = string.capitalize(name) + classtype return getattr(mod, cname) class NamespaceContext: """initial context supplied to NamespaceManagers""" def __init__(self, log_file = None): self.log_file = log_file def debug(self, message, nsm, container = None): if self.log_file is not None: if container is not None: message = "[%s:%s:%s] %s\n" % (container.__class__.__name__, nsm.namespace, container.key, message) else: message = "[%s] %s\n" % (nsm.namespace, message) self.log_file.write(message) class NamespaceManager: """handles dictionary operations and locking for a namespace of values. the implementation for setting and retrieving the namespace data is handled by subclasses. acts as a service for a Container, which stores and retreives a particular key from the namespace, coupled with a "stored time" setting. NamespaceManager may be used alone, or may be privately managed by one or more Container objects. Container objects provide per-key services like automatic expiration and recreation of individual keys and can manange many types of NamespaceManagers for one or more particular namespaces simultaneously. the class supports locking relative to its name. many namespacemanagers within multiple threads or across multiple processes must read/write synchronize their access to the actual dictionary of data referenced by the name. """ def __init__(self, context , namespace, **params): # caution: this might create a circular reference # (which was giving me very weird gc() problems # in previous configurations) self.context = context self.namespace = namespace self.openers = 0 self.mutex = _threading.Lock() def do_acquire_read_lock(self): raise NotImplementedError() def do_release_read_lock(self): raise NotImplementedError() def do_acquire_write_lock(self, wait = True): raise NotImplementedError() def do_release_write_lock(self): raise NotImplementedError() def do_open(self, flags): raise NotImplementedError() def do_close(self): raise NotImplementedError() def do_remove(self): """removes this namespace from wherever it is stored""" raise NotImplementedError() def has_key(self, key): return self.__contains__(key) def __getitem__(self, key): raise NotImplementedError() def __setitem__(self, key, value): raise NotImplementedError() def __contains__(self, key): raise NotImplementedError() def __delitem__(self, key): raise NotImplementedError() def keys(self): raise NotImplementedError() def acquire_read_lock(self): """acquires a read lock for this namespace, and insures that the datasource has been opened for reading if it is not already opened. acquire/release supports reentrant/nested operation.""" self.do_acquire_read_lock() self.open('r', checkcount = True) def release_read_lock(self): """releases the read lock for this namespace, and possibly closes the datasource, if it was opened as a product of the read lock's acquire/release block. acquire/release supports reentrant/nested operation.""" self.close(checkcount = True) self.do_release_read_lock() def acquire_write_lock(self, wait = True): """acquires a write lock for this namespace, and insures that the datasource has been opened for writing if it is not already opened. acquire/release supports reentrant/nested operation.""" r = self.do_acquire_write_lock(wait) if (wait or r): self.open('c', checkcount = True) return r def release_write_lock(self): """releases the write lock for this namespace, and possibly closes the datasource, if it was opened as a product of the write lock's acquire/release block. acquire/release supports reentrant/nested operation.""" self.close(checkcount = True) self.do_release_write_lock() def open(self, flags, checkcount = False): """opens the datasource for this namespace. the checkcount flag indicates an "opened" counter should be checked for zero before performing the open operation, which is incremented by one regardless.""" self.mutex.acquire() try: if checkcount: if self.openers == 0: self.do_open(flags) self.openers += 1 else: self.do_open(flags) self.openers = 1 finally: self.mutex.release() def close(self, checkcount = False): """closes the datasource for this namespace. the checkcount flag indicates an "opened" counter should be checked for zero before performing the close operation, which is otherwise decremented by one.""" self.mutex.acquire() try: if checkcount: self.openers -= 1 if self.openers == 0: self.do_close() else: if self.openers > 0: self.do_close() self.openers = 0 finally: self.mutex.release() def remove(self): self.do_acquire_write_lock() try: self.close(checkcount = False) self.do_remove() finally: self.do_release_write_lock() def debug(self, message, container = None): self.context.debug(message, self, container) class ContainerContext(NamespaceContext): """initial context supplied to Containers. Keeps track of namespacemangers keyed off of namespace names and container types. also keeps namespacemanagers thread local for nsm instances that arent threadsafe (i.e. gdbm) """ def __init__(self, log_file = None): NamespaceContext.__init__(self, log_file) self.registry = {} def get_namespace_manager(self, namespace, container, **params): key = str(_thread.get_ident()) + "|" + container.__class__.__name__ + "|" + namespace try: return self.registry[key] except KeyError: return self.registry.setdefault(key, self.create_nsm(namespace, container, **params)) def create_nsm(self, namespace, container, **params): nsm = container.do_create_namespace_manager(context = self, namespace = namespace, **params) return nsm class Container: """represents a value, its stored time, and a value creation function corresponding to a particular key in a particular namespace. handles storage and retrieval of its value via a single NamespaceManager, as well as handling expiration times and an optional creation function that can create or recreate its value when needed. the Container performs locking operations on the NamespaceManager, including a pretty intricate one for get_value with a creation function, so its best not to pass a NamespaceManager that has been externally locked or open, as it stands currently (i hope to improve on this). Managing multiple Containers for a set of keys within a certain namespace allows management of multiple namespace implementations, expiration properties, and thread/process synchronization, on a per-key basis. """ def __init__(self, key, context, namespace, createfunc = None, expiretime = None, starttime = None, **params): """create a container that stores one cached object. createfunc - a function that will create the value. this function is called when value is None or expired. the createfunc call is also synchronized against any other threads or processes calling this cache. expiretime - time in seconds that the item expires. """ self.key = key self.createfunc = createfunc self.expiretime = expiretime self.starttime = starttime self.storedtime = -1 self.namespacemanager = context.get_namespace_manager(namespace, self, **params) self.do_init(**params) def acquire_read_lock(self): self.namespacemanager.acquire_read_lock() def release_read_lock(self): self.namespacemanager.release_read_lock() def acquire_write_lock(self, wait = True): return self.namespacemanager.acquire_write_lock(wait) def release_write_lock(self): self.namespacemanager.release_write_lock() def debug(self, message): self.namespacemanager.debug(message, self) def do_create_namespace_manager(self, context, namespace, **params): """subclasses should return a newly created instance of their corresponding NamespaceManager.""" raise NotImplementedError() def do_init(self, **params): """subclasses can perform general initialization. optional template method.""" pass def do_get_value(self): """retrieves the native stored value of this container, regardless of if its expired, or raise KeyError if no value is defined. optionally a template method.""" return self.namespacemanager[self.key] def do_set_value(self, value): """sets the raw value in this container. optionally a template method.""" self.namespacemanager[self.key] = value def do_clear_value(self): """clears the value of this container. subsequent do_get_value calls should raise KeyError. optionally a template method.""" if self.namespacemanager.has_key(self.key): del self.namespacemanager[self.key] def has_value(self): """returns true if the container has a value stored, regardless of it being expired or not. optionally a template method.""" self.acquire_read_lock() try: return self.namespacemanager.has_key(self.key) finally: self.release_read_lock() def lock_createfunc(self, wait = True): """required template method that locks this container's namespace and key to allow a single execution of the creation function.""" raise NotImplementedError() def unlock_createfunc(self): """required template method that unlocks this container's namespace and key when the creation function is complete.""" raise NotImplementedError() def can_have_value(self): """returns true if this container either has a non-expired value, or is capable of creating one via a creation function""" return self.has_current_value() or self.createfunc is not None def has_current_value(self): """returns true if this container has a non-expired value""" return self.has_value() and not self.is_expired() def stored_time(self): return self.storedtime def get_namespace_manager(self): return self.namespacemanager def get_all_namespaces(self): return self.namespacemanager.context._container_namespaces.values() def is_expired(self): """returns true if this container's value is expired, based on the last time get_value was called.""" return ( ( self.storedtime == -1 ) or ( self.starttime is not None and self.storedtime < self.starttime ) or ( self.expiretime is not None and time.time() >= self.expiretime + self.storedtime ) ) def get_value(self): """get_value performs a get with expiration checks on its namespacemanager. if a creation function is specified, a new value will be created if the existing value is nonexistent or has expired.""" self.acquire_read_lock() try: has_value = self.has_value() if has_value: [self.storedtime, value] = self.do_get_value() if not self.is_expired(): return value if not self.can_have_value(): raise KeyError(self.key) finally: self.release_read_lock() has_createlock = False if has_value: if not self.lock_createfunc(wait = False): self.debug("get_value returning old value while new one is created") return value else: self.debug("lock_creatfunc (didnt wait)") has_createlock = True if not has_createlock: self.debug("lock_createfunc (waiting)") self.lock_createfunc() self.debug("lock_createfunc (waited)") try: # see if someone created the value already self.acquire_read_lock() try: if self.has_value(): [self.storedtime, value] = self.do_get_value() if not self.is_expired(): return value finally: self.release_read_lock() self.debug("get_value creating new value") try: v = self.createfunc() except CreationAbortedError, e: raise self.set_value(v) return v finally: self.unlock_createfunc() self.debug("unlock_createfunc") def set_value(self, value): self.acquire_write_lock() try: self.storedtime = time.time() self.debug("set_value stored time %d" % self.storedtime) self.do_set_value([self.storedtime, value]) finally: self.release_write_lock() def clear_value(self): self.acquire_write_lock() try: self.debug("clear_value") self.do_clear_value() self.storedtime = -1 finally: self.release_write_lock() class CreationAbortedError(Exception): """a special exception that allows a creation function to abort what its doing""" def __init__(self, **params): self.params = params class MemoryNamespaceManager(NamespaceManager): namespaces = SyncDict(_threading.Lock(), {}) def __init__(self, context, namespace, **params): NamespaceManager.__init__(self, context, namespace, **params) self.lock = Synchronizer(identifier = "memorycontainer/namespacelock/%s" % self.namespace, use_files = False) self.dictionary = MemoryNamespaceManager.namespaces.get(self.namespace, lambda: {}) def do_acquire_read_lock(self): self.lock.acquire_read_lock() def do_release_read_lock(self): self.lock.release_read_lock() def do_acquire_write_lock(self, wait = True): return self.lock.acquire_write_lock(wait) def do_release_write_lock(self): self.lock.release_write_lock() # the open and close methods are totally overridden to eliminate # the unnecessary "open count" computation involved def open(self, *args, **params):pass def close(self, *args, **params):pass def __getitem__(self, key): return self.dictionary[key] def __contains__(self, key): return self.dictionary.__contains__(key) def has_key(self, key): return self.dictionary.__contains__(key) def __setitem__(self, key, value):self.dictionary[key] = value def __delitem__(self, key): del self.dictionary[key] def do_remove(self): self.dictionary.clear() def keys(self): return self.dictionary.keys() class MemoryContainer(Container): def do_init(self, **params): self.funclock = None def do_create_namespace_manager(self, context, namespace, **params): return MemoryNamespaceManager(context, namespace, **params) def lock_createfunc(self, wait = True): if self.funclock is None: self.funclock = NameLock(identifier = "memorycontainer/funclock/%s/%s" % (self.namespacemanager.namespace, self.key), reentrant = True) return self.funclock.acquire(wait) def unlock_createfunc(self): self.funclock.release() class DBMNamespaceManager(NamespaceManager): def __init__(self, context, namespace, dbmmodule = None, data_dir = None, dbm_dir = None, lock_dir = None, digest_filenames = True, **params): NamespaceManager.__init__(self, context, namespace, **params) if dbm_dir is not None: self.dbm_dir = dbm_dir elif data_dir is None: raise "data_dir or dbm_dir is required" else: self.dbm_dir = data_dir + "/container_dbm" if lock_dir is not None: self.lock_dir = lock_dir elif data_dir is None: raise "data_dir or lock_dir is required" else: self.lock_dir = data_dir + "/container_dbm_lock" if dbmmodule is None: import anydbm self.dbmmodule = anydbm else: self.dbmmodule = dbmmodule verify_directory(self.dbm_dir) verify_directory(self.lock_dir) self.dbm = None self.lock = Synchronizer(identifier = self.namespace, use_files = True, lock_dir = self.lock_dir, digest_filenames = digest_filenames) self.encpath = EncodedPath(root = self.dbm_dir, identifiers = [self.namespace], digest = digest_filenames, extension = '.dbm') self.file = self.encpath.path self.debug("data file %s" % self.file) self._checkfile() def file_exists(self, file): if os.access(file, os.F_OK): return True else: for ext in ('db', 'dat', 'pag', 'dir'): if os.access(file + os.extsep + ext, os.F_OK): return True return False def _checkfile(self): if not self.file_exists(self.file): g = self.dbmmodule.open(self.file, 'c') g.close() def get_filenames(self): list = [] if os.access(self.file, os.F_OK): list.append(self.file) for ext in ('pag', 'dir', 'db', 'dat'): if os.access(self.file + os.extsep + ext, os.F_OK): list.append(self.file + os.extsep + ext) return list def do_acquire_read_lock(self): self.lock.acquire_read_lock() def do_release_read_lock(self): self.lock.release_read_lock() def do_acquire_write_lock(self, wait = True): return self.lock.acquire_write_lock(wait) def do_release_write_lock(self): self.lock.release_write_lock() def do_open(self, flags): # caution: apparently gdbm handles arent threadsafe, they # are using flock(), and i would rather not have knowledge # of the "unlock" 'u' option just for that one dbm module. # therefore, neither is an individual instance of # this namespacemanager (of course, multiple nsm's # can exist for each thread). self.debug("opening dbm file %s" % self.file) try: self.dbm = self.dbmmodule.open(self.file, flags) except: self.encpath.verify_directory() self._checkfile() self.dbm = self.dbmmodule.open(self.file, flags) def do_close(self): if self.dbm is not None: self.debug("closing dbm file %s" % self.file) self.dbm.close() def do_remove(self): for f in self.get_filenames(): os.remove(f) def __getitem__(self, key): return cPickle.loads(self.dbm[key]) def __contains__(self, key): return self.dbm.has_key(key) def __setitem__(self, key, value): self.dbm[key] = cPickle.dumps(value, cPickle.HIGHEST_PROTOCOL) def __delitem__(self, key): del self.dbm[key] def keys(self): return self.dbm.keys() class DBMContainer(Container): def do_init(self, **params): self.funclock = None def do_create_namespace_manager(self, context, namespace, **params): return DBMNamespaceManager(context, namespace, **params) def lock_createfunc(self, wait = True): if self.funclock is None: self.funclock = Synchronizer(identifier = "dbmcontainer/funclock/%s" % self.namespacemanager.namespace, use_files = True, lock_dir = self.namespacemanager.lock_dir) return self.funclock.acquire_write_lock(wait) def unlock_createfunc(self): self.funclock.release_write_lock() DbmNamespaceManager = DBMNamespaceManager DbmContainer = DBMContainer class FileNamespaceManager(NamespaceManager): def __init__(self, context, namespace, data_dir = None, file_dir = None, lock_dir = None, digest_filenames = True, **params): NamespaceManager.__init__(self, context, namespace, **params) if file_dir is not None: self.file_dir = file_dir elif data_dir is None: raise "data_dir or file_dir is required" else: self.file_dir = data_dir + "/container_file" if lock_dir is not None: self.lock_dir = lock_dir elif data_dir is None: raise "data_dir or lock_dir is required" else: self.lock_dir = data_dir + "/container_file_lock" verify_directory(self.file_dir) verify_directory(self.lock_dir) self.lock = Synchronizer(identifier = self.namespace, use_files = True, lock_dir = self.lock_dir, digest_filenames = digest_filenames) self.file = EncodedPath(root = self.file_dir, identifiers = [self.namespace], digest = digest_filenames, extension = '.cache').path self.hash = {} self.debug("data file %s" % self.file) def file_exists(self, file): if os.access(file, os.F_OK): return True else: return False def do_acquire_read_lock(self): self.lock.acquire_read_lock() def do_release_read_lock(self): self.lock.release_read_lock() def do_acquire_write_lock(self, wait = True): return self.lock.acquire_write_lock(wait) def do_release_write_lock(self): self.lock.release_write_lock() def do_open(self, flags): if self.file_exists(self.file): fh = open(self.file, 'r') self.hash = cPickle.load(fh) fh.close() self.flags = flags def do_close(self): if self.flags is not None and (self.flags == 'c' or self.flags == 'w'): fh = open(self.file, 'w') cPickle.dump(self.hash, fh, cPickle.HIGHEST_PROTOCOL) fh.close() self.flags = None def do_remove(self): os.remove(self.file) self.hash = {} def __getitem__(self, key): return self.hash[key] def __contains__(self, key): return self.hash.has_key(key) def __setitem__(self, key, value): self.hash[key] = value def __delitem__(self, key): del self.hash[key] def keys(self): return self.hash.keys() class FileContainer(Container): def do_init(self, **params): self.funclock = None def do_create_namespace_manager(self, context, namespace, **params): return FileNamespaceManager(context, namespace, **params) def lock_createfunc(self, wait = True): if self.funclock is None: self.funclock = Synchronizer(identifier = "filecontainer/funclock/%s" % self.namespacemanager.namespace, use_files = True, lock_dir = self.namespacemanager.lock_dir) return self.funclock.acquire_write_lock(wait) def unlock_createfunc(self): self.funclock.release_write_lock() myghty-1.1/lib/myghty/csource.py0000644000175000017500000001742110501064120015770 0ustar malexmalex# $Id: csource.py 2133 2006-09-06 18:52:56Z dairiki $ # csource.py - component source objects for Myghty # Copyright (C) 2005 Michael Bayer mike_mp@zzzcomputing.com # Original Perl code and documentation copyright (c) 1998-2003 by Jonathan Swartz. # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # import posixpath as unixpath import time, re, os, stat, types, sys, string import __builtin__ as builtin import myghty.util as util import myghty.importer as importer import myghty.exception as exception class ComponentSource(object): """a time-sensitive descriptor object for a Myghty component.""" def __init__(self, id, last_modified = None ): "unique identifier of the component." self.id = id "for file components, the filename without path." self.name = "" "for file components, relative directory where the component lives." self.dir_name = "" "for file components, this is dir_name + name" self.path = "" "for file components, this is the real filesystem path of the component." self.file_path = "" "for file components, id of the path, i.e. the key in the component_root hash." self.path_id = "" "the module containing the actual code for the component." self.module = None "guesstimate of the size of the component. this is filled in by Interpreter." self.modulesize = 0 if last_modified: self.last_modified = last_modified else: self.last_modified = int(time.time()) def can_compile(self):return True def get_component_source_file(self):raise NotImplementedError() def get_component_source(self):raise NotImplementedError() def get_object_code(self, compiler, file): """compiles the source of this component using the given compiler, sending output to the given file""" compiler.compile( source = self.get_component_source(), name = self.id, file = file, input_file = self.file_path, ) class ModuleComponentSourceSingleton(type): """a metaclass for ModuleComponentSource which allows its constructor to cache the inspection results of the "arg" constructor parameter. """ def __call__(self, **kwargs): arg = kwargs.get('arg', None) arg_cache = kwargs.pop('arg_cache', None) use_static_source = kwargs.pop('use_static_source', False) if arg_cache is not None and arg is not None: try: mc = arg_cache[arg] except KeyError: mc = type.__call__(self, **kwargs) arg_cache[arg] = mc return mc.copy(use_static_source=use_static_source) else: return type.__call__(self, **kwargs) class ModuleComponentSource(ComponentSource): """represents a loadable module containing either a ModuleComponent class, a function, a class instance which is callable or a method on a class instance. """ __metaclass__ = ModuleComponentSourceSingleton def __init__(self, arg = None, module = None, objpath = None, name = None, callable_ = None, class_ = None, last_modified = None): if arg is not None: if isinstance(arg, str): (modname, objname) = arg.split(':') module = importer.module(modname) objpath = objname.split('.') if objpath is not None: arg = module for t in objpath: arg = getattr(arg, t) name = self.inspect_target(arg, objpath) if module is None: if self.class_ is not None: module = sys.modules[self.class_.__module__] elif self.has_method: module = importer.module(self.callable_.im_class.__module__) else: module = importer.module(self.callable_.__module__) else: self.class_ = class_ self.callable_ = callable_ self.has_method = callable_ is not None and (isinstance(arg, types.MethodType) or not isinstance(arg, types.FunctionType)) if last_modified is None: last_modified = importer.mod_time(module) ComponentSource.__init__(self, "module|%s:%s" % (module.__name__, name), last_modified = last_modified) self.module = module self.objpath = objpath self.name = name def reload(self, module): # the "objpath" is a list of tokens that allow us to traverse from the # module to the callable/class unit. if we dont have that, then we can't # reload dynamically. if self.objpath is None: return self.module = module arg = module for t in self.objpath: arg = getattr(arg, t) self.inspect_target(arg, self.objpath) def inspect_target(self, arg, objpath): self.has_method = False self.class_ = None self.callable_ = None if isinstance(arg, types.TypeType) or isinstance(arg, types.ClassType): self.class_ = arg name = "class:" + arg.__name__ elif isinstance(arg, types.MethodType): self.callable_ = arg if objpath is not None: name = "method:" + string.join(objpath, '_') else: name = "method:" + arg.im_func.__name__ self.has_method = True elif isinstance(arg, types.FunctionType): self.callable_ = arg if objpath is not None: name = "function:" + string.join(objpath, '_') else: name = "function:" + arg.__name__ elif callable(arg): arg = arg.__call__ self.callable_ = arg if objpath is not None: name = "callable:" + string.join(objpath, '_') else: name = "callable:" + arg.__class__.__name__ self.has_method = True else: raise "arg is " + repr(arg) return name def copy(self, use_static_source = False): if use_static_source: last_modified = self.last_modified else: last_modified = importer.modulemodtime(self.module) return ModuleComponentSource(module = self.module, objpath = self.objpath, callable_ = self.callable_, name=self.name, class_ = self.class_, last_modified = last_modified) def can_compile(self): return False class MemoryComponentSource(ComponentSource): def __init__(self, source, id = None, last_modified = None): if id is None: id = str(builtin.id(self)) ComponentSource.__init__(self, id, last_modified) self.source = source def get_component_source_file(self): return util.StringIO(self.source) def get_component_source(self): return self.source class FileComponentSource(ComponentSource): def __init__(self, id, path_id, path, file_path, last_modified=None): if last_modified is None: last_modified = os.stat(file_path)[stat.ST_MTIME] ComponentSource.__init__(self, id, last_modified) self.file_path = file_path self.path = path self.path_id = path_id (self.dir_name, self.name) = unixpath.split(path) def get_component_source_file(self): return open(self.file_path) def get_component_source(self): return self.get_component_source_file().read() myghty-1.1/lib/myghty/escapes.py0000644000175000017500000001123410501064120015744 0ustar malexmalex# $Id: escapes.py 2133 2006-09-06 18:52:56Z dairiki $ # escapes.py - string escaping functions for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # Original Perl code and documentation copyright (c) 1998-2003 by Jonathan Swartz. # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # import re, cgi, urllib, htmlentitydefs, codecs from StringIO import StringIO xml_escapes = { '&' : '&', '>' : '>', '<' : '<', '"' : '"', # also " in html-only "'" : ''' # also ' in html-only } # XXX: " is valid in HTML and XML # ' is not valid HTML, but is valid XML def html_escape(string): return cgi.escape(string, True) def xml_escape(string): return re.sub(r'([&<"\'>])', lambda m: xml_escapes[m.group()], string) def url_escape(string): # convert into a list of octets string = string.encode("utf8") return urllib.quote_plus(string) def url_unescape(string): text = urllib.unquote_plus(string) if not is_ascii_str(text): text = text.decode("utf8") return text _ASCII_re = re.compile(r'\A[\x00-\x7f]*\Z') def is_ascii_str(text): return isinstance(text, str) and _ASCII_re.match(text) ################################################################ class XMLEntityEscaper(object): def __init__(self, codepoint2name, name2codepoint): self.codepoint2entity = dict([(c, u'&%s;' % n) for c,n in codepoint2name.iteritems()]) self.name2codepoint = name2codepoint def escape_entities(self, text): """Replace characters with their character entity references. Only characters corresponding to a named entity are replaced. """ return unicode(text).translate(self.codepoint2entity) def __escape(self, m): codepoint = ord(m.group()) try: return self.codepoint2entity[codepoint] except (KeyError, IndexError): return '&#x%X;' % codepoint __escapable = re.compile(r'["&<>]|[^\x00-\x7f]') def escape(self, text): """Replace characters with their character references. Replace characters by their named entity references. Non-ASCII characters, if they do not have a named entity reference, are replaced by numerical character references. The return value is guaranteed to be ASCII. """ return self.__escapable.sub(self.__escape, unicode(text) ).encode('ascii') # XXX: This regexp will not match all valid XML entity names__. # (It punts on details involving involving CombiningChars and Extenders.) # # .. __: http://www.w3.org/TR/2000/REC-xml-20001006#NT-EntityRef __characterrefs = re.compile(r'''& (?: \#(\d+) | \#x([\da-f]+) | ( (?!\d) [:\w] [-.:\w]+ ) ) ;''', re.X | re.UNICODE) def __unescape(self, m): dval, hval, name = m.groups() if dval: codepoint = int(dval) elif hval: codepoint = int(hval, 16) else: codepoint = self.name2codepoint.get(name, 0xfffd) # U+FFFD = "REPLACEMENT CHARACTER" if codepoint < 128: return chr(codepoint) return unichr(codepoint) def unescape(self, text): """Unescape character references. All character references (both entity references and numerical character references) are unescaped. """ return self.__characterrefs.sub(self.__unescape, text) _html_entities_escaper = XMLEntityEscaper(htmlentitydefs.codepoint2name, htmlentitydefs.name2codepoint) html_entities_escape = _html_entities_escaper.escape_entities html_entities_unescape = _html_entities_escaper.unescape def htmlentityreplace_errors(ex): """An encoding error handler. This python `codecs`_ error handler replaces unencodable characters with HTML entities, or, if no HTML entity exists for the character, XML character references. >>> u'The cost was \u20ac12.'.encode('latin1', 'htmlentityreplace') 'The cost was €12.' """ if isinstance(ex, UnicodeEncodeError): # Handle encoding errors bad_text = ex.object[ex.start:ex.end] text = _html_entities_escaper.escape(bad_text) return (unicode(text), ex.end) raise ex codecs.register_error('htmlentityreplace', htmlentityreplace_errors) myghty-1.1/lib/myghty/exception.py0000644000175000017500000005770310501064120016332 0ustar malexmalex# $Id: exception.py 2136 2006-09-08 19:46:55Z dairiki $ # exception.py - exception classes for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # Original Perl code and documentation copyright (c) 1998-2003 by Jonathan Swartz. # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # import string, sys, re, traceback, os from myghty.util import * # get the directory of myghty, to filter it out of friendly stack traces import myghty MYGHTY_DIR = os.path.normcase(os.path.dirname(myghty.__file__)) class Error(Exception): """generic exception class""" def __init__(self, message=None, wrapped=None, *args, **params): self.wrapped = wrapped if message is None: if self.wrapped is None: self.msg = self.__class__.__name__ else: self.msg = "%s(%s): %s" % ( self.__class__.__name__, self.wrapped.__class__.__name__, self.wrapped) else: self.msg = self.__class__.__name__ + ": " + message self.trace_records = [] self.lead_rec = None self.reversefiles = {} if self.wrapped is not None and isinstance(self.wrapped, SyntaxError): self.lead_rec = Error.TBTraceLine(Error.SimpleTraceLine(self.wrapped.text, self.wrapped.lineno, self.wrapped.filename)) self.ispythonsyntax = True else: self.ispythonsyntax = False Exception.__init__(self, *args, **params) def initTraceback(self, interpreter = None): """ call this right before each re-raise of the exception""" # at the moment, initTraceback only needs to be called once, so # return if we already got trace records if len(self.trace_records): return # extract stack info from the exception throw: (type, value, trcback) = sys.exc_info() self.raw_excinfo = (type, value, trcback) rawrecords = traceback.extract_tb(trcback) # add to that, stack info from the current stack frame rawrecords = traceback.extract_stack() + rawrecords # if a lead record was programmatically hardcoded, init that # (it is probably a SyntaxTraceLine from Lexer) # and dont change it if self.lead_rec is not None: self.lead_rec.init_reverse_file(self, interpreter) set_lead_rec = False else: set_lead_rec = True recs = [] # create TBTraceLines out of the stack frames in an attempt to sort out # compiled templatelines, myghty library lines, and non-myghty python lines for rec in rawrecords: rec = Error.TBTraceLine(Error.SimpleTraceLine(rec[3], rec[1], rec[0])) rec.init_reverse_file(self, interpreter) if not rec.is_myghty_lib() and set_lead_rec: self.lead_rec = rec recs.insert(0, rec) self.trace_records += recs class CodeLine: """represents a line of code in a python source file""" def __init__(self, linenum, line, encoding=None): self.linenum = linenum if line is None: self.line = 'None' else: self.line = _EncodedLine(string.rstrip(line), encoding) class TraceLine: """represents a trace line in a stack trace""" def __init__(self): self.codeline = None self.code = None self.file = None self.original = None def init_reverse_file(self, parent, interpreter):raise NotImplementedError() def is_myghty_lib(self): "returns True if this line corresponds to package in the myghty distribution" return self.has_file() and os.path.commonprefix([MYGHTY_DIR, os.path.normcase(self.file)]) == MYGHTY_DIR def get_lines(self, span = 3):raise NotImplementedError() def get_file_lines(self, f, linenum, span): i = 0 lines = [] encoding = self.get_source_encoding() while True: l = f.readline() i += 1 if i in range(linenum - span, linenum + span + 1): lines.append(Error.CodeLine(i, l, encoding)) if i == linenum + span: break f.close() return lines def has_file(self): return self.file is not None and os.access(self.file, os.F_OK) def get_source_encoding(self): raise NotImplementedError class SyntaxTraceLine(TraceLine): """a traceline generated by Lexer to mark a pre-python syntax error""" def __init__(self, code, line, name, source, file, source_encoding=None): self.code = code self.line = line self.name = name self.file = file self.source = source self.init = False self.original = self self.codeline = self.line if source_encoding is None: source_encoding = sys.getdefaultencoding() self.source_encoding = source_encoding def init_reverse_file(self, parent, interpreter): pass def get_lines(self, span = 3): f = StringIO(self.source) return self.get_file_lines(f, self.line, span) def get_source_encoding(self): return self.source_encoding class SimpleTraceLine(TraceLine): """a traceline for a regular python file""" def __init__(self, code, line, file): self.__code = code self.line = line self.codeline = self.line self.original = self self.file = file self.compiled_record = None def init_reverse_file(self, parent, interpreter): pass def _get_code(self): if self.__code is None: return self.get_lines(span = 0)[0].line else: return self.__code code = property(_get_code) def get_lines(self, span = 3): if self.compiled_record is not None: f = self.compiled_record.get_compiled_source() elif not self.has_file(): return [Error.CodeLine(self.line, self.__code)] #FIXME: encoding else: f = file(self.file) return self.get_file_lines(f, self.line, span) def get_source_encoding(self): if self.compiled_record is not None: f = self.compiled_record.get_compiled_source() elif not self.has_file(): return sys.getdefaultencoding() # FIXME: this is wrong else: f = file(self.file) return _parse_encoding(f) class TBTraceLine(TraceLine): """a wrapper for a simpletraceline that attempts to map its python string or file to an originating myghty template string or file, with line numbers cross-linked between them.""" def __init__(self, original): self.original = original def init_reverse_file(self, parent, interpreter): original = self.original file = original.file self.ismyghtylib = None if parent.reversefiles.has_key(file): self.reversefile = parent.reversefiles[file] elif interpreter is not None and interpreter.reverse_lookup.has_key(file): comprec = interpreter.reverse_lookup[file] self.reversefile = Error.ReverseFile(interpreter, compiled_record = comprec) parent.reversefiles[file] = self.reversefile else: self.reversefile = None if self.reversefile is not None: self.codeline = self.reversefile.get_line_number(original.codeline) self.original.compiled_record = self.reversefile.compiled_record if self.codeline is None: # if we are a compiled template and the line number is within the # area beyond user-defined code, then we are a myghtylib traceline self.ismyghtylib = True else: self.codeline = None if self.codeline is None: self.codeline = original.codeline self.reversefile = None def is_myghty_lib(self): if self.ismyghtylib is not None: return self.ismyghtylib else: return Error.TraceLine.is_myghty_lib(self) def _get_code(self): if self.reversefile is not None: return self.get_lines(span = 0)[0].line else: return self.original.code code = property(_get_code, lambda s,p:None) def _get_file(self): if ( self.reversefile is not None and self.reversefile.compiled_record.csource.file_path is not None): return self.reversefile.compiled_record.csource.file_path else: return self.original.file file = property(_get_file, lambda s,p:None) def get_lines(self, span = 3): if self.reversefile is not None: return self.get_file_lines(self.reversefile.get_source(), self.codeline, span) else: #return [Error.CodeLine(self.codeline, self.original.get_code())] return self.original.get_lines(span) def get_source_encoding(self): return self.original.get_source_encoding() class ReverseFile: """represents a python source string or file, a pointer back to the myghty template file or string that originated it, and a cross reference of all line numbers in the python file that map back to the template file.""" def __init__(self, interpreter, compiled_record): self.sourcemap = [] self.compiled_record = compiled_record self.isvalid = False f = compiled_record.get_compiled_source() try: line = f.readline() match = re.match(r"# File: (\S+) CompilerID: (.*?) Timestamp: (.*?)", line) if not match: return (self.path, self.compilerid, self.timestamp) = \ (match.group(1), match.group(2), match.group(3) ) self.sourcemap.append(None) state = 0 linecounter = 0 for line in f: if state == 0: self.sourcemap.append(None) match = re.match(r"\s*#\s*BEGIN BLOCK (\w+)", line) if match: state = 1 continue elif state == 1: match = re.match(r"\s*#\s*BEGIN CODE BLOCK", line) if match: state = 2 self.sourcemap.append(None) continue elif state == 2: match = re.match(r"\s*#\s*END CODE BLOCK", line) if match: state = 1 self.sourcemap.append(None) continue if state == 1 or state == 2: match = re.match(r"\s*#\s*SOURCE LINE (\w+)", line) if match: self.sourcemap.append(None) linecounter = int(match.group(1)) else: match = re.match(r"\s*#\s*END BLOCK (\w+)", line) if match: self.sourcemap.append(None) state = 0 else: self.sourcemap.append(linecounter) if state == 2: linecounter += 1 self.isvalid = True finally: f.close() def get_source(self): try: return self.compiled_record.csource.get_component_source_file() except: raise "Cant open file '%s'" % self.compiled_record.csource.file_path def get_line_number(self, linenum): if linenum < len(self.sourcemap): return self.sourcemap[linenum - 1] else: return None def htmlformat(self): return HTMLErrorFormatter(self).format() def textformat(self): return PlainTextErrorFormatter(self).format() def format(self): return PlainTextErrorFormatter(self).format() def singlelineformat(self): if len(self.trace_records): return "%s at %s line %d" % (self.msg, self.file, self.codeline) else: return self.msg file =property(lambda self: self.trace_records[0].file) codeline = property(lambda self:self.trace_records[0].codeline) def __str__(self): return self.singlelineformat() def is_python_syntax(self): return self.ispythonsyntax def message(self): return self.msg class ErrorFormatter: """base class for an object that returns a string representation of an error""" def format(self): try: return self.do_format() except Exception, e: return "ErrorFormatter had an exception: " + str(e) except: e = sys.exc_info()[0] return "ErrorFormatter had an exception: " + str(e) class PlainTextErrorFormatter(ErrorFormatter): def __init__(self, error): self.error = error error.initTraceback(None) self.records = self.error.trace_records self.lead_rec = self.error.lead_rec def do_format(self): templatetrace = self._format_traceback(self.records, True) realtrace = self._format_traceback(self.records, False) if self.lead_rec is not None: ret = (self._format_record(self.error.message(), self.lead_rec, templatetrace) + "\n---------------------------------------------\n" + "Original Stack Trace:\n"); if self.error.is_python_syntax(): ret += self._format_record(self.error.message(), self.lead_rec.original, realtrace) else: ret += self._format_record(self.error.message(), self.records[0].original, realtrace) return ret else: return self._format_record(self.error.message(), self.records[0], realtrace) def _format_traceback(self, records, friendly): trace = "" for rec in self.records: if not friendly: rec = rec.original if not friendly or not rec.is_myghty_lib(): trace += "%s:%s\n" % (rec.file, rec.codeline) return trace def _format_record(self, message, rec, trace): frec = ( message + "\n\n" + "file: %s line %s\n" % (rec.file, rec.codeline) ) frec += "\n" + str(rec.code); if trace: frec += "\n\n" + trace return frec class HTMLErrorFormatter(ErrorFormatter): def do_format(self): buffer = StringIO() try: self.interp.execute(self.comp, out_buffer=buffer, output_encoding='latin1', # Default for HTML encoding_errors='htmlentityreplace', request_args = argdict( records = self.records, lead_rec = self.lead_rec, error = self.error )) return buffer.getvalue() except Exception, e: nestederror = Error(wrapped = e) except: nestederror = Error(wrapped = sys.exc_info()[0]) nestederror.initTraceback(self.interp) return ("
%s\n\nAdditionally, HTMLErrorFormatter had the following error:\n\n%s
" % (PlainTextErrorFormatter(self.error).format(), PlainTextErrorFormatter(nestederror).format())) def __init__(self, error): import myghty.interp as interp self.error = error error.initTraceback(None) self.records = self.error.trace_records self.lead_rec = self.error.lead_rec def handle(e, m, **params): raise e self.interp = interp.Interpreter(error_handler = handle) self.comp = self.interp.make_component(""" <%args> records lead_rec error Myghty Template Error % if lead_rec is not None:

Myghty Template Error

<& formatrecord, message = error.message(), lead_rec = lead_rec, records = records, friendly = True &>

Original Stack Trace:

% if error.is_python_syntax(): <& formatrecord, message = error.message(), lead_rec = lead_rec.original, records = records, friendly = False &> % else: <& formatrecord, message = error.message(), lead_rec = records[0].original, records = records, friendly = False &> % % else: <& formatrecord, message = error.message(), lead_rec = records[0], records = records, friendly = False &> % <%def formatrecord> <%args> message lead_rec records friendly
Error: <% message |h %>
File: <% lead_rec.file %> line <% lead_rec.codeline %>
Context: <& formatlines, lines = lead_rec.get_lines(), highlight = lead_rec.codeline &>
Traceback: <& formattraceback, records = records, friendly = friendly &>
<%def formatlines> <%args> lines highlight % for line in lines: % code = m.apply_escapes( line.line.expandtabs(), 'h' ) % code = code.replace(' ',' ') % if line.linenum == highlight: <% line.linenum %>: <% code %>
% else: <% line.linenum %>: <% code %>
% <%def formattraceback> <%args> records friendly % for rec in records: % if not friendly: % rec = rec.original % if not friendly or not rec.is_myghty_lib(): <% rec.file %>:<% rec.codeline %>
% """, id='exception') class Syntax(Error): "invalid syntax in a component" def __init__(self, error, comp_name, source_line, line_number, source = None, file = None, source_encoding = None): self.error = error Error.__init__(self, error) self.lead_rec = Error.SyntaxTraceLine(source_line, line_number, comp_name, source, file, source_encoding) class AbortRequest(Exception): "request aborted" pass class Abort(AbortRequest): "HTTP abort called by component" def __init__(self, aborted_value, reason): self.aborted_value = aborted_value self.reason = reason class Redirected(Abort): "redirect called" def __init__(self, path, code=301, reason=None): self.path = path Abort.__init__(self, code, reason) class Decline(AbortRequest): "decline called by component" def __init__(self, declined_value): self.declined_value = declined_value class ConfigurationError(Error): "configuration error" pass class ServerError(Error): "server error (500) " pass class Request(Error): "request error" pass class Compiler(Error): "compiler error" pass class Interpreter(Error): "interpreter error" pass class MethodNotFound(Error): "couldnt find method" pass class ComponentNotFound(Error): "couldnt find component" def __init__(self, msg, resolution_detail, silent = False): Error.__init__(self, msg) self.resolution_detail = resolution_detail self.silent = silent def message(self): if self.resolution_detail is not None: return self.msg + " (" + string.join(self.resolution_detail, ', ') + ")" else: return self.msg def create_toplevel(self): """converts this ComponentNotFound into a TopLevelNotFound exception, which is used by HTTPHandlers to return a 404 return code.""" return TopLevelNotFound(self.msg, self.resolution_detail, self.silent) class PathIsDirectory(ComponentNotFound): "the specified component path is a directory" pass class TopLevelNotFound(ComponentNotFound): "couldnt find top level component" pass class MissingArgument(Error): "a required argument was missing from a component call" pass class IncompatibleCompiler(Error): "wrong compiler" pass ################################################################ import codecs, parser # Regexp to match python magic encoding line _PYTHON_MAGIC_COMMENT_re = re.compile( r'[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)', re.VERBOSE) def _parse_encoding(fp): """Deduce the encoding of a source file from magic comment. It does this in the same way as the `Python interpreter`__ .. __: http://docs.python.org/ref/encodings.html The ``fp`` argument should be a seekable file object. """ pos = fp.tell() fp.seek(0) try: line1 = fp.readline() has_bom = line1.startswith(codecs.BOM_UTF8) if has_bom: line1 = line1[len(codecs.BOM_UTF8):] m = _PYTHON_MAGIC_COMMENT_re.match(line1) if not m: try: parser.suite(line1) except SyntaxError: # Either it's a real syntax error, in which case the source # is not valid python source, or line2 is a continuation of # line1, in which case we don't want to scan line2 for a magic # comment. pass else: line2 = fp.readline() m = _PYTHON_MAGIC_COMMENT_re.match(line2) if has_bom: if m: raise SyntaxError, \ "python refuses to compile code with both a UTF8" \ " byte-order-mark and a magic encoding comment" return 'utf_8' elif m: return m.group(1) else: return None finally: fp.seek(pos) class _EncodedLine(object): def __init__(self, line, encoding=None): if encoding is None: encoding = sys.getdefaultencoding() self.line = line self.encoding = encoding def __str__(self): if isinstance(self.line, str): return self.line return self.line.encode('ascii', 'replace') def __unicode__(self): if isinstance(self.line, unicode): return self.line return self.line.decode(self.encoding, 'replace') def expandtabs(self, tabsize=8): return _EncodedLine(self.line.expandtabs(tabsize), self.encoding) myghty-1.1/lib/myghty/ext/0000755000175000017500000000000010501065764014565 5ustar malexmalexmyghty-1.1/lib/myghty/ext/__init__.py0000644000175000017500000000000010501064116016652 0ustar malexmalexmyghty-1.1/lib/myghty/ext/memcached.py0000644000175000017500000000477410501064116017047 0ustar malexmaleximport memcache from myghty.synchronization import * from myghty.container import NamespaceManager, Container import sys class MemcachedNamespaceManager(NamespaceManager): def __init__(self, context, namespace, url, **params): NamespaceManager.__init__(self, context, namespace, **params) self.mc = memcache.Client([url], debug=0) # memcached does its own locking. override our own stuff def do_acquire_read_lock(self): pass def do_release_read_lock(self): pass def do_acquire_write_lock(self, wait = True): return True def do_release_write_lock(self): pass # override open/close to do nothing, keep memcache connection open as long # as possible def open(self, *args, **params):pass def close(self, *args, **params):pass def __getitem__(self, key): value = self.mc.get(self.namespace + "_" + key) if value is None: raise KeyError(key) return value def __contains__(self, key): return self.mc.get(self.namespace + "_" + key) is not None def has_key(self, key): return self.mc.get(self.namespace + "_" + key) is not None def __setitem__(self, key, value): keys = self.mc.get(self.namespace + ':keys') if keys is None: keys = {} keys[key] = True self.mc.set(self.namespace + ':keys', keys) self.mc.set(self.namespace + "_" + key, value) def __delitem__(self, key): keys = self.mc.get(self.namespace + ':keys') try: del keys[key] self.mc.delete(self.namespace + "_" + key) self.mc.set(self.namespace + ':keys', keys) except KeyError: raise def do_remove(self): pass def keys(self): keys = self.mc.get(self.namespace + ':keys') if keys is None: return [] else: return keys.keys() class MemcachedContainer(Container): def do_init(self, **params): self.funclock = None def do_create_namespace_manager(self, context, namespace, url, **params): return MemcachedNamespaceManager(context, namespace, url, **params) def lock_createfunc(self, wait = True): if self.funclock is None: self.funclock = Synchronizer(identifier = "memcachedcontainer/funclock/%s" % self.namespacemanager.namespace, use_files = True, lock_dir = self.namespacemanager.lock_dir) return self.funclock.acquire_write_lock(wait) def unlock_createfunc(self): self.funclock.release_write_lock() myghty-1.1/lib/myghty/ext/routeresolver.py0000644000175000017500000001151110501064116020044 0ustar malexmaleximport os, types, string from myghty.resolver import ResolverRule import myghty.csource as csource import myghty.component as comp from myghty.resolver import Resolution import myghty.importer as importer import routes from routes.base import * class RoutesComponentSource(csource.ModuleComponentSource): def __init__(self, objpath, module): self.objpath = objpath self.module = module arg = module for t in objpath: arg = getattr(arg, t) name = "method:" + string.join(objpath, '_') self.has_method = True last_modified = importer.mod_time(module) csource.ComponentSource.__init__(self, "module|%s:%s" % (module.__name__, name), last_modified = last_modified) self.module = module self.objpath = objpath self.name = name self.class_ = RoutesComponent self.callable_ = arg def reload(self, module): self.module = module arg = module for t in self.objpath: arg = getattr(arg, t) self.callable_ = arg def can_compile(self): return False class RoutesComponent(comp.FunctionComponent): def do_run_component(self, **params): m = params['m'] r = params['r'] # do stuff with routes config config = routes.request_config() config.redirect = m.send_redirect config.host = r.headers_in['host'].split(':')[0] if r.environ.get('HTTPS'): config.protocol = 'https' else: config.protocol = 'http' # update params with routes resolution args if hasattr(m, 'resolution'): params.update(m.resolution.override_args) # if we are linked to a method off of an object instance, # see if the object instance has a do_run_component method and run it if self.component_source.has_method: target = self.component_source.callable_.im_self if hasattr(target, 'do_run_component'): getattr(target, 'do_run_component')(**params) return comp.FunctionComponent.do_run_component(self, **params) class RoutesResolver(ResolverRule): name = 'routeresolver' def _find_controllers(self, dirname, prefix=''): controllers = [] for fname in os.listdir(dirname): filename = dirname + '/' + fname if os.path.isfile(filename) and fname.endswith('_controller.py') and not fname.startswith('application_'): controllers.append(prefix + fname) elif os.path.isdir(filename): controllers.extend(self._find_controllers(filename, prefix=fname+'/')) return controllers def _get_controllers(self): clist = {} controllers = self._find_controllers(self.controller_root) for con in controllers: key = con[:-14] # Remove _controller.py clist[key] = self.controller_root + '/' + con return clist def __init__(self, mapper=None, controller_root=None, **params): self.mapper = mapper self.controller_root = controller_root def do_init_resolver(self, resolver, remaining_rules, **params): self.clist = self._get_controllers() self.mapper.create_regs(self.clist.keys()) def do(self, uri, remaining, resolution_detail, **params): if resolution_detail is not None: resolution_detail.append("resolverouteresolver:" + uri) if os.environ['MYGHTY_ENV'] == 'development': self.clist = self._get_controllers() self.mapper.create_regs(self.clist.keys()) match = self.mapper.match(uri) if match: controller = match['controller'] action = match['action'] if action.startswith('_'): return remaining.next().do(uri, remaining, resolution_detail, **params) # Load up the request local singleton config config = routes.request_config() config.mapper = self.mapper config.mapper_dict = match.copy() # Remove the action/controller, rest of the args pass to the function del match['action'] del match['controller'] filename = self.clist[controller] classname = controller.split('/')[-1].lower() module = importer.filemodule(filename) resolution_detail.append("\nController:%s, Action:%s" % (controller, action)) cs = RoutesComponentSource( module=module, objpath=[classname, action], ) #raise repr(cs.__dict__) return Resolution(cs, resolution_detail, override_args = match) else: return remaining.next().do(uri, remaining, resolution_detail, **params) myghty-1.1/lib/myghty/http/0000755000175000017500000000000010501065765014745 5ustar malexmalexmyghty-1.1/lib/myghty/http/__init__.py0000644000175000017500000000006610501064115017044 0ustar malexmalex# $Id: __init__.py 2013 2005-12-31 03:19:39Z zzzeek $ myghty-1.1/lib/myghty/http/ApacheHandler.py0000644000175000017500000001177010501064115017770 0ustar malexmalex# $Id: ApacheHandler.py 2028 2006-01-16 23:39:04Z zzzeek $ # ApacheHandler.py - handles apache requests for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # from mod_python import apache from mod_python import util as mputil import myghty.exception as exception import myghty.buffer import re,string, types, time from myghty.util import * from myghty.resolver import * import myghty.http.HTTPHandler as HTTPHandler def handle(httpreq, interpreter_name = None, **params): return get_handler(httpreq, interpreter_name, **params).handle(httpreq = httpreq, **params) def get_handler(httpreq, interpreter_name = None, **params): if interpreter_name is None: try: interpreter_name = httpreq.get_options()['MyghtyInterpreterName'] except KeyError: pass return HTTPHandler.get_handler(ApacheHandler, httpreq = httpreq, interpreter_name = interpreter_name, **params) class ApacheHandler(HTTPHandler.HTTPHandler): def __init__(self, httpreq, **params): HTTPHandler.HTTPHandler.__init__(self, httpreq, LogBuffer(httpreq.server), **params) def do_handle_result(self, httpreq, status_code, reason): httpreq.status = HTTPHandler.HTTP_OK if status_code == HTTPHandler.HTTP_OK: return apache.OK else: return status_code def do_get_init_params(self, httpreq, **params): table = httpreq.get_options() options = params.copy() for key in table.keys(): match = re.match("^Myghty(.*)$", key) if not match: continue param = match.group(1) param = string.lower(re.sub(r"(\w)([A-Z])", r"\1_\2", param)) if not options.has_key(param): options[param] = eval(table[key]) return options def do_make_request_impl(self, httpreq, **params): httpreq.add_common_vars() return ApacheRequestImpl(httpreq, **params) def do_get_resolver(self, **params): return ApacheResolver(**params) def do_get_component(self, httpreq, **params): return httpreq.uri class LogBuffer(object): def __init__(self, server, retcode = apache.APLOG_NOTICE): self.server = server self.retcode = retcode def write(self, s): apache.log_error(string.rstrip(s), self.retcode, self.server) def writelines(self, list): for line in list: apache.log_error(string.rstrip(line), self.retcode, self.server) def flush(self): pass class ApacheBuffer(HTTPHandler.HTTPWriter): def send_headers(self): if self.headers_sent: return self.headers_sent = True if not self.httpreq._content_type_set: self.httpreq.content_type = 'text/html' # this method is only documented in mod py 2.7 # (where its needed) but seems harmless though useless # to use in mod py 3.0 as well self.httpreq.send_http_header() def writelines(self, list): for line in list: self.write(line + "\n") def flush(self):pass class ApacheResolver(HTTPHandler.HTTPResolver):pass class ApacheRequestImpl(HTTPHandler.HTTPRequestImpl): def __init__(self, httpreq, use_modpython_session = False, **params): self.use_modpython_session = use_modpython_session HTTPHandler.HTTPRequestImpl.__init__(self, httpreq, LogBuffer(httpreq.server), LogBuffer(httpreq.server, apache.APLOG_ERR), **params) def get_session(self, **params): if not hasattr(self, 'session'): if self.use_modpython_session: import mod_python.Session self.session = mod_python.Session.Session(timeout = self.session_args.timeout) else: self.session = self.session_args.get_session(self.httpreq, **params) return self.session def do_get_out_buffer(self, httpreq, out_buffer = None, **params): if out_buffer is None: out_buffer = httpreq return ApacheBuffer(httpreq, out_buffer = out_buffer, **params) def do_make_request_args(self, httpreq, **params): """given a mod_python request, returns a real dict of key values, where the values are one of: a string, a list of strings, a Field object with an upload value, a list of Field objects with upload values.""" def formatfield(field): if type(field) == types.ListType: return map(formatfield, field) elif isinstance(field, mputil.Field): return field else: return str(field) request_args = {} fields = mputil.FieldStorage(httpreq, keep_blank_values = True) for key in fields.keys(): request_args[key] = formatfield(fields[key]) return request_args myghty-1.1/lib/myghty/http/CGIHandler.py0000644000175000017500000000702010501064115017202 0ustar malexmalex# $Id: CGIHandler.py 2028 2006-01-16 23:39:04Z zzzeek $ # CGIHandler.py - handles cgi requests for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # import myghty.interp import myghty.request import myghty.resolver import myghty.session as session import myghty.exception as exception import myghty.http.HTTPHandler as HTTPHandler from myghty.util import * import os, sys, cgi def handle(*args, **params): return HTTPHandler.handle_http(CGIHandler, *args, **params) def get_handler(interpreter_name = None, **params): return HTTPHandler.get_handler(CGIHandler, interpreter_name = interpreter_name, **params) class CGIHandler(HTTPHandler.HTTPHandler): def __init__(self, **params): HTTPHandler.HTTPHandler.__init__(self, None, sys.stderr, **params) def do_get_init_params(self, httpreq, **params): return params def do_get_resolver(self, **params): return CGIResolver(**params) def do_make_request_impl(self, httpreq, **params): return CGIRequestImpl(httpreq, **params) def do_get_component(self, httpreq, **params): return httpreq.path_info def do_handle_result(self, httpreq, status_code, reason): httpreq.send_http_header() if status_code != HTTPHandler.HTTP_OK: sys.stdout.write(""" Response code %d (%s)
Page: "%s" """ % (status_code, reason or "no message", httpreq.path_info)) def handle(self, httpreq = None, interp = None, request_impl = None, component = None, **params): if httpreq is None: httpreq = CGIHTTPRequest() HTTPHandler.HTTPHandler.handle(self, httpreq, interp, request_impl, component, **params) class CGIHTTPRequest(HTTPHandler.HTTPRequest): """simulates a mod_python request for use in non-mod-python applications. only a minimal featureset is here currently. """ def __init__(self): HTTPHandler.HTTPRequest.__init__(self) self.fieldstorage = cgi.FieldStorage(keep_blank_values = True) env = os.environ for key in env.keys(): if key[0:4] == 'HTTP': self.headers_in.add(key[5:], env[key]) self.content_type = 'text/html' self.method = env['REQUEST_METHOD'] self.filename = env['PATH_TRANSLATED'] self.args = env.get('QUERY_STRING', None) self.path_info = env.get('PATH_INFO', '/') self.out_buffer = sys.stdout def do_send_headers(self): headers = self.get_response_headers() headers.get_output(self.out_buffer) self.out_buffer.write("\n") class CGIResolver(HTTPHandler.HTTPResolver):pass class CGIWriter(HTTPHandler.HTTPWriter):pass class CGIRequestImpl(HTTPHandler.HTTPRequestImpl): def __init__(self, httpreq, **params): HTTPHandler.HTTPRequestImpl.__init__(self, httpreq, sys.stderr, sys.stderr, **params) def do_get_out_buffer(self, httpreq, out_buffer = None, **params): if out_buffer is not None: return CGIWriter(httpreq, out_buffer) else: return CGIWriter(httpreq, httpreq.out_buffer) def do_make_request_args(self, httpreq, **params): return self.request_args_from_fieldstorage(httpreq.fieldstorage) myghty-1.1/lib/myghty/http/HTTPHandler.py0000644000175000017500000004032110501064115017360 0ustar malexmalex# $Id: HTTPHandler.py 2028 2006-01-16 23:39:04Z zzzeek $ # generic HTTP handler library for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # import myghty.buffer import types, string, re, sys import myghty.resolver import myghty.interp from myghty.util import * import myghty.session as session import myghty.exception as exception __all__ = ['HTTPHandler', 'HeaderTable', 'HTTPRequest', 'HTTPWriter', 'HTTPResolver', 'handle_http'] handlers = {} def handle_http(handlerclass, interpreter_name = None, interp = None, request_impl = None, component = None, **params): """handles an HTTP request using the named HTTPHandler instance. if an instance of the name does not exist, it is created using the given configuration parameters. implementing modules should define their own handle() method that calls this one with the appropriate arguments.""" return get_handler(handlerclass, interpreter_name, **params).handle(interp = interp, request_impl = request_impl, component = component, **params) def get_handler(handlerclass, interpreter_name = None, **params): """returns an HTTPHandler instance keyed off the given name. if the named instance does not exist, it is created using the given configuration parameters.""" interpreter_name = handlerclass.__name__ + "_" + (interpreter_name or 'main') try: handler = handlers[interpreter_name] except KeyError: handler = handlerclass(**params) handlers[interpreter_name] = handler return handler class HTTPHandler: """entry point for HTTP requests, configures and maintains a reference to an instance of a Myghty intepreter, and serves as a factory for many of the other implementing objects in this module. override the do_*** methods of this class, and call the handle() method inside the the request-handling point of the implementing environment.""" def __init__(self, httpreq, logbuffer, **params): self.params = self._init_params(httpreq, logbuffer, **params) self.interp = myghty.interp.Interpreter(**self.params) def _init_params(self, httpreq, logbuffer, **params): params = self.do_get_init_params(httpreq, **params) if not params.has_key('allow_globals'): params['allow_globals'] = [] params['allow_globals'].append('r') if params.setdefault('use_session', False): params['allow_globals'].append('s') debug_elements = params.setdefault('debug_elements', []) # deprecated if params.setdefault('log_component_loading', False): debug_elements.append('classloading') # deprecated if params.setdefault('log_cache_operations', False): debug_elements.append('cache') if len(debug_elements) > 0 and not params.has_key('debug_file'): params['debug_file'] = logbuffer if not params.has_key('resolver'): d = dict(params) if d.has_key('debug_file'): del d['debug_file'] params['resolver'] = self.do_get_resolver(debug_file = None, **d) if not params.has_key('data_dir'): raise exception.ConfigurationError("data_dir config is required") if not params.has_key('component_root') and not params.has_key('module_components') and not params.has_key('resolver_strategy') and not params.has_key('module_root'): raise exception.ConfigurationError("No file resolution rules detected - component_root, module_components, and/or resolver_strategy") return params def do_get_init_params(self, httpreq, **params):raise NotImplementedError() def do_get_resolver(self, **params):raise NotImplementedError() def do_make_request_impl(self, httpreq, **params):raise NotImplementedError() def do_get_component(self, httpreq, **params):raise NotImplementedError() def do_handle_result(self, httpreq, status_code, reason): """called at the end of the request. in a normal response, headers and probably content have already been sent. in the case of errors or redirects, this method should insure that headers have been properly sent as required by the environment. in all cases, appropriate return data should be returned from this method which will be returned to the calling environment.""" raise NotImplementedError() def handle(self, httpreq, interp = None, request_impl = None, component = None, **params): """handles an HTTP request. implementing classes may want to override this to provide environment-specific request arguments that are then translated into the implementing module's own HTTPRequest implementation. httpreq - HTTPRequest request object or compatible interface interp - optional Interpreter object to override the default request_impl - optional RequestImpl object the default component - optional Component object or request path to serve **params - configuration parameters which override the default parameters """ if interp is None: interp = self.interp implparams = self.params.copy() implparams.update(params) if not request_impl: request_impl = self.do_make_request_impl(httpreq, **implparams) if not component: if implparams.has_key('component'): component = implparams['component'] else: component = self.do_get_component(httpreq, **implparams) reason = None request = interp.make_request(component = component, request_impl = request_impl, **params) try: request.execute() ret = HTTP_OK except IOError: ret = HTTP_OK except exception.Abort, e: ret = e.aborted_value reason = e.reason except exception.TopLevelNotFound: ret = HTTP_NOT_FOUND except exception.ServerError: ret = HTTP_INTERNAL_SERVER_ERROR httpreq.status = ret return self.do_handle_result(httpreq, ret, reason) class HTTPRequestImpl(myghty.request.AbstractRequestImpl): """subclasses the myghty AbstractRequestImpl class, to provide the connector between the "real" http request (HTTPRequest) and the Myghty request (Request). this class is responsible for setting up the resources needed by Request, including the output buffer, session, request arguments (usually via a FieldStorage type API), sending redirects and handling errors and other exceptions. """ def __init__(self, httpreq, logger, errorlogger, request_args = None, use_session = False, global_args = None, log_errors = False, output_errors = True, error_handler=None, **params): self.httpreq = httpreq if request_args is None: request_args = self.do_make_request_args(httpreq, **params) self.request_args = request_args if not global_args is None: # copy the global_args in case they are synonymous with those # given to the interpreter self.global_args = global_args.copy() else: self.global_args = {} self.global_args['r'] = self.httpreq self.session_args = session.MyghtySessionArgs(**params) self.error_handler = error_handler self.use_session = use_session if self.use_session: self.global_args['s'] = self.get_session() self.log_errors = log_errors self.output_errors = output_errors self.logger = logger self.errorlogger = errorlogger self.out_buffer = self.do_get_out_buffer(httpreq, **params) buffer = property(lambda self:self.out_buffer) def do_make_request_args(self, httpreq, **params):raise NotImplementedError() def do_get_out_buffer(self, httpreq, out_buffer = None, **params):raise NotImplementedError() def log(self, message): self.logger.write(message) def request_args_from_fieldstorage(self, fields): """based on FieldStorage request information, returns a dict of key values, where the values are one of: a string, a list of strings, a Field object with an upload value, a list of Field objects with upload values.""" def formatfield(field): if type(field) == types.ListType: return map(formatfield, field) elif not field.file: return field.value else: return field request_args = {} if fields.file: request_args['_file'] = fields.file elif fields.list: for key in fields.keys(): request_args[key] = formatfield(fields[key]) return request_args def get_session(self, **params): if not hasattr(self, 'session'): self.session = self.session_args.get_session(self.httpreq, **params) return self.session def handle_error(self, error, m, **params): if self._run_error_handler(self.error_handler, self.errorlogger, error, m, r = self.httpreq, **params): return if isinstance(error, exception.TopLevelNotFound): if not error.silent: self.errorlogger.write(error.singlelineformat()) raise error else: if self.log_errors: self.errorlogger.writelines(string.split(error.format(), "\n")) else: self.errorlogger.write(error.singlelineformat()) if self.output_errors: self.httpreq.content_type = "text/html" self.out_buffer.write(error.htmlformat()) #self.out_buffer.write(error.textformat()) else: raise exception.ServerError(wrapped = error) def send_redirect(self, path): self.httpreq.err_headers_out["Location"] = path self.httpreq.content_type = "text/html" self.httpreq.status = 302 self.out_buffer.write('

The document has moved' ' here

\n' % path) raise exception.Redirected(path, 302) def send_abort(self, ret, reason): raise exception.Abort(ret, reason) def clone(self, **params): # quick shallow copy cloner. constructor clone doesnt # really work in a superclass. # override this if a more complex clone is needed cloner = ConstructorClone(self, **params) impl = cloner.copyclone() return impl class HeaderTable(OrderedDict): """a dictionary for storing HTTP headers, works similarly to the table object in mod_python.""" def add(self, key, value): if self.has_key(key): list = self[key] if type(list) != types.ListType: list = [list] self[key] = list list.append(value) else: self[key] = value def __getitem__(self, key): return OrderedDict.__getitem__(self, string.lower(key)) def __setitem__(self, key, value): OrderedDict.__setitem__(self, string.lower(key), value) def has_headers(self): return len(self) > 0 def get_output(self, buffer): for key, value in self.iteritems(): key = re.sub('_', '-', key) if type(value) == types.StringType: buffer.write("%s: %s\n" % (key, value)) else: for v in value: buffer.write("%s: %s\n" % (key, v)) class HTTPResolver(myghty.resolver.FileResolver): """a subclass of the FileResolver, which can have HTTP-environment-specific functionality attached to it if desired.""" pass class HTTPRequest(object): """represents the actual HTTP request. implementations have to provide this object's implementation. in the case of mod_python, the actual mod_python request object is used instead of this one. the interface should have a compatible subset of the mod_python apache request object's attributes as follows: # set up by the base class __init__ method headers_in err_headers_out headers_out status headers_sent = False # should be added by the implementing __init__ method method content_type # not as required, but preferred: path_info args filename # required method is: do_send_headers() the headers attributes should use the HeaderTable class to implement. """ def __init__(self): self.headers_in = HeaderTable() self.headers_out = HeaderTable() self.err_headers_out = HeaderTable() self.status = HTTP_OK self.headers_sent = False def send_http_header(self): if not self.headers_sent: self.headers_sent = True self.do_send_headers() def do_send_headers(self):raise NotImplementedError() def get_response_headers(self): if len(self.err_headers_out): self.err_headers_out['content-type'] = self.content_type return self.err_headers_out else: self.headers_out['content-type'] = self.content_type return self.headers_out class HTTPWriter(myghty.buffer.BufferDecorator): """subclasses BufferDecorator to provide the output stream for the response. also calls send_http_header() off of the request at the moment content is first sent. """ def __init__(self, httpreq, out_buffer, *args, **params): myghty.buffer.BufferDecorator.__init__(self, out_buffer) self.httpreq = httpreq self.headers_sent = False def send_headers(self): if not self.headers_sent: self.headers_sent = True self.httpreq.send_http_header() def write(self, text): if not self.headers_sent: self.send_headers() self.buffer.write(text) def writelines(self, lines): if not self.headers_sent: self.send_headers() self.buffer.writelines(lines) # what HTTP thingamabob is complete without the HTTP status codes # defined somewhere HTTP_CONTINUE = 100 HTTP_SWITCHING_PROTOCOLS = 101 HTTP_PROCESSING = 102 HTTP_OK = 200 HTTP_CREATED = 201 HTTP_ACCEPTED = 202 HTTP_NON_AUTHORITATIVE = 203 HTTP_NO_CONTENT = 204 HTTP_RESET_CONTENT = 205 HTTP_PARTIAL_CONTENT = 206 HTTP_MULTI_STATUS = 207 HTTP_MULTIPLE_CHOICES = 300 HTTP_MOVED_PERMANENTLY = 301 HTTP_MOVED_TEMPORARILY = 302 HTTP_SEE_OTHER = 303 HTTP_NOT_MODIFIED = 304 HTTP_USE_PROXY = 305 HTTP_TEMPORARY_REDIRECT = 307 HTTP_BAD_REQUEST = 400 HTTP_UNAUTHORIZED = 401 HTTP_PAYMENT_REQUIRED = 402 HTTP_FORBIDDEN = 403 HTTP_NOT_FOUND = 404 HTTP_METHOD_NOT_ALLOWED = 405 HTTP_NOT_ACCEPTABLE = 406 HTTP_PROXY_AUTHENTICATION_REQUIRED= 407 HTTP_REQUEST_TIME_OUT = 408 HTTP_CONFLICT = 409 HTTP_GONE = 410 HTTP_LENGTH_REQUIRED = 411 HTTP_PRECONDITION_FAILED = 412 HTTP_REQUEST_ENTITY_TOO_LARGE = 413 HTTP_REQUEST_URI_TOO_LARGE = 414 HTTP_UNSUPPORTED_MEDIA_TYPE = 415 HTTP_RANGE_NOT_SATISFIABLE = 416 HTTP_EXPECTATION_FAILED = 417 HTTP_UNPROCESSABLE_ENTITY = 422 HTTP_LOCKED = 423 HTTP_FAILED_DEPENDENCY = 424 HTTP_INTERNAL_SERVER_ERROR = 500 HTTP_NOT_IMPLEMENTED = 501 HTTP_BAD_GATEWAY = 502 HTTP_SERVICE_UNAVAILABLE = 503 HTTP_GATEWAY_TIME_OUT = 504 HTTP_VERSION_NOT_SUPPORTED = 505 HTTP_VARIANT_ALSO_VARIES = 506 HTTP_INSUFFICIENT_STORAGE = 507 HTTP_NOT_EXTENDED = 510 myghty-1.1/lib/myghty/http/HTTPServerHandler.py0000644000175000017500000002305310501064115020552 0ustar malexmalex# $Id: HTTPServerHandler.py 2133 2006-09-06 18:52:56Z dairiki $ # HTTPServerHandler.py - standalone HTTP server for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # import myghty.interp import myghty.request import myghty.resolver import myghty.exception as exception from myghty.buffer import LinePrinter import myghty.http.HTTPHandler as HTTPHandler import BaseHTTPServer, SocketServer from myghty.util import * import os, sys, cgi, re, types, traceback import mimetypes,posixpath,shutil,urllib class HTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): """main server class. just overrides the basic webserver.""" def __init__(self, port = 8000, docroot = None, handlers = None, text_only = True, **params): self.params = params self.port = port self.text_only = text_only self.daemon_threads = False server_address = ('', port) if handlers is None: handlers = [{r'(.*)':HSHandler(**params)}] if docroot is not None: if not (isinstance(docroot, types.ListType)): handlers.append(docroot) else: handlers += docroot self.handlers = OrderedDict(handlers) BaseHTTPServer.HTTPServer.__init__(self, server_address, HTTPRequestHandler) class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """request handling for the webserver. this is the engine of the standalone server.""" def do_POST(self): self.handle_request() def do_GET(self): self.handle_request() def handle_request(self): httpreq = HSHTTPRequest(self) try: if ( self.server.text_only and httpreq.content_type[0:4] != "text" ): self.serve_file(httpreq) else: for key in self.server.handlers.keys(): match = re.match(key, httpreq.path_info) if match: handler = self.server.handlers[key] if match.lastindex >= 1: httpreq.converted_path_info = match.group(1) else: httpreq.converted_path_info = httpreq.path_info if type(handler) == str: self.serve_file(httpreq) else: handler.handle(httpreq) return except: traceback.print_exc(file=sys.stdout) self.send_error(500, "Server Error") # methods ripped from SimpleHTTPServer to handle sending binary # files, non-myghty files def translate_path(self, path): """Translate a /-separated PATH to the local filename syntax. Components that mean special things to the local file system (e.g. drive or directory names) are ignored. (XXX They should probably be diagnosed.) """ for key in self.server.handlers.keys(): match = re.match(key, path) if match: root = self.server.handlers[key] if type(root) != str: continue if match.lastindex >=1: path = match.group(1) break else: return None path = posixpath.normpath(urllib.unquote(path)) words = path.split('/') words = filter(None, words) path = root for word in words: drive, word = posixpath.splitdrive(word) head, word = posixpath.split(word) if word in (os.curdir, os.pardir): continue path = posixpath.join(path, word) return path def guess_type(self, path): """Guess the type of a file. Argument is a PATH (a filename). Return value is a string of the form type/subtype, usable for a MIME Content-type header. The default implementation looks the file's extension up in the table self.extensions_map, using text/plain as a default; however it would be permissible (if slow) to look inside the data to make a better guess. """ base, ext = posixpath.splitext(path) if ext in self.extensions_map: return self.extensions_map[ext] ext = ext.lower() if ext in self.extensions_map: return self.extensions_map[ext] else: return self.extensions_map[''] extensions_map = mimetypes.types_map.copy() extensions_map.update({ '': 'text/html', # Default '.myt':'text/html', '.myc':'text/html', '.py': 'text/plain', '.c': 'text/plain', '.h': 'text/plain', }) def serve_file(self, httpreq): ctype = httpreq.content_type if ctype.startswith('text/'): mode = 'r' else: mode = 'rb' path = httpreq.filename if path is None: self.send_error(404, "File not found") return try: f = open(path, mode) except IOError: self.send_error(404, "File not found") return self.send_response(200) self.send_header("Content-type", ctype) self.send_header("Content-Length", str(os.fstat(f.fileno())[6])) self.end_headers() shutil.copyfileobj(f, self.wfile) f.close() # now, the myghty stuff. def handle(httpreq, interpreter_name = None, **params): return HTTPHandler.handle_http(HSHandler, interpreter_name = interpreter_name, httpreq = httpreq, **params) class HSHandler(HTTPHandler.HTTPHandler): def __init__(self, **params): HTTPHandler.HTTPHandler.__init__(self, None, LinePrinter(sys.stderr), **params) def do_get_init_params(self, httpreq, **params): return params def do_get_resolver(self, **params): return HSResolver(**params) def do_make_request_impl(self, httpreq, **params): return HSRequestImpl(httpreq, **params) def do_get_component(self, httpreq, **params): return httpreq.converted_path_info def do_handle_result(self, httpreq, status, message): # in the case that we had an error, do a send_error, which shows the # BaseHTTPServer's error page # for codes below 300, do nothing; this will send no content, unless # the application has (as it does for a redirect) if status >= 400: if message is not None: httpreq.handler.send_error(status, message) else: httpreq.handler.send_error(status) def handle(self, httpreq): HTTPHandler.HTTPHandler.handle(self, httpreq) class HSHTTPRequest(HTTPHandler.HTTPRequest): def __init__(self, httpreqhandler): HTTPHandler.HTTPRequest.__init__(self) self.handler = httpreqhandler headers = httpreqhandler.headers for key in headers.keys(): self.headers_in.add(key, headers[key]) self.method = httpreqhandler.command match = re.match(r"([^\?]*)(?:\?(.*))?", httpreqhandler.path) if match: self.path_info = match.group(1) if match.lastindex >=2: self.args = match.group(2) else: self.args = None else: self.path_info = None self.args = None self.content_type = self.handler.guess_type(self.path_info) self.filename = self.handler.translate_path(self.path_info) # cobble together a cgi.FieldStorage object from what we have cgienviron = { 'QUERY_STRING': self.args, 'REQUEST_METHOD': self.method, } if self.headers_in.has_key('content-type'): cgienviron['CONTENT_TYPE'] = self.headers_in['content-type'] if self.headers_in.has_key('content-length'): cgienviron['CONTENT_LENGTH'] = self.headers_in['content-length'] self.fieldstorage = cgi.FieldStorage( fp = self.handler.rfile, environ = cgienviron, keep_blank_values = True ) def do_send_headers(self): self.handler.send_response(self.status) headers = self.get_response_headers() for key, value in headers.iteritems(): self.handler.send_header(key, value) self.handler.end_headers() class HSResolver(HTTPHandler.HTTPResolver): pass class HSWriter(HTTPHandler.HTTPWriter):pass class HSRequestImpl(HTTPHandler.HTTPRequestImpl): def __init__(self, httpreq, **params): HTTPHandler.HTTPRequestImpl.__init__(self, httpreq, LinePrinter(sys.stderr), LinePrinter(sys.stderr), **params) def do_make_request_args(self, httpreq, **params): return self.request_args_from_fieldstorage(httpreq.fieldstorage) def do_get_out_buffer(self, httpreq, out_buffer = None, **params): if out_buffer is None: return HSWriter(httpreq, httpreq.handler.wfile, **params) else: return out_buffer if __name__ == '__main__': params = {} for item in sys.argv[1:]: (key, value) = item.split('=', 1) params[key] = eval(value) httpd = HTTPServer(**params) print "HTTPServer listening on port %d" % httpd.port httpd.serve_forever() myghty-1.1/lib/myghty/http/WSGIHandler.py0000644000175000017500000001011410501064115017347 0ustar malexmalex# $Id: WSGIHandler.py 2133 2006-09-06 18:52:56Z dairiki $ # WSGIHandler.py - handles WSGI requests for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # import myghty.interp import myghty.request import myghty.resolver import myghty.buffer import myghty.escapes as escapes import myghty.session as session import myghty.exception as exception import myghty.http.HTTPHandler as HTTPHandler import myghty.http.HTTPHandler as http from myghty.util import * import os, sys, cgi def handle(environ, start_response, **params): return HTTPHandler.handle_http(WSGIHandler, environ = environ, start_response = start_response, **params) def get_handler(interpreter_name = None, **params): return HTTPHandler.get_handler(WSGIHandler, interpreter_name = interpreter_name, **params) def application(environ, start_response): return get_handler(**environ.get('myghty.application', {})).handle(environ, start_response, **environ.get('myghty.request', {})) class WSGIHandler(HTTPHandler.HTTPHandler): def __init__(self, **params): HTTPHandler.HTTPHandler.__init__(self, None, myghty.buffer.LinePrinter(sys.stderr), **params) def do_get_init_params(self, httpreq, **params): return params def do_get_resolver(self, **params): return WSGIResolver(**params) def do_make_request_impl(self, httpreq, **params): return WSGIRequestImpl(httpreq, **params) def do_get_component(self, httpreq, **params): return httpreq.path_info def do_handle_result(self, httpreq, status_code, reason): httpreq.status = status_code httpreq.status_message = reason httpreq.send_http_header() return [httpreq.out_buffer.getvalue()] def handle(self, environ, start_response, httpreq = None, interp = None, request_impl = None, component = None, **params): if httpreq is None: httpreq = WSGIRequest(environ, start_response) return HTTPHandler.HTTPHandler.handle(self, httpreq, interp, request_impl, component, **params) class WSGIRequest(HTTPHandler.HTTPRequest): def __init__(self, environ, start_response): HTTPHandler.HTTPRequest.__init__(self) self.start_response = start_response self.environ = environ for key in environ.keys(): if key[0:4] == 'HTTP': self.headers_in.add(key[5:], environ[key]) self.content_type = 'text/html' self.method = environ['REQUEST_METHOD'] self.path_info = environ.get('PATH_INFO', '/') self.args = environ.get('QUERY_STRING', None) self.status_message = None self.filename = environ.get('PATH_TRANSLATED', None) self.out_buffer = StringIO() self.fieldstorage = cgi.FieldStorage( fp = environ['wsgi.input'], environ = environ, keep_blank_values = True ) def do_send_headers(self): headers = [(key, value) for key, value in self.get_response_headers().iteritems()] if self.status_message is None: reason = "HTTP return code" else: reason = self.status_message self.start_response(str(self.status) + " " + reason, headers) class WSGIResolver(HTTPHandler.HTTPResolver): pass class WSGIWriter(HTTPHandler.HTTPWriter):pass class WSGIRequestImpl(HTTPHandler.HTTPRequestImpl): def __init__(self, httpreq, **params): logger = myghty.buffer.LinePrinter(httpreq.environ['wsgi.errors']) HTTPHandler.HTTPRequestImpl.__init__(self, httpreq, logger, logger, **params) def do_make_request_args(self, httpreq, **params): return self.request_args_from_fieldstorage(httpreq.fieldstorage) def do_get_out_buffer(self, httpreq, out_buffer = None, **params): if out_buffer is None: return WSGIWriter(httpreq, httpreq.out_buffer) else: return WSGIWriter(httpreq, out_buffer) myghty-1.1/lib/myghty/importer.py0000644000175000017500000001767510501064120016201 0ustar malexmalex# $Id: importer.py 2013 2005-12-31 03:19:39Z zzzeek $ # importer.py - Myghty memory-managed module importer # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # import string, os, sys, imp, re, stat, types, time, weakref, __builtin__ """ module loading and management. loads modules by file paths directly, as well as via module names. keeps track of last modified time to provide a "reasonable" level of "reload when changed" behavior without restarting the application. By "reasonable" we include the module itself, but not its parent package or any of its named dependencies. in the case of file-based modules, which correspond to compiled templates as well as module components resolved via file paths, they are kept out of sys.modules so they can be cleanly reloaded when modified, and removed from memory when they fall out of scope. To maintain "importability" of these modules, the builtin __import__ method is overridden application-wide to search for these modules in a local weak dictionary first before proceeding to normal import behavior. in the case of modules located by a package/module name, they are loaded into sys.modules via the default __import__ method and are reloaded via reload(). For these modules, the "singleton" behavior of Python's regular module system applies. This behavior includes the caveats that old attributes stay lying around, and the module is reloaded "in place" which in rare circumstances could affect code executing against the module. The advantage is that the module's parent packages all remain pointing to the correctly reloaded module and no exotic synchronization-intensive "reconnection" of newly reloaded modules to their packages needs to happen. The "importability" of a module loaded here is usually not even an issue as it typcially is only providing Myghty components which are solely invoked by the Interpreter. However, in addition to the case where the developer is explicitly importing from a module that also provides Myghty components, the other case when the module requires import is when a class defined within it is deserialized, such as from a cache or session object; hence the need to override __import__ as well as maintaining the structure of packages. """ modules = weakref.WeakValueDictionary() # override __import__ to look in our own local module dict first builtin_importer = __builtin__.__import__ def import_module(name, globals = None, locals = None, fromlist = None): try: return modules[name].module except KeyError: return builtin_importer(name, globals, locals, fromlist) __builtin__.__import__ = import_module class ModValue: """2.3 is not letting us make a weakref to a module. so create a lovely circular reference thingy and weakref to that.""" def __init__(self, module): self.module = module module.__modvalue__ = self def module(name): """imports a module by string name via normal module importing, attaches timestamp information""" if name == '__main__': return sys.modules[name] mod = builtin_importer(name) components = name.split('.') for comp in components[1:]: mod = getattr(mod, comp) if not hasattr(mod, "__modified_time"): mod.__modified_time = modulemodtime(mod) mod.__is_file = False return mod def filemodule(path, id = None, reload = True, forcereload = False): """loads a module directly from a file path.""" if id is None: id = re.sub(r'\W+','_',path) if not forcereload: try: module = modules[id].module if not reload or module.__modified_time >= modulemodtime(module): return module except KeyError: pass modfile = open(path, 'r') try: #print "loading: " + path # Check mtime before loading module, so that modified_time # is guaranteed not to be later than the mtime of the loaded # version of the file. modified_time = os.fstat(modfile.fileno())[stat.ST_MTIME] module = imp.load_source(id, path, modfile) del sys.modules[id] modules[id] = ModValue(module) module.__modified_time = modified_time module.__is_file = True return module finally: modfile.close() def reload_module(module): """reloads any module that was loaded with filemodule(), if its modification time has changed. """ if not hasattr(module, '__modified_time'): # if we didnt load it, we dont change it return module elif module.__modified_time < modulemodtime(module): if module.__is_file is False: #print "regular reload: " + module.__name__ # Get mtime before reload to ensure it is <= the actual mtime # of the reloaded module. modified_time = modulemodtime(module) reload(module) module.__modified_time = modified_time return module else: file = module.__file__ file = re.sub(r'\.pyc$|\.pyo$', '.py', file) return filemodule(file, id = module.__name__, forcereload = True) else: return module def mod_time(module): try: return module.__modified_time except AttributeError: return modulemodtime(module) def modulemodtime(module): """returns the modified time of a module's source file""" try: file = module.__file__ pyfile = re.sub(r'\.pyc$|\.pyo$', '.py', file) if os.access(pyfile, os.F_OK): file = pyfile #print "checking time on " + file st = os.stat(file) return st[stat.ST_MTIME] except AttributeError: return None class ObjectPathIterator: """walks a file path looking for a python module. once it loads the python module, then continues walking the path into module's attributes.""" def __init__(self, path, reload = True): self.path = path self.reload = reload self.module = None self.objpath = [] if isinstance(path, types.ModuleType): self.module = path if reload: reload_module(self.module) self.last_modified = None def get_unit(self, tokens, stringtokens = [], moduletokens = []): if isinstance(self.path, str): return self.get_string_unit(tokens + stringtokens) else: return self.get_attr_unit(tokens + moduletokens) def get_string_unit(self, tokens): for token in tokens: path = self.path + "/" + token #print "check path " + repr(path) if self._check_module(path): return (self.path, token) if not os.access(path, os.F_OK): continue self.path = path return (self.path, token) else: raise StopIteration def get_attr_unit(self, tokens): for token in tokens: try: #print "check attr path " + repr(self.path) + " " + token attr = getattr(self.path, token) if isinstance(attr, types.ModuleType): raise AttributeError(token) self.path = attr self.objpath.append(token) return (self.path, token) except AttributeError: continue else: self.path = None raise StopIteration def _check_module(self, path): try: st = os.stat(path + ".py") except OSError: return False if stat.S_ISREG(st[stat.ST_MODE]): self.path = filemodule(path + ".py", reload = self.reload) self.module = self.path self.last_modified = mod_time(self.module) return True myghty-1.1/lib/myghty/interp.py0000644000175000017500000005165210501064120015632 0ustar malexmalex# $Id: interp.py 2133 2006-09-06 18:52:56Z dairiki $ # interp.py - interprets requests to Myghty templates # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # Original Perl code and documentation copyright (c) 1998-2003 by Jonathan Swartz. # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # """The interp package and its primary class, Interpreter, represents the runtime environment for Myghty components. It serves as a "home base" for configuration information and request construction, and also assists the Request object in locating components. The Interpreter maintains a pool of component objects and handles, at a high level, their compilation and filesystem representation.""" from myghty.util import * import myghty.util as util from myghty.synchronization import * import myghty.exception as exception import myghty.resolver import myghty.request import myghty.csource import myghty.compiler import myghty.escapes import myghty.importer as importer import myghty.buffer import myghty.cache import myghty.component from stat import * import time, os, stat, re, types, sys, string, random import posixpath as unixpath import weakref, imp, py_compile __all__ = ['Interpreter'] class Interpreter: """Represents an environment in which to create requests, compile and execute components.""" def __init__(self, attributes = None, data_dir = None, compiler = None, resolver = None, request = None, code_cache_size = 16777216, use_auto_handlers = True, use_object_files = True, debug_file = myghty.buffer.LinePrinter(sys.stderr), debug_threads = False, debug_elements = [], escapes = {}, global_args = None, auto_handler_name = 'autohandler', **params): """constructs a new Interpreter object. All Myghty configuration parameters are sent to this method as keyword parameters. Those parameters that are used directly by this Interpreter are consumed. Remaining arguments are sent off to other constructed objects, which include a Compiler, a Resolver, and a Request prototype object.""" self.global_args = global_args or {} self._attributes = {} if attributes is not None: self._attributes.update(attributes) self.attributes = InheritedDict(self._attributes, lambda: None) self.init_params = params self.use_auto_handlers = use_auto_handlers self.auto_handler_name = auto_handler_name self.use_object_files = use_object_files self.use_static_source = params.get('use_static_source', False) # set up debug stuff self.debuggers = {} self.log_file = myghty.buffer.LogFormatter(debug_file, 'Myghty', id_threads = debug_threads) if len(debug_elements): self._debug = True for elem in debug_elements: # how very strange this is ! s = ( "def log(msg):\n" + " self.log_file.write('[%s] ' + msg)\n" + "self.debuggers[elem] = log") % elem exec s in locals() self.log_file.write("%s starting - debug logging: %s" % (repr(self), string.join(debug_elements, ' '))) else: self._debug = False # ------------------ if params.has_key('component'): del params['component'] # set up cache object creator self.cache_args = myghty.cache.CacheArgs(cache_data_dir = data_dir, **params) if self.debuggers.get('cache', None) is not None: self.cache_args.set_params(debug_file = myghty.buffer.FunctionBuffer(self.debuggers['cache'])) # set up resolver if resolver: self.resolver = resolver else: self.resolver = myghty.resolver.Resolver(**params) if self.debuggers.get('resolution', None) is not None: self.resolver.debug_file = myghty.buffer.FunctionBuffer(self.debuggers['resolution']) # set up thread-local compiler prototype if not compiler: compiler = myghty.compiler.Compiler(**params) self.compiler = ThreadLocal(creator = lambda: compiler.clone()) # set up request prototype if request: self.request = request else: self.request = myghty.request.Request(self, **params) # data directory stuff, make sure directories exist if data_dir is not None: self.data_dir = data_dir self.object_dir = unixpath.join(data_dir, 'obj') self.lock_dir = unixpath.join(data_dir, 'lock') else: self.data_dir = None self.object_dir = None self.lock_dir = None if data_dir is None: self.use_object_files = False else: self._verify_directory(self.object_dir) self._verify_directory(self.lock_dir) # a dictionary of CompileRecord objects linked to the filename they # are compiled into. strong reference provided by the resulting FileComponent # object. self.reverse_lookup = weakref.WeakValueDictionary() def componentdeleted(comp): if self._debug: self.debug("code_cache size %d - removing %s (%d bytes)" % (self.code_cache.currentsize, comp.id, comp.size), "codecache") self.code_cache = LRUCache(code_cache_size, deletefunc=componentdeleted) self.escapes = {'h':myghty.escapes.html_escape, 'u':myghty.escapes.url_escape, 'x':myghty.escapes.xml_escape} self.escapes.update(escapes) def execute(self, component, **params): """executes the given component with the given request construction arguments. The component can be a string path which will be resolved, or a Component object.""" return self.make_request(component = component, **params).execute() def make_request(self, component, **params): """creates a new request with the given component and request construction arguments. The request is copied from an existing request instance, overriding its parameters with those given. The returned request is executed by calling its execute() method. The component can be a string path which will be resolved when the request is executed, or a Component object.""" return self.request.clone(component = component, interpreter = self, **params) def raise_error(self, error): """raises an Interpreter error with the given message string.""" raise exception.Interpreter(error) def component_exists(self, path, **params): """returns True if the given path can be resolved to a ComponentSource instance, i.e. if a component can be loaded for this path.""" if self.resolver.resolve(path, raise_error = False, **params): return True else: return False def load(self, path, **params): """resolves and loads the component specified by the given path. **params is a set of keyword arguments passed to the resolution system.""" resolution = self.resolve_component(path, **params) if not resolution: return None return self.load_component(resolution.csource) def load_module_component(self, raise_error = True, **params): """resolves and loads a module component specified within the given keyword parameters. The keyword parameters are passed directly to myghty.csource.ModuleComponentSource. The easiest way to use this method is to pass the keyword parameter "arg", which references a string in the form ":", where is a regular Python module to be loaded via __import__, and is the dotted path to either a callable object inside the module or a class inside the module which subclasses myghty.component.ModuleComponent.""" csource = myghty.csource.ModuleComponentSource(**params) return self.load_component(csource) def resolve_component(self, path, **params): """resolves the component specified by the given path. a myghty.resolver.Resolution object is returned containing a myghty.csource.ComponentSource reference. Raises ComponentNotFound by default if the path cannot be resolved. **params is a set of keyword parameters sent directly to the resolve() method on myghty.resolver.Resolver.""" return self.resolver.resolve(path, **params) def load_component(self, csource): """loads the component corresponding to the given ComponentSource object. This can be any subclass of ComponentSource. If the component was already loaded and exists within this Interpreter's code cache, returns the existing Component. If the Component has been modified at its source, it will re-load or re-compile the Component as needed. If this Component has not been loaded, it will import or compile the Component as needed. This operation is also synchronized against other threads. On systems that support file-locking it is synchronized against other processes as well. """ component_id = csource.id try: component = self.code_cache[component_id] if ( self.use_static_source or (component.component_source.last_modified >= csource.last_modified and component.load_time >= csource.last_modified) ): return component else: component.needs_reload = True except KeyError: pass # lock this block to other threads based on the ID of the component name_lock = self._get_component_mutex(component_id) def create(): component = self.get_compiled_component(csource, use_file = self.use_object_files, recompile_newer = not self.use_static_source) if self._debug: self.debug("code_cache size %d - adding %s (%d bytes)" % (self.code_cache.currentsize, component.id, component.size), "codecache") component.load_time = int(time.time()) component.needs_reload = False return component def isvalid(component): return not component.needs_reload component = self.code_cache.sync_get(component_id, create, name_lock, isvalid) return component def _get_component_mutex(self, component_id): return NameLock(identifier = "interpreter/loadcomponent/%s" % component_id) def make_component(self, source, id = None): """creates an in-memory template Component given its source as a string...this can be any Myghty template that would ordinarily be compiled from the filesystem. the optional "id" string parameter is used to identify this component within this Interpreter's code cache. If not given, one will be generated.""" csource = myghty.csource.MemoryComponentSource(source = source, id = id) return self.get_compiled_component(csource, use_file = False) def get_component_object_files(self, component_source): """returns a tuple containing the full paths to the .py, .pyc, and .pyo files corresponding to the given FileComponentSource object.""" return ( self.object_dir + '/' + component_source.path_id + '/' + component_source.path + ".py", self.object_dir + '/' + component_source.path_id + '/' + component_source.path + ".pyc", self.object_dir + '/' + component_source.path_id + '/' + component_source.path + ".pyo" ) class CompileRecord: """a record used to link exception reporting back to the originating python and myghty template source""" def __init__(self, module_id, csource, compiledto = None, compiledsource = None): self.module_id = module_id self.csource = csource self.compiledto = compiledto self.compiledsource = compiledsource def get_compiled_source(self): if self.compiledsource is not None: return util.StringIO(self.compiledsource) else: return open(self.compiledto) def get_compiled_component(self, csource, always_recompile = False, recompile_newer = True, use_file = True): """used by load_component to deliver a new Component object from a ComponentSource.""" if not csource.can_compile(): return self._get_module_component(csource, always_recompile, recompile_newer) elif not use_file: return self._get_inmemory_component(csource, always_recompile, recompile_newer) else: return self._get_file_component(csource, always_recompile, recompile_newer) def _get_module_component(self, csource, always_recompile = False, recompile_newer = True): """loads a module component from a regular Python module. Most of the module-loading code is in the util package.""" module = csource.module if self._debug: self.debug("loading component %s from module object %s" % (csource.id, repr(module)), "classloading") if module.__name__ != '__main__': if recompile_newer or always_recompile: csource.reload(importer.reload_module(module)) if csource.class_ is not None and issubclass(csource.class_, myghty.component.ModuleComponent): return csource.class_(self, csource) else: return myghty.component.FunctionComponent(self, csource) def _get_file_component(self, csource, always_recompile = False, recompile_newer = True): """loads and compiles a file-based component as a regular Python module with a .py and .pyc file.""" destbuf = None compiler = self.compiler.get() # the full module name of the component to be compiled cid = "_myghtycomp_" + re.sub(r"\W", "_", csource.path_id) + "_" + re.sub(r"\W", "_", csource.id) (object_file, compiled_object_file, optimized_object_file) = self.get_component_object_files(csource) # since we might write to a file in this area, synchronize against other threads and processes file_name_lock = Synchronizer("interpreter/filewriter/%s" % csource.id, use_files = True, lock_dir = self.lock_dir) def needs_recompile(): # the _need_file checks the csource last modified, which corresponds to the .myt # file's last modified, to the actual .py file's modified time. return ( always_recompile or (recompile_newer and self._need_file(csource.last_modified, object_file)) or (not self._file_exists(object_file)) or (hasattr(csource, '_bad_magic_number') and csource._bad_magic_number) ) force_reload = False # check if we have to recompile to a file if needs_recompile(): file_name_lock.acquire_write_lock() try: # through the lock, check again to see if another fork already compiled if needs_recompile(): self._verify_directory(unixpath.join(self.object_dir, "./" + csource.path_id + "/" + csource.dir_name)) if self._file_exists(compiled_object_file): os.remove(compiled_object_file) if self._file_exists(optimized_object_file): os.remove(optimized_object_file) if self._debug: self.debug("compiling %s in file %s" % (csource.id, object_file), "classloading") destbuf = file(object_file, 'w') csource.get_object_code(compiler, destbuf) destbuf.close() if getattr(csource, '_bad_magic_number', False): force_reload = True csource._bad_magic_number = False finally: file_name_lock.release_write_lock() file_name_lock.acquire_read_lock() try: comprec = Interpreter.CompileRecord(cid, csource, compiledto = object_file) self.reverse_lookup[object_file] = comprec if self._debug: self.debug("loading component from source file %s" % object_file, "classloading") module = importer.filemodule(object_file, id = cid, forcereload = force_reload) csource.module = module # attach the compile record to the module so it remains # referenced as long as the module does module.__compile = comprec # get the size of the source file and attach to the component source csource.modulesize = os.stat(object_file)[ST_SIZE] # check for bad magic number (incompatible compiler version), # and recompile if needed if ( not hasattr(module, '_MAGIC_NUMBER') or module._MAGIC_NUMBER != compiler.get_magic_number() ): # paranoid check against impossible endless loop if hasattr(csource, '_bad_magic_number') and csource._bad_magic_number: raise "assertion failed: csource magic number check completed but recompile still produces bad magic number" # set a flag, we'll call again csource._bad_magic_number = True else: csource._bad_magic_number = False finally: file_name_lock.release_read_lock() if csource._bad_magic_number: return self.get_compiled_component(csource, always_recompile = always_recompile, recompile_newer = recompile_newer, use_file = True) else: return module.get_component(self, csource) def _get_inmemory_component(self, csource, always_recompile = False, recompile_newer = True): """loads and compiles a file-based component as an in-memory module.""" destbuf = None compiler = self.compiler.get() # the full module name of the component to be compiled cid = "_myghtycomp_" + re.sub(r"\W", "_", csource.path_id) + "_" + re.sub(r"\W", "_", csource.id) if self._debug: self.debug("compiling %s in memory" % csource.id, "classloading") destbuf = util.StringIO() csource.get_object_code(compiler, destbuf) filename = "memory:" + cid module = csource.module def needs_recompile(): return ( always_recompile or module is None or (recompile_newer and csource.last_modified > module._modified_time) ) if needs_recompile(): module = imp.new_module(cid) code = compile(destbuf.getvalue(), filename, 'exec') exec code in module.__dict__, module.__dict__ module._modified_time = time.time() csource.module = module comprec = Interpreter.CompileRecord(cid, csource, compiledsource = destbuf.getvalue()) self.reverse_lookup[filename] = comprec module.__compile = comprec csource.modulesize = len(destbuf.getvalue()) csource._bad_magic_number = False return module.get_component(self, csource) def _file_exists(self, file): return os.access(file, os.F_OK) def _need_file(self, modified_time, file): return (not os.access(file, os.F_OK) or modified_time > os.stat(file)[ST_MTIME] or os.stat(file)[ST_SIZE] == 0) def _verify_directory(self, dir): verify_directory(dir) def debug(self, message, type = None): """writes a debug message to the debug file. the optional type string refers to a "debug_element" type, i.e. 'resolution', 'cache', etc. which will only write a message if that debug_element was enabled with this Interpreter.""" if type is None: self.log_file.write(message) else: log = self.debuggers.get(type, None) if log is not None: log(message) # deprecated def get_attribute(self, key): """gets an attribute from this Interpreter. deprecated, use the "attributes" member directly instead.""" return self.attributes(key) def set_attribute(self, key, value): """sets an attribute on this Interpreter. deprecated, use the "attributes" member directly instead.""" self.attributes(key, value) def get_attributes(self): """returns the attributes dictionary for this Interpreter. deprecated, use the "attributes" member directly instead.""" return self.attributes() myghty-1.1/lib/myghty/lexer.py0000644000175000017500000005372210501064120015450 0ustar malexmalex# $Id: lexer.py 2133 2006-09-06 18:52:56Z dairiki $ # lexer.py - template parsing routines for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # Original Perl code and documentation copyright (c) 1998-2003 by Jonathan Swartz. # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # import string, re, sys, codecs from myghty import exception from myghty.util import * """initial parser for a Myghty file, locates tokens and fires events in a Compiler object. Lexer is currently stateful and is not thread safe. the clone() method can be used to create copies of this object for use in multiple threads. """ # map of block names pointing to parse method names BLOCKS = { 'args' : 'variable_list_block', 'attr' : 'key_value_block', 'flags' : 'key_value_block', 'cleanup' : 'raw_block', 'doc' : 'doc_block', 'filter' : 'raw_block', 'init' : 'raw_block', 'once' : 'raw_block', 'global' : 'synonym:once', 'threadonce' : 'raw_block', 'threadlocal': 'synonym:threadonce', 'python' : 'raw_block', 'shared' : 'raw_block', 'requestlocal' : 'synonym:shared', 'requestonce' : 'synonym:shared', 'text' : 'text_block', } PYTHON_SCOPES = { 'component': 'python', 'request': 'shared', 'thread': 'threadonce', 'global': 'once', 'init' : 'init', 'cleanup': 'cleanup' } class Lexer: def __init__(self, **params): self.current = None def get_object_id(self): """returns an ID that can identify this lexer""" return "Myghty.Lexer" def clone(self, **params): """creates a clone of this Lexer. allow the Prototype pattern to be used in creating lexers for use in other threads.""" return Lexer(**params) class LexContext: """an object tracking the lexer's progress through a component block.""" def __init__(self, source, name, compiler): self.source = source self.name = name self.compiler = compiler # a regular expression to match the "end" of whatever construct # the parser located # different methods override this to locate different kinds of # endings # This will be overridden if entering a def or method section. self.ending = re.compile(r'\Z', re.S) # place to begin regular expression matching # since I cannot find an equivalent of perl's \G in python self.match_position = 0 self.in_def = False self.in_method = False self.block_name = None self.block_type = None self.lines = 0 def set_in_named_block(self, block_type, name): if block_type == 'def': self.in_def = True elif block_type == 'method': self.in_method = True elif block_type == 'closure': pass else: raise "invalid block type %s" % block_type self.block_name = name self.block_type = block_type def reset_in_named_block(self): self.in_def = False self.in_method = False self.block_name = None def match_pos(self, regstring = None, flags = None, regexp = None): if regexp == None: if flags: regexp = re.compile(regstring, flags) else: regexp = re.compile(regstring) match = regexp.match(self.source, self.match_position ) if match: (start, end) = match.span() # attempt to simulate perl's \G operator. usually works, except # it behaves differently with zero-length matches. # well actually perl's operator behaves more strangely. # see def variable_list_block for further \G angst if end == start: self.match_position = end + 1 else: self.match_position = end return match def lex(self, source, name, compiler, input_file = None): # Holds information about the current lex. current = Lexer.LexContext(source, name, compiler) # set current lex to this one self.current = current # optional full path of the file the source came from; passed through # to SyntaxErrors for exception reporting self.input_file = input_file # Clean up Mac and DOS line endings current.source = re.sub(r'\r\n?', "\n", current.source) # Detect and remove leading UTF-8 byte-order-marker # Some windows editors add these at the beginning of a file to # mark their content as UTF-8. if current.source.startswith(codecs.BOM_UTF8): current.source = current.source[len(codecs.BOM_UTF8):] self.current.compiler.magic_encoding_comment('utf_8') try: try: current.compiler.start_component() self.start() except Exception, e: raise finally: current.compiler.end_component() def start(self): end = None length = len(self.current.source) while (True): if self.current.match_position > length: break end = self.match_end() if end: break if self.match_block(): continue if self.match_named_block():continue if self.match_substitute(): continue if self.match_comp_call(): continue if self.match_python_line(): continue if self.match_comp_content_call(): continue if self.match_comp_content_call_end(): continue if self.match_text(): continue isend = (self.current.match_position > len(self.current.source)) if (self.current.in_def or self.current.in_method) and isend: self.raise_syntax_error("Missing closing tag" % self.current.block_type) if isend: break raise exception.Compiler("Infinite parsing loop encountered - Lexer bug?") if self.current.in_def or self.current.in_method: type = self.current.block_type if not isinstance(end, str) or not self.current.ending.match(end): block_name = self.current.block_name self.raise_syntax_error("no closing tag for <%%%s %s> block" % (type, type, block_name)) def match_block(self): match = self.current.match_pos(regexp = re.compile(r'\<%(' + string.join(BLOCKS.keys(), '|') + r')(\s+[^>]*)?\s*>', re.I | re.S )) if match: (type, attr) = (match.group(1).lower(), match.group(2)) self.current.block_type = type attributes = {} if attr: attrmatch = re.findall(r"\s*((\w+)\s*=\s*('[^']*'|\"[^\"]*\"|\w+))\s*", attr) for att in attrmatch: (full, key, val) = att try: attributes[key] = eval(val) except: (e, msg) = sys.exc_info()[0:2] self.raise_syntax_error("Non-evaluable attribute value: '%s' (%s: %s)" % (val, e, msg)) syntype = None # get method name for this block try: method = BLOCKS[type] if string.find(method, ':') != -1: syntype = method.split(':', 1)[-1] method = BLOCKS[syntype] except KeyError: self.raise_syntax_error("no such block type '%s'" % type) if attributes.has_key('scope') and type == 'python' or syntype == 'python': try: syntype = PYTHON_SCOPES[attributes['scope']] except KeyError: self.raise_syntax_error("unknown python scope '%s'" % attributes['scope']) if syntype: self.current.compiler.start_block(block_type = syntype, attributes = attributes) else: self.current.compiler.start_block(block_type = type, attributes = attributes) # call method dynamically getattr(self, method)(block_type = type, synonym_for = syntype, attributes = attributes) self.current.block_type = None return True else: return False def match_named_block(self): match = self.current.match_pos(regexp = re.compile(r"<%(def|method|closure)(?:\s+([^\n]+?))?(\s+[^>]*)?\s*>", re.I | re.S)) if match: (type, name, attr) = (match.group(1).lower(), match.group(2), match.group(3)) attributes = {} if attr: attrmatch = re.findall(r"\s*((\w+)\s*=\s*('[^']*'|\"[^\"]*\"|\w+))\s*", attr) for att in attrmatch: (full, key, val) = att attributes[key] = val if not type or not name: self.raise_syntax_error("%s block without a name" % type) self.current.compiler.start_named_block(block_type = type, name = name, attributes = attributes) # preserve a little state existingending = self.current.ending # screw with the current compile context self.current.ending = re.compile(r"<\/%%%s>(\n?)" % type, re.I) self.current.set_in_named_block(block_type = type, name = name) # recursively call the start() stuff self.start() # tell compiler to close up the block self.current.compiler.end_named_block(block_type = type) # restore the state of the current compile self.current.ending = existingending self.current.reset_in_named_block() # give our caller the good news return True else: return False def match_text(self): current = self.current match = current.match_pos(regexp = re.compile(r""" (.*?) # anything, followed by: ( (?<=\n)(?=[%#]) # an eval or comment line, preceded by a consumed \n | (?= tag # present, so match_block() must happen first. if not self.current.match_pos(r"<%"): return False match = self.current.match_pos( regexp = re.compile(""" (.+?) # Substitution body ($1) ( \s* (? # Closing tag """, re.X | re.I | re.S)) if match: (body, extra, escape) = match.group(1, 2, 3) self.current.lines += self._count_lines(body) if extra: self.current.lines += self._count_lines(extra) self.current.compiler.substitution(body, escape) return True else: self.raise_syntax_error("'<%' without matching '%>'") def match_comp_call(self): match = self.current.match_pos(regexp = re.compile(r"<&(?!\|)", re.S)) if match: match = self.current.match_pos(regexp = re.compile(r"(.*?)&>", re.S)) if match: call = match.group(1) self.current.compiler.component_call(call) self.current.lines += self._count_lines(call) return True else: self.raise_syntax_error("'<&' without matching '&>'") else: return False def match_comp_content_call(self): match = self.current.match_pos(regexp = re.compile(r"<&\|", re.S)) if match: match = self.current.match_pos(regexp = re.compile(r"(.*?)&>", re.S)) if match: call = match.group(1) self.current.compiler.component_content_call(call) self.current.lines += self._count_lines(call) return True else: self.raise_syntax_error("'<&|' without matching '&>'") else: return False def match_comp_content_call_end(self): match = self.current.match_pos(r"") if match: self.current.compiler.component_content_call_end() return True else: return False def match_block_end(self, block_type, allow_text = True, **params): if allow_text: regex = re.compile(r"(.*?)(\n?)" % block_type, re.I | re.S) else: regex = re.compile(r"\s*(\n?)" % block_type, re.I | re.S) match = self.current.match_pos(regex) if match: if allow_text: return tuple(match.group(1,2)) else: return match.group(1) else: self.raise_syntax_error("Invalid <%%%s> section line" % block_type) def match_python_line(self): match = self.current.match_pos(r"(?<=^)([%#])([^\n]*)(?:\n|\Z)", re.M) if match: # comment if match.group(1) == '#': if self.current.lines < 2: # Magic -*- encoding: foo -*- comment m = re.search(r'coding[=:]\s*([-\w.]+)', match.group(2)) if m: self.current.compiler.magic_encoding_comment(m.group(1)) self.current.lines += 1 return True self.current.compiler.python_line(line = match.group(2)) self.current.lines += 1 return True else: return False def match_end(self): match = self.current.match_pos(regexp = self.current.ending) if match: string = match.group() self.current.lines += self._count_lines(string) if string: return string else: return True else: return False def variable_list_block(self, block_type, attributes = None, **params): # python doesnt quite do the regexp here the same way as perl (which seems to # do it, incorrectly ??? somehow perl magically knows to stop global matching beyond # the line based on the (?= ) match at the end. python doesnt. # or maybe i just goofed.). # anyway, just to get this to work, get the whole ARG block out of the source first, # then operate upon that. if theres some all-in-one way # to do it in python, or i goofed, be my guest. match = self.current.match_pos(regexp = re.compile(r""".*?(?= <\/%%%s> )""" % block_type, re.M | re.S | re.X)) if match: source = match.group() else: source = '' # operate upon the stuff inside of <%block> regexp = re.compile(r""" (?: (?: [ \t]* ( [^\W\d]\w* ) #only allows valid Python variable names [ \t]* (?: (?: # begin optional part of arg = ( [^\n]+ ) # default value, also consumes an inline comment, if any ) | (?: # an optional comment after an arg without a default [ \t]* \# [^\n]* ) )? ) | [ \t]* # a comment line \# [^\n]* | [ \t]* # just space ) (\n?) # optional newline. the ? makes finditer() go into an endless loop. """ , re.VERBOSE | re.I | re.M) # finditer has a bug here. goes into an endless loop. # but findall works. if i take the ? off the last newline there, then # finditer works, but we lose the args if it looks like <%args>foo # with no newline. *shrug* matches = regexp.findall(source) #matches = regexp.finditer(source) scope = None if attributes is not None and attributes.has_key('scope'): scope = attributes['scope'] for match in matches: (name, default, linebr) = match #(name, default, linebr) = match.group(1, 2, 3) if name: self.current.compiler.variable_declaration(block_type=block_type, name=name, default=default, scope = scope) if linebr: self.current.lines += 1 params['allow_text'] = False nl = self.match_block_end(block_type = block_type, **params) if nl: self.current.lines +=1 self.current.compiler.end_block(block_type = block_type) def key_value_block(self, block_type, **params): # do this like the variable_list_block # see that method for regexp quirks match = self.current.match_pos(regexp = re.compile(r""".*?(?= <\/%%%s> )""" % block_type, re.M | re.S | re.X)) if match: source = match.group() else: source = '' regexp = re.compile(r""" (?: [ \t]* ([\w_]+) # identifier [ \t]*[=:][ \t]* # separator (\S[^\n]*) # value ( must start with a non-space char) | [ \t]* # an optional comment \# [^\n]* | [ \t]* # just space ) (\n?) """ , re.VERBOSE | re.I) matches = regexp.findall(source) #matches = regexp.finditer(source) for match in matches: (key, value, newline) = match #(key, value) = match.group(1, 2) if key: self.current.compiler.key_value_pair(block_type = block_type, key = key, value = value) if newline: self.current.lines += 1 params['allow_text'] = False nl = self.match_block_end(block_type = block_type, **params) if nl: self.current.lines +=1 self.current.compiler.end_block(block_type = block_type) def generic_block(self, method, **params): params['allow_text'] = True (block, n1) = self.match_block_end(**params) if params.has_key('synonym_for') and params['synonym_for'] is not None: compiler_block_type = params['synonym_for'] else: compiler_block_type = params['block_type'] getattr(self.current.compiler, method)(block_type = compiler_block_type, block = block) self.current.lines += self._count_lines(block) if n1: self.current.lines +=1 self.current.compiler.end_block(block_type = compiler_block_type) def text_block(self, **params): self.generic_block('text_block', **params) def raw_block(self, **params): self.generic_block('raw_block', **params) def doc_block(self, **params): self.generic_block('doc_block', **params) def line_number(self): return self.current.lines + 1 def get_name(self): return self.current.name def _count_lines(self, text): return len(re.findall(r"\n", text)) def _current_line(self): lines = re.split(r"\n",self.current.source[0:self.current.match_position]) if len(lines) <= self.current.lines: return '' else: return lines[self.current.lines] def raise_syntax_error(self, error): raise exception.Syntax( error = error, comp_name = self.get_name(), source_line = self._current_line(), line_number = self.line_number(), source = self.current.source, file = self.input_file, source_encoding = self.current.compiler.get_encoding()) myghty-1.1/lib/myghty/objgen.py0000644000175000017500000010017310501064120015566 0ustar malexmalex# $Id: objgen.py 2133 2006-09-06 18:52:56Z dairiki $ # objgen.py - generates object files for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # import re, string, types, time from myghty.util import * import myghty.exception as exception class ObjectGenerator: """ generates myghty object files. uses the visitor pattern combined with the Compiler's flyweight pattern to efficiently loop through a tokenized page object and produce appropriate code. an ObjectGenerator is stateful and is not threadsafe. the clone() method can be used to create copies of this object for use in multiple threads. """ def visit_code_block(self, block_obj, context):raise NotImplementedError() def visit_code_single_line(self, block_obj, context):raise NotImplementedError() def visit_text_line(self, block_obj, context):raise NotImplementedError() def visit_component_block(self, compiled):raise NotImplementedError() def visit_def_block(self, compiled):raise NotImplementedError() def visit_method_block(self, compiled):raise NotImplementedError() def visit_closure_block(self, compiled):raise NotImplementedError() def visit_substitution(self, block_obj, context):raise NotImplementedError() def visit_component_call(self, block_obj, context):raise NotImplementedError() def visit_component_content_call(self, block_obj, context):raise NotImplementedError() def visit_component_content_call_end(self, block_obj, context):raise NotImplementedError() def generate(self, compiler, compiled, stream):raise NotImplementedError() def clone(self, **params): """creates a clone of this ObjectGenerator. allow the Prototype pattern to be used in creating generators for other threads.""" raise NotImplementedError() class PythonGenerator: def clone(self, **params): """creates a clone of this ObjectGenerator. allow the Prototype pattern to be used in creating generators for use in other threads.""" return PythonGenerator() def generate(self, compiler, compiled, stream): self.printer = PythonPrinter(stream) self.compiler = compiler self.compiled = compiled self.textbuffer = '' self.indentstack = [] self.in_block = False self.last_asserted_line = -1 self.uses_shared = (compiled.block_has_code("shared")) self.uses_threadonce = (compiled.block_has_code("threadonce")) # call the initial accept_visitor that will start the # traversal of the whole compiled object compiled.accept_visitor(self) self.printer.close() def visit_code_block(self, block_obj, context): """handles a <%python> block, adjusting indent to match the current indent level.""" self._flush_text() lines = block_obj.get_lines(context) self.printer.print_comment("BEGIN CODE BLOCK") self._assert_line_comment(block_obj, context) linenum = block_obj.get_line_number(context) for line in lines: line = self.compiler.post_process_python(line) #self.printer.add_normalized_block_line(self._line_comment(linenum)) #linenum += 1 self.printer.add_normalized_block_line(line) self.printer.print_comment("END CODE BLOCK") def visit_text_line(self, block_obj, context): """handles regular lines of text. Text line printing is buffered so that consecutive lines of text can be combined into a single print statement""" if not self.textbuffer: self._assert_line_comment(block_obj, context) self.textbuffer += self.compiler.post_process_text(block_obj.get_line(context)) def visit_component_call(self, block_obj, context): self._generate_component_call(block_obj, context, content = False) def visit_component_content_call(self, block_obj, context): self._print_python("def %s():" % "component_content", block_obj, context) self._assert_line_comment(block_obj, context) self._cc_line = block_obj.get_line_number(context) def visit_component_content_call_end(self, block_obj, context): self._generate_component_call(block_obj, context, content = True) def _generate_component_call(self, block_obj, context, content = False): name = block_obj.get_component(context) self._assert_line_comment(block_obj, context) if self.closures.has_key(name): funcname = 'm.closure_with_content' args = [name] else: funcname = 'm.execute_component' args = ["'%s'" % name] if block_obj.get_args(context): args.append("args = argdict(%s)" % block_obj.get_args(context)) if content is True: args.append("content = component_content") if block_obj.get_line_number(context) == self._cc_line: self._print_python("pass", block_obj, context) self._print_python(["", "%s(%s)" % (funcname, string.join(args, ", "))], block_obj, context) else: self._print_python("%s(%s)" % (funcname, string.join(args, ", ")), block_obj, context) def visit_code_single_line(self, block_obj, context): self._assert_line_comment(block_obj, context) self._print_python(block_obj.get_line(context), block_obj, context, normalize_indent = True) def visit_substitution(self, block_obj, context): escapes = block_obj.get_escapes(context) if escapes: code = "m.write(m.apply_escapes(%s, %s))" % (block_obj.get_line(context), repr(escapes)) else: code = "m.write(%s)" % block_obj.get_line(context) self._assert_line_comment(block_obj, context) self._print_python(code, block_obj, context) def visit_component_block(self, compiled): self._generate_module_header(compiled) for block in compiled.get_named_blocks(): block.accept_visitor(self) self._generate_component_class(compiled) def visit_def_block(self, compiled): self._generate_component_class(compiled, is_subcomponent = True, is_method = False) def visit_method_block(self, compiled): self._generate_component_class(compiled, is_subcomponent = True, is_method = True) def visit_closure_block(self, block_obj, context): compiled = block_obj.get_compiled(context) indent = self.printer.indentstring * self.printer.indent + " " * len(compiled.name) self.closures[compiled.name] = True def format_arg(arg): if not arg.required: return "\n%s%s = %s, " % (indent, arg.name, arg.default) else: return "\n%s%s, " % (indent, arg.name) args = [] reqarg = [arg for arg in compiled.args.values() if arg.required] if len(reqarg): args.append(string.join(map(format_arg, reqarg))) nonreqarg = [arg for arg in compiled.args.values() if not arg.required] if len(nonreqarg): args.append(string.join(map(format_arg, nonreqarg))) args += ["\n" + indent + "**_comp_params"] code = "def %s(%s):" % (compiled.name, string.join(args)) self._print_python(code, block_obj, context) self._generate_run_body(compiled) # generation methods. each spits out various parts of the module # file. they are broken up to handle some freaky rearrangements that occur # based on if the component uses <%shared>, <%filter>, <%cleanup> etc. # <%shared> is the craziest one by far. def _generate_module_header(self, compiled): """generates the top header of the file, the <%once> section, and the dynamic subs if the module has a <%shared> block""" self.printer.print_comment("File: %s CompilerID: %s Timestamp: %s" % (compiled.fileid, self.compiler.get_object_id(), time.asctime())) if compiled.encoding is not None: self.printer.print_comment("-*- encoding: %s -*-" % compiled.encoding) self.printer.print_python_line("from myghty.component import Component, FileComponent, SubComponent") self.printer.print_python_line("import myghty.request as request") self.printer.print_python_line("import myghty.args") self.printer.print_python_line("from myghty.util import *") self.printer.print_python_line("_CREATION_TIME = %d" % time.time()) self.printer.print_python_line("_MAGIC_NUMBER = %s" % repr(self.compiler.get_magic_number())) if compiled.block_has_code("once"): self._output_block(compiled, "once") if self.uses_threadonce: self._push_indent() self._generate_python_def(compiled, "thread_local_initializer", append_globals = True, append_component = False, args = ['self']) self._output_block(compiled, "threadonce") if self.uses_shared: self._push_indent() self._generate_python_def(compiled, "request_local_initializer", append_globals = True, append_component = False, args = ['self']) self._output_block(compiled, "shared") self._generate_dynamic_subs(compiled) if self.uses_shared: self._pop_indent() self.printer.print_raw_line("\n") self.printer.print_python_line("return request_local_initializer") self._pop_indent() elif self.uses_shared: self._push_indent() self._generate_python_def(compiled, "request_local_initializer", append_globals = True, append_component = False, args = ['self']) self._output_block(compiled, "shared") self._generate_dynamic_subs(compiled) self._pop_indent() def _generate_dynamic_subs(self, compiled): for block in compiled.get_named_blocks() + [compiled]: self._push_indent() self._generate_run_define(block, methodname = ("run_%s" % self._format_class_name(block.name)), is_dynamic_method = True) self._generate_run_body(block) self._pop_indent() self.printer.print_python_line("return {") for block in compiled.get_named_blocks() + [compiled]: self.printer.print_python_line(" '%s' : %s," % (self._format_class_name(block.name),("run_%s" % self._format_class_name(block.name)) )) self.printer.print_python_line("}") def _generate_component_class(self, compiled, **params): """generates the class definition of a component, all methods within the class, and a static accessor function if the component is the lead component""" self._push_indent() self._generate_init(compiled, **params) dynamic = (self.uses_shared or self.uses_threadonce) self._generate_run_define(compiled, add_dynamic_call = dynamic) if not dynamic: self._generate_run_body(compiled) self._pop_indent() if compiled.in_main: self.printer.print_raw_line("\n") self.printer.print_python_line("def get_component(interpreter, csource): return %s(interpreter, csource)" % self._format_class_name(compiled.name)) def _generate_subcomponent_constructor(self, compiled, is_method): """generates a constructor call to create a subcomponent (def or method)""" return "'%s' : %s('%s', self, is_method = %s)" % ( compiled.name, self._format_class_name(compiled.name), compiled.name, repr(is_method) ) def _generate_init(self, compiled, is_subcomponent = False, is_method = False): """generates the __init__ method for a component """ if is_subcomponent: superclass = "SubComponent" else: superclass = "FileComponent" self.printer.print_raw_line("\n") self.printer.print_python_line("class %s(%s):" % (self._format_class_name(compiled.name), superclass)) self.printer.print_python_line("def do_component_init(self):") self.printer.print_python_line("self.defs = {%s}" % string.join(map(lambda c: self._generate_subcomponent_constructor( c, False), compiled.get_named_blocks("def")), ",\n")) self.printer.print_python_line("self.methods = {%s}" % string.join(map(lambda c: self._generate_subcomponent_constructor( c, True), compiled.get_named_blocks("method")), ",\n")) self.printer.print_python_line("self.flags = %s" % self._repr_key_value(compiled.flags)) self.printer.print_python_line("self.attr = %s" % self._repr_key_value(compiled.attr)) self.printer.print_python_line("self.arguments = [%s]" % string.join(["myghty.args." + repr(arg) for arg in compiled.args.values()], ",\n")) self.printer.print_python_line("self.creationtime = _CREATION_TIME") if compiled.in_main: if self.uses_threadonce: self.printer.print_python_line("self.thread_local_initializer = thread_local_initializer") elif self.uses_shared: self.printer.print_python_line("self.request_local_initializer = request_local_initializer") if compiled.block_has_code("filter"): self._generate_python_def(compiled, "filter", append_globals = True, append_component = False, args = ['f']) self._output_block(compiled, "filter") self.printer.print_python_line("") self.printer.print_python_line("self.filter = self._init_filter_func(filter)") elif compiled.flags.has_key('trim'): self.printer.print_python_line("self.filter = self._init_filter_func()") self.printer.print_python_line("") self.printer.print_python_line("def uses_request_local(self):return %s" % repr(self.uses_shared and compiled.in_main)) self.printer.print_python_line("def uses_thread_local(self):return %s" % repr(self.uses_threadonce and compiled.in_main)) def _repr_key_value(self, keyvalue): return ("{" + string.join(map(lambda k: "'%s' : %s" % (k, keyvalue[k]), keyvalue.keys()), ",\n") + "}") def _generate_run_define(self, compiled, methodname = "do_run_component", is_dynamic_method = False, add_dynamic_call = False): self.closures = {} (args, indent) = self._generate_method_args(compiled, methodname, append_globals = (is_dynamic_method or not add_dynamic_call), append_component = (is_dynamic_method or not add_dynamic_call)) if not is_dynamic_method: defargs = ['self, '] + args else: defargs = args self.printer.print_comment("BEGIN BLOCK args") if compiled.start_blocks['args'] != -1: self.printer.print_comment(self._line_comment(compiled.start_blocks['args'])) self.printer.print_python_line("def %s(%s\n%s):" % (methodname, string.join(defargs), indent)) self.printer.print_comment("END BLOCK args") if add_dynamic_call: self.printer.print_python_line("return self._call_dynamic('%s', %s)" % ( self._format_class_name(compiled.name), string.join(args) ) ) def _generate_python_def(self, compiled, methodname, append_globals, append_component, args = None): (genargs, indent) = self._generate_method_args(compiled, methodname, append_globals = append_globals, append_component = append_component) if args is not None: genargs = [arg + ", " for arg in args] + genargs self.printer.print_python_line("def %s(%s\n%s):" % (methodname, string.join(genargs), indent)) def _generate_method_args(self, compiled, methodname, append_globals, append_component): indent = self.printer.indentstring * self.printer.indent + " " * len(methodname) # we're trying to print the args with linebreaks, for an <%ARGS> section that was defined like # "g ='foo' #define g to be foo" , but this doesnt really work anyway since you need to get the commas # in between where the comment starts.... def format_arg(arg): if not arg.required: return "\n%s%s = %s, " % (indent, arg.name, arg.default) else: return "\n%s%s, " % (indent, arg.name) args = ['m, ', 'ARGS, '] if append_globals: # append global arguments to arg list. the most ordinary is # "r" for the HTTP request. "s" for session would be nice as well. args += map(lambda x: "%s, " % x, compiled.compiler.allow_globals) if append_component: reqarg = [arg for arg in compiled.args.values() if arg.required] if len(reqarg): args.append(string.join(map(format_arg, reqarg))) nonreqarg = [arg for arg in compiled.args.values() if not arg.required] if len(nonreqarg): args.append(string.join(map(format_arg, nonreqarg))) args += ["\n" + indent + "**_comp_params"] else: args += ['**_comp_params'] return (args, indent) def _generate_run_body(self, compiled): has_blocks = False # generate blocks in order if compiled.block_has_code("cleanup"): # increments indent one more level self.printer.print_python_line("try:") for key in ('init', 'body', 'cleanup'): if not compiled.block_has_code(key): continue if key == 'cleanup': self.printer.print_python_line("finally:") self.printer.print_python_line("pass") self._output_block(compiled, key) has_blocks = True if not has_blocks: self.printer.print_python_line("pass") self.printer.print_python_line("") if compiled.block_has_code("cleanup"): # decrement indent one more level self.printer.print_python_line("") def _output_block(self, compiled, block_type): """traverses the lines of flyweight objects in a block object (such as init, once, body) and outputs python code""" self._push_indent() iter = compiled.get_block_iterator(block_type) has_code = False outermost_block = False if not self.in_block: self.printer.print_comment("BEGIN BLOCK %s" % block_type) self.in_block = True outermost_block = True for code in iter: has_code = True code.accept_visitor(self, iter) if has_code: self._flush_text() if outermost_block: self.printer.print_comment("END BLOCK %s" % block_type) self.in_block = False self._pop_indent() self.printer.clear_whitespace_stack() return has_code def _print_python(self, code, block_obj, context, normalize_indent = False): """prints python code, performing the convenience steps of flushing the existing text buffer, printing line number tracking comments, and post processing of python code""" self._flush_text() self._assert_line_comment(block_obj, context) if type(code) == types.ListType: for c in code: self.compiler.post_process_python(c) self.printer.print_python_line(c, normalize_indent = normalize_indent) else: self.compiler.post_process_python(code) self.printer.print_python_line(code, normalize_indent = normalize_indent) def _assert_line_comment(self, block_obj, context): if block_obj.get_line_number(context) != self.last_asserted_line: self.printer.print_comment(self._line_comment(block_obj.get_line_number(context))) self.last_asserted_line = block_obj.get_line_number(context) def _line_comment(self, linenumber): return "SOURCE LINE %s" % (linenumber) def _flush_text(self): if not self.textbuffer: return buffer = self.compiler.post_process_text(self.textbuffer) # escape single quotes and backslashes buffer = re.sub(r"(['\\])", r"\\\1", buffer) if self.compiler.disable_unicode: self.printer.print_python_line("m.write('''%s''')" % buffer) else: self.printer.print_python_line("m.write(u'''%s''')" % buffer) self.textbuffer = '' def _format_class_name(self, name): name = re.sub(r"\.", "_", name) name = re.sub(r"[^\d\w_]", "", name) return "_" + name def _push_indent(self): """stores the current indentation level of the Python printer on a stack.""" self.indentstack.append(self.printer.indent) def _pop_indent(self): """restores the indentation level of the Python printer to what we last stored.""" indent = self.indentstack.pop() self.printer.indent = indent class PythonPrinter: """prints Python code, keeping track of indentation level. PythonPrinter has two basic modes, "print_python_line" mode which prints one line at a time and calculates whitespace on the fly, and "add_normalized_block_line" mode where you add lines to a buffer, and then at the end print them all out as they were stored, except relative to the current indent.""" def __init__(self, stream): # the indentation counter self.indent = 0 # a stack storing information about why we incremented # the indentation counter, to help us determine if we # should decrement it self.indent_detail = [] # the string of whitespace multiplied by the indent # counter to produce a line self.indentstring = " " # a stack of whitespace we pulled from "normalized" # Python lines to track when the indentation counter should # be incremented or decremented self.spacestack = [] # the stream we are writing to self.stream = stream # a list of lines that represents a buffered "block" of code, # which can be later printed relative to an indent level self.line_buffer = [] # boolean indicating if we are in "print_python_line" mode or # "add_normalized_block_line" mode self.in_indent_lines = False self._reset_multi_line_flags() def print_python_line(self, line, normalize_indent = False, is_comment = False): """prints a line to the output buffer, preceded by a blank indentation string of proportional size to the current indent counter. If the line ends with a colon, the indentation counter is incremented after printing. If the line is blank, the indentation counter is decremented. if normalize_indent is set to true, the line is printed with its existing whitespace "normalized" to the current indentation counter; additionally, its existing whitespace is measured and compared against a stack of whitespace strings grabbed from other normalize_indent calls, which is used to adjust the current indentation counter. basically, "normalize_indent" is used with % lines in a template, and non-normalize_indent is used by the objectgenerator's internally generated lines of code. """ if not self.in_indent_lines: self.flush_normalized_block() self.in_indent_lines = True decreased_indent = False if ( re.match(r"^\s*#",line) or re.match(r"^\s*$", line) ): hastext = False else: hastext = True if normalize_indent: # determine the actual amount of whitespace preceding the # line of code. check it against a stack of whitespace # and push it on if it is of greater indent and also # has some non-whitespace text, or pop # the existing whitespace if it is of lesser indent line = string.expandtabs(line) space = re.match(r"^([ ]*)", line).group(1) # see if we have any whitespace already if len(self.spacestack) == 0: if hastext: self.spacestack.append(space) else: if len(space) > len(self.spacestack[-1]): if hastext: self.spacestack.append(space) elif len(space) < len(self.spacestack[-1]): self.spacestack.pop() if self.indent > 0: self.indent -=1 if len(self.indent_detail) == 0: raise exception.Compiler("Too many whitespace closures") self.indent_detail.pop() decreased_indent = True line = string.lstrip(line) else: # not normalizing indentation. no whitespace should be present. space = None # see if this line should decrease the indentation level if (not decreased_indent and not is_comment and (not hastext or self._is_unindentor(line, space)) ): if self.indent > 0: self.indent -=1 # if the indent_detail stack is empty, the user # probably put extra closures - the resulting # module wont compile. if len(self.indent_detail) == 0: raise exception.Compiler("Too many whitespace closures") self.indent_detail.pop() # write the line self.stream.write(self._indent_line(line) + "\n") # see if this line should increase the indentation level. # note that a line can both decrase (before printing) and # then increase (after printing) the indentation level. if re.search(r":[ \t]*(?:#.*)?$", line): # increment indentation count, and also # keep track of what the keyword was that indented us, # if it is a python compound statement keyword # where we might have to look for an "unindent" keyword match = re.match(r"^\s*(if|try|elif|while|for)", line) if match: # its a "compound" keyword, so we will check for "unindentors" indentor = match.group(1) self.indent +=1 self.indent_detail.append((space, indentor)) else: indentor = None # its not a "compound" keyword. but lets also # test for valid Python keywords that might be indenting us, # else assume its a non-indenting line m2 = re.match(r"^\s*(def|class|else|elif|except|finally)", line) if m2: self.indent += 1 self.indent_detail.append((space, indentor)) def _is_unindentor(self, line, space): """looks at the keyword and its whitespace that was most recently responsible for incrementing the indent, and compares against the keyword (if any) and whitespace for the given line, to see if an unindent operation should occur""" # no indentation detail has been pushed on; return False if len(self.indent_detail) == 0: return False (indspace, indentor) = self.indent_detail[-1] # the last indent keyword we grabbed is not a # compound statement keyword; return False if indentor is None: return False # the indentation from the last indent keyword we # grabbed does not match this current keword; return False if indspace != space: return False # if the current line doesnt have one of the "unindentor" keywords, # return False match = re.match(r"^\s*(else|elif|except|finally)", line) if not match: return False # whitespace matches up, we have a compound indentor, # and this line has an unindentor, this # is probably good enough return True # should we decide that its not good enough, heres # more stuff to check. #keyword = match.group(1) # match the original indent keyword #for crit in [ # (r'if|elif', r'else|elif'), # (r'try', r'except|finally|else'), # (r'while|for', r'else'), #]: # if re.match(crit[0], indentor) and re.match(crit[1], keyword): return True #return False def print_comment(self, comment): self.print_python_line("# " + comment, is_comment = True) def _indent_line(self, line, stripspace = ''): return re.sub(r"^%s" % stripspace, self.indentstring * self.indent, line) def _reset_multi_line_flags(self): (self.backslashed, self.triplequoted) = (False, False) def _in_multi_line(self, line): # we are only looking for explicitly joined lines here, # not implicit ones (i.e. brackets, braces etc.). this is just # to guard against the possibility of modifying the space inside # of a literal multiline string with unfortunately placed whitespace current_state = (self.backslashed or self.triplequoted) if re.search(r"\\$", line): self.backslashed = True else: self.backslashed = False triples = len(re.findall(r"\"\"\"|\'\'\'", line)) if triples == 1 or triples % 2 != 0: self.triplequoted = not self.triplequoted return current_state def print_raw_line(self, line, indentlevel = 0): """adds a line to the deffered print buffer with the "multiline" flag set to true, so that this line will come out with its original indentation intact.""" self.in_indent_lines = False self.line_buffer.append([True, line]) def add_normalized_block_line(self, line): """adds a line to the deferred print buffer. When the deferred print buffer is flushed, its lines will be scanned for the initial whitespace amount. lines with that much or greater whitespace will have their whitespace adjusted to line up with the whitespace that has been established by the last print_indent_line call. Lines within continued triplequotes and backslashes will remain unaffected.....if all goes well. This method is basically used for <%python> blocks. """ self.in_indent_lines = False # append a record consisting of a multiline flag # as well as the line itself self.line_buffer.append([False, line]) def flush_normalized_block(self): stripspace = None self._reset_multi_line_flags() for entry in self.line_buffer: if self._in_multi_line(entry[1]): self.stream.write(entry[1] + "\n") else: entry[1] = string.expandtabs(entry[1]) if stripspace is None and re.search(r"[^ \t]", entry[1]): stripspace = re.match(r"^([ \t]*)", entry[1]).group(1) self.stream.write(self._indent_line(entry[1], stripspace) + "\n") self.line_buffer = [] self._reset_multi_line_flags() def clear_whitespace_stack(self): self.spacestack = [] def close(self): self.flush_normalized_block() myghty-1.1/lib/myghty/paste/0000755000175000017500000000000010501065765015102 5ustar malexmalexmyghty-1.1/lib/myghty/paste/__init__.py0000644000175000017500000000000010501064116017166 0ustar malexmalexmyghty-1.1/lib/myghty/paste/templates.py0000644000175000017500000000063310501064116017441 0ustar malexmalexfrom paste.script.templates import Template class RoutesTemplate(Template): _template_dir = '../paster_templates/routes' summary = 'Routes Template' class SimpleTemplate(Template): _template_dir = '../paster_templates/simple' summary = 'Simple Template' class MCTemplate(Template): _template_dir = '../paster_templates/modulecomponents' summary = 'Module Component Template' myghty-1.1/lib/myghty/paste/wsgiapp.py0000644000175000017500000000134210501064116017113 0ustar malexmaleximport sys, os from paste.util import import_string import myghty.http.WSGIHandler as WSGIHandler from myghty.resolver import * from myghty.ext.routeresolver import RoutesResolver def make_myghty_app(global_conf, package_name=None, root_path=None, **app_conf): if package_name: package = package_name if isinstance(package, (str, unicode)): package = import_string.simple_import(package+'.webconfig') config = package.config def myghtyapp(environ, start_response): return WSGIHandler.handle(environ, start_response, **config ) return myghtyapp myghty-1.1/lib/myghty/paster_templates/0000755000175000017500000000000010501065765017342 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/__init__.py0000644000175000017500000000000010501064120021421 0ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/modulecomponents/0000755000175000017500000000000010501065765022735 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/0000755000175000017500000000000010501065765024456 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/__init__.py0000644000175000017500000000000110501064117026544 0ustar malexmalex myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/components/0000755000175000017500000000000010501065765026643 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/components/autohandler0000644000175000017500000000043710501064117031066 0ustar malexmalex <% m.request_component.attributes('title') %> <& /header.myc &> <& /leftnav.myc &> % m.call_next() <& /footer.myc &> <%attr> Welcome to Myghty myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/components/footer.myc0000644000175000017500000000006310501064117030640 0ustar malexmalex myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/components/header.myc0000644000175000017500000000031010501064117030565 0ustar malexmalex
Module Components Layout % if s.has_key('user'):
Welcome <% s['user'].fullname %>!
%
myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/components/leftnav.myc0000644000175000017500000000134210501064117031002 0ustar malexmalex
<&contextlink, url="/", text="Home", context="home"&> % if s.get('user', None) is not None: logout % else: <&contextlink, url="/login/", text="Login", context="login"&> %
<%def contextlink trim="both"> <%doc>Displays a hyperlink with the given url and text. Can also be given a "context" string, which is compared against the "context" attribute of the current request_component. If they match, the hyperlink is displayed simply as bolded text. <%args> url text context = None % if context is not None and context == m.request_component.attributes.get('context', None): <% text %> % else: <% text %> % myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/htdocs/0000755000175000017500000000000010501065765025742 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/htdocs/myghty_small.png0000644000175000017500000000604410501064117031153 0ustar malexmalex‰PNG  IHDR²F_W>UgAMAÖØÔOX2tEXtSoftwareAdobe ImageReadyqÉe<`PLTEÕýõÊõùæýúRY]³åÿ÷þý¼êý "Æíþ¨Ýý×××­áÿv“¢´ÓÚ–¢©¹çþ¶èýet{’ºÏšÇß.59ïïïÖôüÂñú½¼¼Òéë>IP>?@±ãÿÿÿÿ(nx tRNSÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ\\í IDATxÚbr €pˆ³²²21ˆ3000J ° ²I02Š‹3…ÜÉÄ€ËÅLLLŒŒŒ@'³±€ §Äàp3@1àr0#'›ÐÉl< + v50¤ÜÍÄ€ÃÁl,<<‚Œ Œ‚²@÷‚Èá,‚œÒÄ€ÍÅŒ@³ 0°A ÐÑ,@1F¦u3@1`:˜è(9ènƒÌaá‘eË1 ¨›ˆÃÅ‚‚ l‚<\‚P60ÊrÉJ€@8Ž T'ƒ‚˜EVPæJA.'óðH€y@G3\6 T‹ƒsÜ•‚ÈP€„2(™³áÀ8 €P\ r¨ » H 8Ê 7K°pP8J ‚K2„+x¸€™ ái,#!Ë#À90á @ È.¾°€ºR€ èd&°“i†‡‹ædP* p DYÁÀ­4ÒØýL çƒÒ20”a2‚@µâád€B8™IJ‚ÀZR‹ƒ*6N`% æprÂd€E3°yŽs Ü @ ˆ¬ÇjH°Ëd`[oÀ Æ(ÎÀÈ b â â‚À©Ä"9rÖºAš„yx¸¸¸Ø€5†8dƒ5'ˆ LÓ<ƒ&0IœN “ ÊhNæE' 0„x!NÕ4R `×~‚ÌÐTdVlNæÇRHc8̰'ƒ'óÉËØÉ„”–!Ù”0¸Ø@Ñ)r¸ØÉ ¾ ++(ì™@Å2<óñ#»ÙÉÌòÄ8)˜¹ñÚ„Ê,à?h(ÒÕfa…2ÈuüBàö$a€jpU( eP†YÉÍÉbÄ9ÌB(¥ x(°l²s;¨ÀBZ€““”–9A230¨ùÅ9AN •1‚ŒâÖ# ²šd’(rþ‘D)—‰r2(9€ƒY oæ“— x‰Vp)ÀÃÂ)nÉ›ÉÐbUœS@Ta‹‹³•r›ÿ‚‚ ÃËu„ EN–„3?¾Z•À[E¾(0•»û¬ üÄN r˜äd`“T³€Òˆâ Š“aÁ ´E_ €@NædCu²,Š“9E å='ÄÉLâ\Àb,Ê"™Y0,CŽ“!ÁÌTÆ`i± ZrPÇÌu2¨<å`ç”'  “Á¾3(ó‰Èœ,‡ì8Ò ñ(3¡î"@Ì)ˆÛÉâœ<¼À–;’“!Å68˜A™O–… –ý¸™Ÿ°“1³(˜e Õ:n@(M|¡ ¬=@=)x€8™Ú>ã.°BNn3!'Ë`-È  An¼N pGJ6æ‰5”¹¸¸@ [âdDåIã<œâˆF‘$¤I$JÈÉ <-*ž«€v#ð7ñÒ]å”Å eH!Ç ò§8hP€âdFèˆ8˜…dÄÁN†¶1Á¤ Áœv8„øù…8È!‡·y`Aw3 §8#P;#'ˆƒFº@%´(#"åƒÃD>bË oM2‹t2rÛÉÉürx›`@¡Œ”žÿêwÅ€yLœTû °À£ä:¤±|nH8sðQ.#õ`8øå‰kÛC@Á¸0œ )¥ qЀÁêw3ÊÂG=eeA%?°?ŽÇ’’D®JðIvffvvnôî­mFÀîd.ÐØÉ àFv2¨?% ®s0"o,NŒ˜Ž7@AkYÑ3 ÜÝÀòÔ…ô»Q*³„y°Î—€: ¢d8Yšˆ@– Ø8¨­ƒ[J‚œâ @ç °ÀÓ±, (§H–,jY1fò†)ø‰Ò@0'³bÈ)„ Ü#”åB1¸±Äłſ#hºä—“#&´0R¸¬&¬ €Ó;œ YI ^QHÙ ,Ë‚HÐKRpp“âZI9Qbµb è$¼NM ‚UÃÊS~.ä)fä~¾(I.†UñÄy €&Wq& Y“ÁóÛ Æ=´`áA÷c—U%ü$¦ ˆ“9؉JL„€B™vçÄÊðØÍæÃ›– ”Å â8ÂÊ ZE@hKHp¸Y@Ø÷D-¯ÊÅò„¾P‡S›£YÀÓÖÈ2ÀÆé@­Ô ôåP ¥:Ø :”T!À)>`Ë¡sÑ™8§A'ÜÂ"  ,Kûp4R*æÐU”„u¥¸¸°M«•Ä9A „c™ª¸8'–Œ( l‚Š ðBU€Â¾˜ìh 7 °’Ä@¯¬ ÜK®Å@ j"C!hxó»X €ä‡ !èd€‚N0ãöõ¥°C¹cIEND®B`‚myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/htdocs/style.css0000644000175000017500000000077510501064117027613 0ustar malexmalexbody { font-family: verdana, arial, sans-serif; font-size: 12pt; } .main { margin:10px; padding:10px; } .welcomeuser { font-size:11pt; font-weight:normal; text-align:right; } .leftnav { border: 1px solid; margin:10px; padding:10px; float:left; width:80px; height:250px; } .header { border: 1px solid; margin:10px; padding:10px; font-size:16pt; font-weight:bold; } .footer { border: 1px solid; padding:10px; text-align:center; margin:10px; clear:left; } .red { color:red; } myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/lib/0000755000175000017500000000000010501065765025224 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/lib/controller.py0000644000175000017500000000402410501064117027747 0ustar malexmalexclass SimpleController(object): """a Myghty controller that delivers a welcome page and a login system.""" def do_component_init(self, *args, **kwargs): pass def __call__(self, m, **kwargs): """the default callable on this SimpleController. useful with module-path resolution to produce a result when no further path tokens exist.""" if m.request_path != '/': m.abort(404) self.welcome_page(m, **kwargs) def welcome_page(self, m, **kwargs): m.subexec("index.myt") def login(self, m, s, username=None, password=None, cmd=None, **kwargs): """displays a login page and processes login information. note the presence of the 's' argument which represents the session. the 's' variable is configured in the webconfig.py file via 'use_session=True'.""" if username is not None: user = _validate_user(username, password) if user is not None: s['user'] = user s.save() self.welcome_page(m, **kwargs) else: m.subexec("login.myt", username=username, failed=True) elif cmd == 'logout': del s['user'] s.save() m.subexec("logout.myt") else: m.subexec("login.myt") # establish an instance of SimpleController as 'index' index = SimpleController() # private objects and methods, which are preceded by an underscore # to mark as private when using the ResolvePathModule resolver class _User(object): """represents a logged-in user.""" def __init__(self, username, fullname): self.username = username self.fullname = fullname def _validate_user(username, password): """given a username and password, returns a valid User object or None if no user could be found. """ # some stub code. if username == 'testuser' and password == 'password': return _User('testuser', 'Test User') else: return None myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/templates/0000755000175000017500000000000010501065765026454 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/templates/index.myt0000644000175000017500000000464510501064117030315 0ustar malexmalex<%attr> context='home' title="Welcome to Module Components"
welcome to the slightly more complicated myghty app !

This template illustrates a simple controller setup, including a login page, and a context-sensitive navigation bar.

The page you are viewing is "templates/index.myt". It works similarly to index.myt inside the "myghty_simple" template; the page is "wrapped" by an autohandler file, "components/autohandler", and the autohandler pulls in "components/header.myc", "components/footer.myc" and "components/leftnav.myc" to create the finished page.

However, unlike the myghty_simple setup, every request is served by a controller module, which is "lib/controller.py". Path names in the URL are mapped to individual method names upon one or more object instances inside the controller via the ResolvePathModule resolver rule. Each controller forwards onto an ".myt" template for display.

The directories laid out for this approach are:

  • lib/ - stores Python modules, including controller modules that handle requests. Currently there is just one module, "controller.py", which contains one controller object, an instance of a class named "SimpleController". SimpleController has two methods, one for the homepage and one for the login system. While this is a very simple setup, there can be any number of controller modules, objects, and methods.
  • templates/ - stores .myt templates which are invoked by controller modules. These templates also can include a context attribute at the top, which gives a clue to the left navigation bar about the currently displaying page, as well as a title attribute, which overrides the titlebar text set up by the autohandler.
  • components/ - stores .myc components, which are components used by the templates. ".myc" vs. ".myt" is just a naming convention that makes it easier to distinguish between top-level templates and smaller components.
  • htdocs/ - stores all static content, such as .css, .png, etc. The Myghty interpreter doesn't even know about /htdocs, as static content is handled by the underlying Paste server (or whatever static webserver is being used).

Note that by keeping all templated files in a different set of directories than the static directory, there is no chance of a templated file ever being served "raw" by the webserver.

myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/templates/login.myt0000644000175000017500000000072210501064117030306 0ustar malexmalex<%attr> context='login' title = 'Login' <%args> failed = None
% if failed is True: %
Username: ('testuser')
Password: ('password')
Login failed !
myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/templates/logout.myt0000644000175000017500000000013410501064117030504 0ustar malexmalex<%attr> context="login"

You have been logged out.

Home myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/webconfig.py0000644000175000017500000000354610501064117026771 0ustar malexmaleximport os, sys from myghty.resolver import * publish_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.extend((publish_dir + '/lib',) ) # The following options are passed directly into Myghty, so all configuration options # available to the Myghty handler are available for your use here config = {} config['data_dir'] = os.path.join(publish_dir, 'cache') config['component_root'] = [ {'templates' : os.path.join(publish_dir, 'templates')}, {'comp' : os.path.join(publish_dir, 'components')}, ] config['use_session'] = True config['resolver_strategy'] = [ # request-level resolution ConditionalGroup(context='request', rules=[ # bounce anything that is not "/" or ".myt" down to silent "not found" # controller, which will forward out of Myghty to the Paste file handler ConditionalGroup(regexp=r'(?!.*(/|\.myt)$)', rules = [ NotFound(silent=True) ]), # everything else request-level is handled by the controller. # here we are using ResolvePathModule to automatically match pathnames # to methods on objects inside the controller. Other options include # the more explicit ResolveModule as well as the new Routes resolver. ResolvePathModule('controller'), # ...or not found. NotFound(), ]), # these rules then handle all other component calls, # including subrequest, component, and inherited PathTranslate(), ResolveDhandler(), URICache(), ResolveUpwards(), ResolvePathModule(), ResolveModule(), ResolveFile() ] myghty-1.1/lib/myghty/paster_templates/modulecomponents/+package+/wsgiapp.py_tmpl0000644000175000017500000000042010501064117027520 0ustar malexmaleximport os, sys from myghty.paste import wsgiapp root = os.path.dirname(os.path.abspath(__file__)) def make_app(global_conf, **kw): return wsgiapp.make_myghty_app( global_conf, package_name='${package}', root_path=root, **kw) myghty-1.1/lib/myghty/paster_templates/modulecomponents/server.conf_tmpl0000644000175000017500000000111010501064120026117 0ustar malexmalex # defines the application when you run "paster serve" [server:main] use = egg:PasteScript#wsgiutils host = 127.0.0.1 port = 8000 # defines a composite application. first call myghty app via ${package}, then if it 404's # fall thru to "static", which serves non-myghty files [composit:main] use = egg:Paste#cascade app1 = ${package} app2 = static # define static application, which is Paste StaticURLParser [app:static] use = egg:Paste#static document_root = ${package}/htdocs # define Myghty application, which is the app name ${package} [app:${package}] use = egg:${package} myghty-1.1/lib/myghty/paster_templates/modulecomponents/setup.py_tmpl0000644000175000017500000000065210501064120025466 0ustar malexmalexfrom setuptools import setup, find_packages setup( name=${repr(project)|empty}, version=${repr(version)|empty}, #description="", #author="", #author_email="", #url="", install_requires=["Myghty >= 0.98d"], packages=find_packages(), package_data = {'templates' : ["*.myt", "*.js", "*.html"]}, entry_points=""" [paste.app_factory] main=${package}.wsgiapp:make_app """, ) myghty-1.1/lib/myghty/paster_templates/routes/0000755000175000017500000000000010501065765020663 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/routes/+package+/0000755000175000017500000000000010501065765022404 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/routes/+package+/__init__.py0000644000175000017500000000000010501064117024471 0ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/routes/+package+/controllers/0000755000175000017500000000000010501065765024752 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/routes/+package+/controllers/__init__.py0000644000175000017500000000000010501064117027037 0ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/routes/+package+/controllers/application_controller.py0000644000175000017500000000034310501064117032060 0ustar malexmalexfrom routes.util import redirect_to, url_for class ApplicationController(object): def do_run_component(self, m, r, s, **params): m.global_args['url_for'] = url_for m.global_args['redirect_to'] = redirect_tomyghty-1.1/lib/myghty/paster_templates/routes/+package+/lib/0000755000175000017500000000000010501065765023152 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/routes/+package+/lib/__init__.py0000644000175000017500000000000010501064117025237 0ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/routes/+package+/public/0000755000175000017500000000000010501065765023662 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/routes/+package+/public/index.html_tmpl0000644000175000017500000000451410501064117026705 0ustar malexmalex Myghty Default Page

Welcome to your Myghty Web Application

Weren't expecting to see this page?

The ${package}/public/ directory is searched for static files before your controllers are run. Remove this file (${package}/public/index.html) and edit the routes in ${package}/webconfig.py like so:

 m.connect('', controller='entry', action='view', id=10)
Please Note: If this is your first time using Myghty, you should follow the 'Getting Started' below, then read the Myghty documents to become familiar with Myghty templates. Do this before removing this file (index.html).

Getting Started

You're now ready to start creating your own web application. Here's what a basic controller looks like to print out 'Hello World' and respond to http://localhost:8000/hello:

# ${package}/controllers/hello_controller.py
# Note that the line above is the file you should create and put the following into...

from application_controller import *

class HelloController(ApplicationController):
    def index(self, m, **params):
        m.write('Hello World')
        
hello = HelloController()

Using a template

If you want to call a template and do something a little more complex, here's an example printing out some request information from a Myghty template.

# ${package}/templates/serverinfo.myt

<p>Hi, here's the server environment: <br />
<% str(r.environ) %></p>

<p>
and here's the URL you called: <% url_for() %>
</p>

Then add this to your hello controller class:
    def serverinfo(self, m, **params):
        m.subexec('/serverinfo.myt')
You can now view the page at: http://localhost:8000/hello/serverinfo

myghty-1.1/lib/myghty/paster_templates/routes/+package+/templates/0000755000175000017500000000000010501065765024402 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/routes/+package+/templates/autohandler_tmpl0000644000175000017500000000001710501064117027653 0ustar malexmalex% m.call_next()myghty-1.1/lib/myghty/paster_templates/routes/+package+/webconfig.py_tmpl0000644000175000017500000000321310501064117025742 0ustar malexmalexfrom routes.base import Mapper from routes.util import url_for, redirect_to from myghty.resolver import * from myghty.ext.routeresolver import RoutesResolver import os # This is the mapper, its responsible for setting up Routes to determine how your # controllers are called. m = Mapper() m.connect(':controller/:action/:id') root_path = os.path.dirname(os.path.abspath(__file__)) # The following options are passed directly into Myghty, so all configuration options # available to the Myghty handler are available for your use here config = {} config['use_session'] = True config['session_key'] = '${package}' config['session_secret'] = 'CHANGEME' config['log_errors'] = True if os.environ['MYGHTY_ENV'] == 'development': config['output_errors'] = True else: config['output_errors'] = False config['allow_globals'] = ['url_for', 'redirect_to'] #config['debug_elements'] = ['resolution','classloading'] #config['path_translate'] = [ (r'/your/(stuff.*)', r'/that\1) ] config['data_dir'] = root_path+'/cache/' config['component_root'] = [{'components':root_path + '/components'}, {'templates':root_path + '/templates'}, ] config['resolver_strategy'] = [ \ ConditionalGroup(context = 'request', rules = [PathTranslate(), RoutesResolver(mapper=m, controller_root=root_path+'/controllers'), NotFound(), ]), URICache(rule = ResolveFile()), ResolveDhandler(), ResolveUpwards(), ResolveFile(), NotFound() ] myghty-1.1/lib/myghty/paster_templates/routes/+package+/wsgiapp.py_tmpl0000644000175000017500000000131010501064117025445 0ustar malexmaleximport os, sys from myghty.paste import wsgiapp from paste.cascade import Cascade from paste.urlparser import StaticURLParser if not os.environ.get('MYGHTY_ENV'): os.environ['MYGHTY_ENV'] = 'development' myloc = os.path.dirname(os.path.abspath(__file__)) # Add our lib and controllers path [sys.path.insert(0, myloc + path) for path in ['/controllers', '/lib']] def make_app(global_conf, **kw): return wsgiapp.make_myghty_app( global_conf, package_name='${package}', root_path=myloc, **kw) def make_composit_app(global_conf, **kw): staticapp = StaticURLParser(myloc+'/public') myapp = make_app(global_conf, **kw) return Cascade([staticapp, myapp])myghty-1.1/lib/myghty/paster_templates/routes/server.conf_tmpl0000644000175000017500000000016210501064117024061 0ustar malexmalex[server:main] use = egg:PasteScript#wsgiutils host = 127.0.0.1 port = 8000 [app:main] use = egg:${package}#paste myghty-1.1/lib/myghty/paster_templates/routes/setup.py_tmpl0000644000175000017500000000072710501064117023425 0ustar malexmalexfrom setuptools import setup, find_packages setup( name=${repr(project)|empty}, version=${repr(version)|empty}, #description="", #author="", #author_email="", #url="", install_requires=["Myghty >= 0.99"], packages=find_packages(), package_data = {'templates' : ["*.myt", "*.js", "*.html"]}, entry_points=""" [paste.app_factory] main=${package}.wsgiapp:make_app paste=${package}.wsgiapp:make_composit_app """, )myghty-1.1/lib/myghty/paster_templates/simple/0000755000175000017500000000000010501065765020633 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/simple/+package+/0000755000175000017500000000000010501065765022354 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/simple/+package+/__init__.py0000644000175000017500000000000110501064117024442 0ustar malexmalex myghty-1.1/lib/myghty/paster_templates/simple/+package+/components/0000755000175000017500000000000010501065765024541 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/simple/+package+/components/autohandler_tmpl0000644000175000017500000000037510501064117030021 0ustar malexmalex <& SELF:title &> <& /header.myc &> % m.call_next() <& /footer.myc &> <%method title trim="both"> Site Title myghty-1.1/lib/myghty/paster_templates/simple/+package+/components/footer.myc_tmpl0000644000175000017500000000006310501064117027572 0ustar malexmalex myghty-1.1/lib/myghty/paster_templates/simple/+package+/components/header.myc_tmpl0000644000175000017500000000006310501064117027524 0ustar malexmalex
This is the header
myghty-1.1/lib/myghty/paster_templates/simple/+package+/htdocs/0000755000175000017500000000000010501065765023640 5ustar malexmalexmyghty-1.1/lib/myghty/paster_templates/simple/+package+/htdocs/index.myt_tmpl0000644000175000017500000000137310501064117026530 0ustar malexmalex<%method title> Welcome to Myghty
welcome to the myghty app !

The page you are viewing is "htdocs/index.myt". This page is "wrapped" by an autohandler file, "components/autohandler". The autohandler pulls in "components/header.myc" and "components/footer.myc" to create the finished page.

That's all there is to the simple setup! You can build an entire site just using top level files (index.myt), components (header.myc, footer.myc) and simple inheritance (autohandler). A more automated website might include a "controller" layer. For examples of that, try installing the "myghty_routes" template for a Rails-like approach or the "myghty_modulecomponent" template for a more open-ended approach.

myghty-1.1/lib/myghty/paster_templates/simple/+package+/htdocs/style.css_tmpl0000644000175000017500000000036010501064117026533 0ustar malexmalexbody { font-family: verdana, arial, sans-serif; font-size: 12pt; } .main { margin:10px; padding:10px; } .header { border: 1px solid; margin:10px; padding:10px; } .footer { border: 1px solid; margin:10px; padding:10px; } myghty-1.1/lib/myghty/paster_templates/simple/+package+/webconfig.py_tmpl0000644000175000017500000000217010501064117025713 0ustar malexmaleximport os from myghty.resolver import * publish_dir = os.path.dirname(os.path.abspath(__file__)) # The following options are passed directly into Myghty, so all configuration options # available to the Myghty handler are available for your use here config = {} config['data_dir'] = os.path.join(publish_dir, 'cache') config['component_root'] = [ {'comp' : os.path.join(publish_dir, 'components')}, {'htdocs' : os.path.join(publish_dir, 'htdocs')}, ] config['resolver_strategy'] = [ ConditionalGroup(context='request', rules=[ PathTranslate((r'/$$', '/index.myt')), # bounce non-myghty requests down to paste htdocs handler, # using "silent" NotFound rule ConditionalGroup(regexp=r'(?!.*\.myt$$)', rules = [ NotFound(silent=True) ]), ]), PathTranslate(), ResolveDhandler(), URICache(), ResolveUpwards(), ResolvePathModule(), ResolveModule(), ResolveFile() ] myghty-1.1/lib/myghty/paster_templates/simple/+package+/wsgiapp.py_tmpl0000644000175000017500000000042010501064117025416 0ustar malexmaleximport os, sys from myghty.paste import wsgiapp root = os.path.dirname(os.path.abspath(__file__)) def make_app(global_conf, **kw): return wsgiapp.make_myghty_app( global_conf, package_name='${package}', root_path=root, **kw) myghty-1.1/lib/myghty/paster_templates/simple/server.conf_tmpl0000644000175000017500000000111010501064117024023 0ustar malexmalex # defines the application when you run "paster serve" [server:main] use = egg:PasteScript#wsgiutils host = 127.0.0.1 port = 8000 # defines a composite application. first call myghty app via ${package}, then if it 404's # fall thru to "static", which serves non-myghty files [composit:main] use = egg:Paste#cascade app1 = ${package} app2 = static # define static application, which is Paste StaticURLParser [app:static] use = egg:Paste#static document_root = ${package}/htdocs # define Myghty application, which is the app name ${package} [app:${package}] use = egg:${package} myghty-1.1/lib/myghty/paster_templates/simple/setup.py_tmpl0000644000175000017500000000065110501064117023371 0ustar malexmalexfrom setuptools import setup, find_packages setup( name=${repr(project)|empty}, version=${repr(version)|empty}, #description="", #author="", #author_email="", #url="", install_requires=["Myghty >= 0.98d"], packages=find_packages(), package_data = {'templates' : ["*.myt", "*.js", "*.html"]}, entry_points=""" [paste.app_factory] main=${package}.wsgiapp:make_app """, )myghty-1.1/lib/myghty/request.py0000644000175000017500000012677310501064120016030 0ustar malexmalex# $Id: request.py 2133 2006-09-06 18:52:56Z dairiki $ # request.py - handles component calls for Myghty templates # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # Original Perl code and documentation copyright (c) 1998-2003 by Jonathan Swartz. # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # """The request package and its primary class, Request, provide the "m" object in Myghty templates. The Request is instantiated by the Interpreter and handles component execution state as well as buffered state. It provides the interface to all actions taken by components, such as writing output, calling other components, calling subrequests, and controllling request state. Also provides the programmatic interface to key objects such as caches and sessions. """ from myghty.util import * import myghty.buffer from myghty import exception import sys, string, re, StringIO, types, os, time, warnings import posixpath as unixpath from myghty.container import CreationAbortedError import myghty.escapes as escapes import myghty.csource as csource from myghty.requestbuffer import UnicodeRequestBuffer, StrRequestBuffer DEFAULT_OUTPUT_ENCODING = sys.getdefaultencoding() DEFAULT_ENCODING_ERRORS = 'strict' # current instance of Request for this thread. # this is flaky as the request module seems to get reimported when it is # first imported within a generated component reqinstance = ThreadLocal() __all__ = ['Request', 'instance', 'threadlocal'] class Request(object): """request object, calls components and directs output. also is the primary programmatic interface presented to a template's environment.""" def __init__(self, interpreter, component = None, request_impl = None, max_recursion = 32, auto_flush = False, use_dhandlers = True, dont_auto_flush_filters = False, resolver_context = 'request', parent_request = None, request_depth = 0, request_path = None, raise_error = False, disable_wrapping = False, output_encoding = DEFAULT_OUTPUT_ENCODING, encoding_errors = DEFAULT_ENCODING_ERRORS, disable_unicode = False, **params ): """init is called internally by the Interpreter.""" self.component = component self.auto_flush = auto_flush self.executed = False self.interpreter = interpreter self.execution_stack = [] self.max_recursion = max_recursion self.dont_auto_flush_filters = dont_auto_flush_filters self.use_dhandlers = use_dhandlers self.disable_wrapping = disable_wrapping self.raise_error = raise_error self.parent_request = parent_request self._attributes = {} self.request_depth = request_depth self.current_csource = None self.starttime = time.time() self.comphash = {} self.compcaches = {} self.request_path = request_path self.resolver_context = resolver_context if parent_request is not None: self.root_request = parent_request.root_request self.declined_components = parent_request.declined_components self.dhandler_path = parent_request.dhandler_path else: self.declined_components = {} self.root_request = self self.dhandler_path = None if request_impl: self.request_impl = request_impl else: self.request_impl = DefaultRequestImpl(**params) self.buffer = None self.__output_encoding = output_encoding, encoding_errors self.disable_unicode = bool(disable_unicode) notes = property(lambda self: self.attributes, doc="""A synonym for m.attributes""") root_request_path = property(lambda self:self.root_request.request_path, doc="""\ The URI sent to the ultimate root request of this Request. """) root_request_args = property(lambda self:self.root_request.request_args, doc="""\ The request argument dictionary sent to the ultimate root request of this Request. """) def _parentattr(self): if self.parent_request is not None: return self.parent_request.attributes else: return None attributes = property(lambda self:InheritedDict(self._attributes, lambda: self._parentattr()), doc="""\ A dictionary where arbitrary attributes can be stored and retrieved. Inherits data from its parent request, if any. """) def is_subrequest(self): """returns True if this request is a subrequest.""" return self.parent_request is not None def clone(self, **params): """creates a copy of this Request. Normally used to create subrequests.""" if not params.has_key('request_impl'): params['request_impl'] = self.request_impl.clone(**params) clone = ConstructorClone(self, **params) return clone.clone() class StackFrame: """data object representing the currently executing component context. gets pushed and popped to/from the execution_stack instance variable. """ def __init__(self, request, component, args, base_component, content, is_call_self): self.component = component self.args = args self.base_component = base_component self.content = content self.is_call_self = is_call_self self.content_args = None # set up an indirect filter function that is passed to output # buffers, etc. allows the filtering function can be dynamically # reset to a no-op, in the case that the component calls call_self # or cache_self, which should disable the final output filtering if isinstance(component, myghty.component.Component) and component.has_filter(): def filt(f): return component.filter(f, m = request, ARGS = args, **request.global_args) self._filter = filt self.filter = lambda f: self._filter(f) def reset_filter(self): self._filter = lambda f: f def out(self, string): """a synonym for write""" self.write(string) def write(self, string): """writes textual content to the current output buffer.""" self.buffer.write(string) def flush_buffer(self): """flushes the current output buffer to its destination.""" self.buffer.flush() def clear_buffer(self): """clears all content from the current output buffer.""" self.buffer.clear() def execute(self): """executes this request after constructed. This is the first method called, and can only be called once per request.""" if self.executed: self._raise_error("Can only call execute() once per request") self.executed = True if reqinstance.exists(): existing_request = reqinstance.get() else: existing_request = None reqinstance.put(self) # Divert python warnings to log file saved_showwarning = warnings.showwarning warnings.showwarning = self.__showwarning if self.parent_request is None \ or isinstance(self.request_impl, CapturingRequestImpl): # top-level request (or subrequest with output captured to # out_buffer): set up buffer if self.disable_unicode: self.buffer = StrRequestBuffer(self.request_impl.buffer) else: self.buffer = UnicodeRequestBuffer(self.request_impl.buffer, self.output_encoding, self.encoding_errors) else: # use parent's request buffer self.buffer = self.parent_request.buffer self.out = self.write = self.buffer.write # optimization self.request_args = self.request_impl.request_args self.global_args = self.request_impl.global_args [self.global_args.setdefault(k, v) for k,v in self.interpreter.global_args.iteritems()] [self.global_args.setdefault(k, None) for k in self.interpreter.compiler().allow_globals] component = self.component # current request-clearing try/finally try: # exception handling try/except try: if type(component) == types.StringType: component = self.fetch_lead_component(component) elif component.is_file_component(): self.request_path = component.path self.request_component = component depth = 0 self.wrapper_chain = [] while (component): self.wrapper_chain.append(component) if not component.is_file: break component = component.parent_component depth += 1 if depth >= self.max_recursion: self._raise_error("Max %d levels deep in determining inheritance chain (recursive inheritance pattern?)" % depth); if self.disable_wrapping: first_component = self.wrapper_chain[0] else: first_component = self.wrapper_chain[-1] result = None try: result = self.execute_component(first_component, base_component = self.request_component, args = self.request_args) except exception.Abort: raise except exception.Decline, d: result = d.declined_value except exception.AbortRequest: pass return result except exception.Redirected, e: self.request_impl.send_redirect(e.path) except exception.Abort, e: self.request_impl.send_abort(e.aborted_value, e.reason) except exception.Error, e: error = e except Exception, e: error = exception.Error(wrapped = e) except: e = sys.exc_info()[0] error = exception.Error(wrapped = e) if error: error.initTraceback(self.interpreter) if self.parent_request is not None: raise error else: if self.raise_error: raise error else: self.request_impl.handle_error(error, self) finally: # before we leave the execute method, remove all # buffers and restore the request threadlocal del self.write, self.out self.buffer = None warnings.showwarning = saved_showwarning if existing_request: reqinstance.put(existing_request) else: reqinstance.remove() def comp(self, component, **params): """component calling method which takes a simple parameter list. this method is meant to be the default method to call when calling components programmatically. compare to scomp().""" return self.execute_component(component = component, args = params) def scomp(self, component, **params): """component calling method which returns output as a string""" self.buffer.push_capture_buffer() self.execute_component(component = component, args = params, store = None) return self.buffer.pop_capture_buffer().getvalue() def subexec(self, component, **params): """creates a subrequest with the given component and request parameters and executes it. see create_subrequest().""" self.make_subrequest(component, **params).execute() def execute_component(self, component, args = {}, base_component = None, content = None, store = None, is_call_self = False): """component calling method which takes parameter list as a dictionary, and allows special execution options. This method is used by component call tags and internally within the Request object.""" path = None if type(component) == types.StringType: path = component component = self.fetch_component(path) if self.depth >= self.max_recursion: self._raise_error("%d levels deep in component stack (infinite recursive call?)" % self.depth) # if base component was set, use that # otherwise, figure it out if not base_component: # however, for method and file components, we might have to figure # the appropriate base class if there is a colon in the path if ( (component.is_method_component() or not component.is_sub_component()) and path and not component.is_module_component() and not re.match(r"(?:SELF|PARENT|REQUEST)(?:\:..*)?$", path)): match = re.match(r"(.*):", path) if match: base_component = self.fetch_component(match.group(1)) else: base_component = component if base_component.is_sub_component(): base_component = base_component.owner elif len(self.execution_stack): # base_component defaults to that of the top of the execution stack base_component = self.execution_stack[-1].base_component frame = Request.StackFrame(self, component, args, base_component, content, is_call_self) self.execution_stack.append(frame) initial_buffer_state = self.buffer.get_state() # if they requested to store the output, push that buffer onto the stack if store: self.buffer.push_capture_buffer(store) if component.has_filter(): self.buffer.push_filter(frame.filter) # Figure out if buffering is needed. do_auto_flush = component.use_auto_flush() if component.has_filter(): # If filtering, auto_flush defaults to False if do_auto_flush is None or self.dont_auto_flush_filters: do_auto_flush = False # If this is the top-level component, auto_flush defaults to config val if do_auto_flush is None and len(self.execution_stack) == 1: do_auto_flush = self.auto_flush if not do_auto_flush: self.buffer.push_buffer() discard_buffered_output = True try: if component.flags.setdefault('use_cache', False): retval = value() if self.cache_self(retval = retval, **component.flags): discard_buffered_output = False return retval() result = component.run(self, **args) discard_buffered_output = False finally: self.execution_stack.pop() # Warning: pop_to_state can raise UnicodeError self.buffer.pop_to_state(initial_buffer_state, discard=discard_buffered_output) return result def fetch_lead_component(self, path): """fetches the top level (initial) component to be executed by this request. Differs from fetch_component in that the resolver context is "request" or "subrequest" and the dhandler flag is enabled when resolving. Also does not support method calls. """ path = re.sub(r"/+", "/", path) request_path = path try: resolution = self.interpreter.resolve_component(path, resolver_context = self.resolver_context, enable_dhandler = self.use_dhandlers, declined_components = self.declined_components) except exception.ComponentNotFound, cfound: raise cfound.create_toplevel() # set up a pointer to the current component source, in case # the initialization of this component wants to do a fetch_component csource = resolution.csource self.current_csource = csource self.resolution = resolution component = self.interpreter.load_component(csource) if hasattr(resolution, 'dhandler_path'): self.dhandler_path = resolution.dhandler_path if self.request_path is None: self.request_path = request_path return component def fetch_component(self, path, resolver_context = 'component', **params): """ Given a component path (absolute or relative), returns a component. Handles SELF, PARENT, REQUEST, comp:method, relative->absolute conversion, MODULE, and local subcomponents. """ hascolon = (string.find(path, ':') != -1) if not hascolon: if path == 'SELF': return self.base_component elif path == 'PARENT': comp = self.current_component.parent_component if not comp: self._raise_error("PARENT designator used from component with no parent") else: return comp elif path == 'REQUEST': return self.request_component elif path == 'MODULE': self._raise_error("MODULE designator requires module name") if self.has_current_component(): if hascolon: (owner_path, argument) = re.split(':', path, 1) if owner_path == 'MODULE': return self.fetch_module_component(argument, **params) elif owner_path[0]=='@': return self.fetch_module_component(path[1:], **params) owner_component = self.fetch_component(owner_path, resolver_context = resolver_context, **params) method_component = owner_component.locate_inherited_method(argument) return method_component if not hascolon: subcomp = self.current_component.get_sub_component(path) if subcomp: return subcomp # adjust requested path to the path of the current component path = unixpath.join(self.current_component.dir_name, path) else: # no current component. current csource ? if self.current_csource is not None: path = unixpath.join(self.current_csource.dir_name, path) return self.load_component(path, resolver_context = resolver_context, **params) def fetch_module_component(self, moduleorpath, classname = None, raise_error = True): """fetches a module-based component. Usually called by fetch_component when the 'MODULE' keyword is given as a prefix. """ module = None modulename = None if type(moduleorpath) == types.StringType: if classname is None: (modulename, classname) = moduleorpath.split(':') else: modulename = moduleorpath else: module = moduleorpath modulename = moduleorpath.__name__ if classname is None: self._raise_error("classname is required with component module") key = "module:" + modulename + ":" + classname if self.comphash.has_key(key): return self.comphash[key] component = self.interpreter.load_module_component(arg = modulename + ":" + classname) self.comphash[key] = component if raise_error and component is None: raise exception.ComponentNotFound("Cant locate component %s" % key) return component def load_component(self, path, raise_error = True, resolver_context = None, **params): """a mirror of interpreter.load() which caches its results in a request-local dictionary, thereby bypassing all the filesystem checks that interp does for a repeated file-based request.""" # since different contexts can come in here, cache based on that key as well # technically, all the other context-specific stuff, like dhandler, search upwards # etc., should be built into the key as well key = "%s_%s" % (str(resolver_context), path) if self.comphash.has_key(key): # get the component from the hash...might be None component = self.comphash[key] else: component = self.interpreter.load(path, raise_error = raise_error, resolver_context = resolver_context, **params) self.comphash[key] = component if raise_error and component is None: raise exception.ComponentNotFound("Cant locate component %s" % path) return component def make_subrequest(self, component, **params): """creates a subrequest with the given component and request parameters. see create_subrequest().""" return self.create_subrequest(component, request_args = params) def create_subrequest(self, component, resolver_context = 'subrequest', **params): """base subrequest-creation method. A subrequest is a request that is a child to this one, enabling execution of a component and its full inheritance chain within this request.""" if type(component) == types.StringType and self.has_current_component(): component = unixpath.join(self.current_component.dir_name, component) params['component'] = component params['parent_request'] = self if params.get('out_buffer'): params['request_impl'] = CapturingRequestImpl(self.request_impl, **params) params.setdefault('output_encoding', DEFAULT_OUTPUT_ENCODING) params.setdefault('encoding_errors', DEFAULT_ENCODING_ERRORS) request = self.clone(resolver_context = resolver_context, **params) request.request_depth = self.request_depth + 1 if request.request_depth > self.max_recursion: raise exception.Error("recursion limit exceeded") return request def cache(self, **params): """a synonym for get_cache().""" return self.get_cache(**params) def get_cache(self, component = None, **params): """returns the given component's cache. **params is a dictionary of options used as the default options for individually cached elements.""" if component is None: component = self.current_comp() elif type(component) == types.StringType: component = self.fetch_component(component) if not self.compcaches.has_key(component.id): self.compcaches[component.id] = self.interpreter.cache_args.get_cache(component = component, starttime = component.creationtime, **params) return self.compcaches[component.id] def cache_self(self, key = '_self', component = None, retval = None, **params): """caches this component's output. this is called in a "reentrant" fashion; if it returns False, component execution should continue. If it returns True, component execution should cease.""" cache = self.get_cache(component = component, **params) def getself(): rv = value() buf = StringIO.StringIO() ret = self.call_self(buf, rv) if not ret: raise CreationAbortedError() else: return (buf, rv()) try: (buf, ret) = cache.get_value(key, cache_createfunc = getself) if retval is not None: retval.assign(ret) # turn off output filtering for the currently # executing stack frame self.execution_stack[-1].reset_filter() self.buffer.write(buf.getvalue()) return True except CreationAbortedError: return False def call_self(self, output_buffer, return_buffer): """calls this component and places its output and return value in the given buffers. This component is called in a "reentrant" fashion, where a return value of False means to continue component execution and a return value of True means to halt execution and return. """ if self.execution_stack[-1].is_call_self: return False # turn off output filtering for the currently # executing stack frame self.execution_stack[-1].reset_filter() result = self.execute_component(self.current_component, args = self.component_args, store = output_buffer, is_call_self = True) return_buffer.assign(result) return True def get_session(self, *args, **params): """returns the Session object used by this request. If the session has not been created it will be created when this method is called. Once the session is created, this method will return the same session object repeatedly. **params is a dictionary of options used when the session is first constructed; if it already exists, the parameters are ignored.""" return self.request_impl.get_session(*args, **params) def has_current_component(self): """returns if this request has a currently executing component. This could return false if the request's top-level-component is in the loading stage.""" return len(self.execution_stack) > 0 def component_exists(self, path, **params): """returns True if the given component path exists.""" return self.interpreter.component_exists(path, **params) def apply_escapes(self, text, escapes): """applies the given escape flags to the given text. escapes is a list of escape flags, such as 'h', 'u', and 'x'.""" # XXX: If you change this string coercion code, you should also # make matching changes to {Unicode,Str}RequestBuffer.write() if text is None: text = '' elif self.disable_unicode: text = str(text) else: text = unicode(text) esctable = self.interpreter.escapes try: escfuncs = [ esctable[esc] for esc in escapes ] except KeyError, k: self._raise_error("no such escape '%s'" % k) for f in escfuncs: text = f(text) return text def _get_status(self): """debugging method that returns the current execution stack entry""" return ("Base Comp: %s\n_request Comp: %s\n_current Comp: %s\n" % (self.base_component.id, self.request_component.id, self.current_component.id())) def abort(self, status_code=None, reason=None): """raises an abort exception. """ if status_code is None: raise exception.AbortRequest() else: raise exception.Abort(aborted_value=status_code, reason=reason) def decline(self, returnval = None): """used by dhandlers to decline handling the current request. Control will be passed to the next enclosing dhandler, or if none is found a ComponentNotFound (and possibly 404 error) is raised.""" self.buffer.clear() subreq = self.make_subrequest(component=self.request_path, request_args = self.request_args) subreq.declined_components[self.request_component.id] = self.request_component ret = subreq.execute() raise exception.Decline(declined_value = ret) def callers(self, index = None): """returns the list of components in the current call stack. if an integer index is given, returns the component at that position in the current call stack.""" if index is None: return map(lambda f: f.component, self.execution_stack) else: return self.execution_stack[index].component def caller_args(self, index = None): """returns the list of component arguments in the current call stack. if an integer index is given, returns the component arguments at that position in the current call stack.""" if index is None: return map(lambda f: f.args, self.execution_stack) else: return self.execution_stack[index].args def call_stack(self, index = None): """returns the current execution stack, which consists of StackFrame objects. if an integer index is given, returns the StackFrame object at that position in the current call stack.""" if index is None: return self.execution_stack else: return self.execution_stack[index] def call_next(self, **params): """used within an inheritance chain to call the next component in the chain. If **params are given, each parameter will override the arguments sent to this component when calling the next component.""" comp = self.fetch_next() args = {} args.update(self.request_args) args.update(params) return self.comp(comp, **args) def fetch_next(self): """in an inheritance chain (i.e. of autohandlers), returns the next component in the chain""" try: self.wrapper_chain.pop() return self.wrapper_chain[-1] except IndexError: self._raise_error("No component available for fetch_next()") def fetch_all(self): """pops off the entire remaining list of components in the inheritance chain.""" ret = [] while len(self.wrapper_chain): ret.append(self.wrapper_chain.pop()) return ret def send_redirect(self, path, hard=True): """sends a redirect to the given path. If hard is True, sends an HTTP 302 redirect via the underlying RequestImpl being used. If False, clears out the current output buffer and executes a subrequest with the given path. The path can also contain url encoded query string arguments with a question mark which will be converted to key/value arguments for the next request, even if hard=False.""" if hard: raise exception.Redirected(path) else: if re.search(r'\?', path): (path, query) = path.split('?') args = dict([(escapes.url_unescape(m.group(1)), escapes.url_unescape(m.group(2))) for m in re.finditer(r'([^=&]*)=([^=&]*)', query)]) else: args = {} self.buffer.clear() req = self.create_subrequest(path, resolver_context = self.resolver_context, request_args = args) req.execute() self.buffer.flush() raise exception.AbortRequest() def has_content(self): """returns whether or not the current component call was called with captured content specified""" return self.execution_stack[-1].content != None def call_content(self, **kwargs): """calls the "captured content" function within a component call with content""" if not self.execution_stack[-1].content: return frame = self.execution_stack.pop() try: self.execution_stack[-1].content_args = kwargs frame.content() finally: self.execution_stack[-1].content_args = None self.execution_stack.append(frame) def content(self, **kwargs): """when inside a component call with content, this method calls the "captured content" function and buffers the content, returning the string value.""" self.buffer.push_capture_buffer() try: self.call_content(**kwargs) finally: buffer = self.buffer.pop_capture_buffer() return buffer.getvalue() def closure_with_content(self, func, content=None, args=None): """given a function, will execute it with the given arguments, after pushing the given content function onto the call stack via a new StackFrame, enabling the function to call it via the content() method.""" frame = Request.StackFrame(self, func, args, self.base_component, content, False) if args is None: args = {} self.execution_stack.append(frame) try: func(**args) finally: self.execution_stack.pop() def _raise_error(self, error): self.interpreter.raise_error(error) def log(self, message): """writes a message to the request log. The log is RequestImpl-specific and can be standard error, a webserver error log, or a user-defined file object.""" self.request_impl.log(message) def __showwarning(self, message, category, filename, lineno): """A custom warnings handler. """ interpreter = self.interpreter try: # Try to map (filename, lineno) back to original try: comprec = interpreter.reverse_lookup[filename] except KeyError: comprec = interpreter.reverse_lookup['memory:' + filename] reversefile = exception.Error.ReverseFile(interpreter, comprec) csource = comprec.csource filename, lineno = \ csource.file_path, reversefile.get_line_number(lineno) except: pass mesg = warnings.formatwarning(message, category, filename, lineno) if mesg.endswith('\n'): mesg = mesg[:-1] self.log(mesg) # property accessors logger = property(lambda self: _Logger(self), doc="""\ returns the logger for this Request, which is a file-like object. """) depth = property(lambda self: len(self.execution_stack), doc="""\ Returns the current depth of the execution stack. """) base_component = property(lambda self: self.execution_stack[-1].base_component, doc="""\ Returns the "base component" for the current stack frame, which is either the top level component or the owning component of a method component. """) component_args = property(lambda self: self.execution_stack[-1].args, doc="""\ returns the argument dictionary from the current stack frame, corresponding to the arguments sent to the currently executing component. """) content_args = property(lambda self:self.execution_stack[-1].content_args, doc="""\ returns the component-call-with-content argument dictionary from the current stack frame, corresponding to the arguments sent in an m.content() call. """) caller = property(lambda self: self.execution_stack[-2].component, doc="""\ returns the calling component for the currently executing component. """) dhandler_argument = property(lambda self: self.dhandler_path, doc="""\ returns the current dhandler_argument, which corresopnds to the remaining path tokens in the request URI when executing a dhandler. """) current_component = property(lambda self: self.execution_stack[-1].component, doc="""\ returns the component currently executing from the current stack frame. """) def get_output_encoding(self): if self.buffer: return self.buffer.encoding else: return self.__output_encoding[0] output_encoding = property( get_output_encoding, doc="""The ouput encoding for the request This is the encoding of the output written to the output buffer of the top-level request. (I.e. the encoding delivered to the user.) This is a read-only attribute, though you can change its value using the `set_output_encoding`_ method. """) def get_encoding_errors(self): if self.buffer: return self.buffer.errors else: return self.__output_encoding[1] encoding_errors = property( get_encoding_errors, doc="""The encoding error handling strategy for the request This is the error handling strategy used when encoding text writtent to the output buffer of the top-level request. This is a read-only attribute, though you can change its value using the `set_output_encoding`_ method. """) def set_output_encoding(self, encoding, errors='strict'): """Change the output_encoding. Note that in most cases you do not want to change it after you have written any output (as then your output will be in two different encodings --- probably not what you wanted unless, perhaps, you are generating Mime multipart output.) """ if self.buffer: self.buffer.set_encoding(encoding, errors) self.__output_encoding = self.buffer.encoding, self.buffer.errors else: self.__output_encoding = encoding, errors # deprecated getter methods ! def get_attribute(self, key): """deprecated. use the attributes property.""" return self.notes(key) def set_attribute(self, key, value): """deprecated. use the attributes property.""" self.notes(key, value) def get_attributes(self): """deprecated. use the attributes property.""" return self.notes def get_depth(self): """deprecated. use the depth property.""" return self.depth def get_log(self): """deprecated. use the logger property.""" return self.logger def get_start_time(self): """deprecated. use the starttime property.""" return self.starttime def get_request_args(self): """deprecated. use the request_args property.""" return self.request_args def get_base_component(self): """deprecated. use the base_component property.""" return self.base_component def base_comp(self): """deprecated. use the base_component property.""" return self.base_component def get_request_component(self): """deprecated. use the request_component property.""" return self.request_component def request_comp(self): """deprecated. use the request_component property.""" return self.request_component def get_component_args(self): """deprecated. use the component_args property.""" return self.component_args def get_dhandler_argument(self): """deprecated. use the request_path or dhandler_argument property.""" return self.request_path def dhandler_arg(self): """deprecated. use the request_path or dhandler_argument property.""" return self.request_path def get_request_path(self): """deprecated. use the request_path property.""" return self.request_path def get_interpreter(self): """deprecated. use the interpreter property.""" return self.interpreter def get_current_component(self): """deprecated. use the current_component property.""" return self.current_component def current_comp(self): """deprecated. use the current_component property.""" return self.current_component def instance(): """returns the Request instance corresponding to the current thread""" return reqinstance.get() def threadlocal(value = None): """returns a thread local container, with initial value that of the given variable""" return ThreadLocal(value) def value(value = None): """returns a value container object. useful for mutating data that was instantiated in a requestonce or shared block""" return Value(value) class AbstractRequestImpl(object): def clone(self):raise NotImplementedError() def handle_error(self, e, m, **params):raise NotImplementedError() def get_session(self, **params):raise NotImplementedError() def send_redirect(self, path):raise NotImplementedError() def send_abort(self, ret, reason):raise NotImplementedError() def log(self, message):raise NotImplementedError() def _run_error_handler(self, handler, logger, error, m, **params): err = None try: if handler: return handler(error, m, **params) except exception.Error, e: err = e except Exception, e: err = exception.Error(wrapped = e) except: e = sys.exc_info()[0] err = exception.Error(wrapped = e) if err: err.initTraceback(m.interpreter) logger.write("Custom error handler had an error:") logger.writelines(string.split(err.format(), "\n")) return False def __init__(self): self.global_args = None self.request_args = None self.logger = None self.buffer = None self.request = None class DefaultRequestImpl(AbstractRequestImpl): def __init__(self, out_buffer = None, global_args = None, request_args = None, error_handler = None, **params): if out_buffer: self.out_buffer = out_buffer else: self.out_buffer = sys.__stdout__ self.error_handler = error_handler if request_args is None: request_args = {} self.request_args = request_args if global_args is None: self.global_args = {} else: # copy the global_args in case they are synonymous with those given to the # interpreter self.global_args = global_args.copy() self.logger = sys.stderr self.buffer = self.out_buffer def handle_error(self, error, m, **params): if self._run_error_handler(self.error_handler, self.logger, error, m, **params): return sys.stderr.write(error.format()) def log(self, message):self.logger.write(message + "\n") def clone(self, **params): cloner = ConstructorClone(self, **params) return cloner.clone() class _Logger: '''A file-like object which "writes" to an object ``log()`` method. ''' def __init__(self, impl): self.impl = impl def write(self, text): self.impl.log(text) def writelines(self, lines): for text in lines: self.write(text) class CapturingRequestImpl(AbstractRequestImpl): def __init__(self, parent_request_impl, out_buffer, request_args=None, global_args=None, error_handler=None, **params): self.buffer = self.out_buffer = out_buffer self.parent_request_impl = parent_request_impl if request_args is None: request_args = parent_request_impl.request_args.copy() self.request_args = request_args # XXX: No .copy() here, subrequest can change parent requests # global_args. Is that correct? if global_args is None: global_args = parent_request_impl.global_args self.global_args = global_args self.error_handler = error_handler logger = property(lambda self: _Logger(self)) def handle_error(self, error, m, **params): if self.error_handler: self._run_error_handler(self.error_handler, self.logger, error, m, **params) else: self.parent_request_impl.handle_error(error, m, **params) def log(self, message): self.parent_request_impl.log(message) myghty-1.1/lib/myghty/requestbuffer.py0000644000175000017500000002660110501064120017207 0ustar malexmalex# $Id$ # requestbuffer.py - help for dealing with various character encodings # # Copyright (C) 2006 Geoffrey T. Dairiki # and Michael Bayer # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # import codecs, sys, StringIO class _Link(object): def __init__(self, parent): self.parent = parent def write(self, text): """Write text to the buffer chain. See also `RequestBuffer.write()`_. """ raise NotImplementedError, "(pure virtual)" def flush(self): pass def clear(self): pass def __repr__(self): return "<%s: Parent:\n %s>" % ( object.__repr__(self)[1:-1], repr(self.parent)) class _Buffer(_Link): def __init__(self, parent, buffer=None): _Link.__init__(self, parent) if buffer is None: buffer = StringIO.StringIO() self.buffer = buffer self.write = self.buffer.write def write(self, text): # XXX: Note that this method never really gets called. # (See __init__.) self.buffer.write(text) def flush(self): self.buffer.seek(0) self.parent.write(self.buffer.read()) self.clear() def clear(self): self.buffer.truncate(0) # work around for bug in python2.3's cStringIO: try: self.buffer.seek(0) except: pass class _TransparentBuffer(_Buffer): """A buffer which defers all recoding. """ def __init__(self, parent): _Link.__init__(self, parent) self._buf = [] def write(self, text): self._buf.append(text) def flush(self): self.parent.write(''.join(self._buf)) self.clear() def clear(self): self._buf = [] class _CaptureBuffer(_Buffer): """A buffer used to capture output. """ pass class _EncodedBuffer(_Buffer): """This is a buffer which expects encoded strings. """ def __init__(self, parent, buffer, encoding=sys.getdefaultencoding(), errors='strict'): _Buffer.__init__(self, parent, buffer) self._writer = None self.set_encoding(encoding, errors) def set_encoding(self, encoding, errors): if self._writer: self._writer.reset() stream_writer = codecs.lookup(encoding)[3] self._writer = stream_writer(self.buffer, errors) self.write = self._writer.write self.__encoding = (encoding, errors) def write(self, text): self._writer.write(text) def flush(self): self._writer.reset() _Buffer.flush(self) def clear(self): self._writer.reset() _Buffer.clear(self) def get_encoding(self): return self.__encoding[0] encoding = property(get_encoding, doc="Encoding of the output stream.") def get_errors(self): return self.__encoding[1] errors = property(get_errors, doc="Encoding error handling strategy of the output stream.") class _StrBuffer(_Buffer): """This is a buffer which strs. """ def __init__(self, parent, buffer): _Buffer.__init__(self, parent, buffer) def get_encoding(self): return None encoding = property(get_encoding, doc="Encoding of the output stream.") def get_errors(self): return None errors = property(get_errors, doc="Encoding error handling strategy of the output stream.") class _Filter(_Link): def __init__(self, parent, filter): _Link.__init__(self, parent) self.filter = filter class _UnicodeFilter(_Filter): def write(self, text): self.parent.write(unicode(self.filter(text))) class _StrFilter(_Filter): def write(self, text): self.parent.write(str(self.filter(text))) ################################################################ class MismatchedPop(Exception): pass class _RequestBuffer(object): def __init__(self, top): self._top = self.__root = top def push_buffer(self): '''Push a component output buffer. These buffers are used to buffer component output temporarily. When the buffer is flushed, it passes it contents on to the next buffer in the stack. Normally the buffers are flushed when they are popped off the stack. ''' self.__push(_TransparentBuffer) def pop_buffer(self, discard=False): """Pop a component output buffer The argument ``discard``, if set, specifies that the contents of the buffer are to be discarded, rather than passed on to the next buffer in the stack. """ buf = self.__pop(_TransparentBuffer) if not discard: buf.flush() def push_filter(self, filter): """Push a filter. The filter function gets passed either a ``unicode`` or a system default encoded ``str``. If the ``disable_unicode`` config parameter is set, the filter always gets a ``str``. The filter can return any type acceptable to the `write`_ method. """ self.__push(self._FilterType, filter=filter) def pop_filter(self): """Pop a filter. Returns the filter function. """ return self.__pop(self._FilterType).filter def push_capture_buffer(self, buffer=None): """Push a capture buffer. This is a buffer used to capture component output. It does not propagate its contents down the buffer chain when it is popped. The ``buffer`` should be a file-like object supporting at least the ``write()`` method. The strings passed to ``buffer.write()`` will be ``unicode``s or system default (usually ASCII) encoded ``str``s. """ self.__push(_CaptureBuffer, buffer=buffer) def pop_capture_buffer(self): """Pop a capture buffer of the stack. Returns the underlying buffer. """ return self.__pop(_CaptureBuffer).buffer def __push(self, link_class, **kw): self._top = link_class(self._top, **kw) return self._top def __pop(self, *expected_link_classes): link = self._top if not isinstance(link, expected_link_classes): raise MismatchedPop, \ "RequestBuffer.pop* expected %s, got %s" \ % ( repr(map(lambda x: x.__name__, expected_link_classes)), link.__class__.__name__ ) self._top = link.parent return link def get_state(self): """Get the current state of the buffer stack. Returns an opaque value, which can be pssed to `pop_to_state`_ to restore the buffer to it's current state. """ return (self._top,) def pop_to_state(self, state, discard=False): """Pop buffers, and restore saved state. Buffers are popped from the stack back to the position of the saved state. (The buffers are flushed as they are popped, unless ``discard`` parameter is set.) After the stack is unwound, the ``errors`` attribute is restored to what it was at the time the state was saved. If a ``UnicodeError`` is thrown while flushing buffers, no further buffers are flushed. However the stack is still unwound and the buffer state restored before the exception is re-raised. """ top, = state if not isinstance(top, _Link): raise ValueError, "invalid state" bufs_to_flush = [] for b in self.__bufs(end=None): if b is top: break elif not isinstance(b, _CaptureBuffer): bufs_to_flush.append(b) else: # states top is not in our stack raise ValueError, "invalid state" try: if not discard: for buf in bufs_to_flush: buf.flush() finally: self._top = top def __bufs(self, begin=None, end="root"): if begin is None: begin = self._top if end == "root": end = self.__root b = begin while b is not end: yield b b = b.parent def write(self, text): """Write text to the buffer. """ raise NotImplementedError, "pure virtual" def flush(self): """Flush output. Output is flushed up the first capture buffer in the stack. If there is no capture buffer, then output is flushed all the way to the final output buffer. """ for buf in self.__bufs(): if isinstance(buf, _CaptureBuffer): break buf.flush() def clear(self): """Clears all buffers in the stack. """ for buf in self.__bufs(): buf.clear() encoding = property( lambda self: self.__root.encoding, doc="""The current output encoding of the buffer. Output written to the underlying output buffer (e.g. the Apache request object) are encoded using this encoding. """) errors = property( lambda self: self.__root.errors, doc="""The current encoding error handling strategy. This is the error handling strategy of the underlying output buffer. """) def set_encoding(self, encoding, errors='strict'): """Set encoding of output to the final buffer. """ self.__root.set_encoding(encoding, errors) def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, repr(self._top)) class UnicodeRequestBuffer(_RequestBuffer): _FilterType = _UnicodeFilter def __init__(self, output_buffer, output_encoding, errors='strict'): """Constructor. The ``output_buffer`` is assumed to be a buffer like stdout or an apache request object, so we will not do anything but ``.write()`` to it. """ top = _EncodedBuffer(None, output_buffer, output_encoding, errors) _RequestBuffer.__init__(self, top) def write(self, text): """Write text to the buffer. .. _`RequestBuffer.write()`: If ``text`` is a plain ``str``, it is interpreted according to the value of the system default encoding (obtainable from ``sys.getdefaultencoding()``.) Otherwise, if ``text`` is a ``unicode`` object or has a ``__unicode__`` method, it is treated as unicode. Finally, if ``text`` is neither a ``str`` nor convertable to a ``unicode``, it is coerced to a ``str`` and interpreted according to the system default encoding. """ if text is None: return self._top.write(unicode(text)) class StrRequestBuffer(_RequestBuffer): _FilterType = _StrFilter def __init__(self, output_buffer): """Constructor. The ``output_buffer`` is assumed to be a buffer like stdout or an apache request object, so we will not do anything but ``.write()`` to it. """ _RequestBuffer.__init__(self, _StrBuffer(None, output_buffer)) def write(self, text): """Write text to the buffer. """ if text is None: return self._top.write(str(text)) myghty-1.1/lib/myghty/resolver.py0000644000175000017500000007774410501064120016204 0ustar malexmalex# $Id: resolver.py 2024 2006-01-12 04:16:15Z zzzeek $ # resolver.py - file path resolution functions for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # Original Perl code and documentation copyright (c) 1998-2003 by Jonathan Swartz. # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # import types, re, os, string, stat, sys, imp import posixpath as unixpath import myghty.util as util from myghty import exception import myghty.csource as csource import myghty.importer as importer import time, copy __all__ = ['Resolver', 'ResolverRule', 'ResolveFile', 'AdjustedResolveFile', 'ResolveModule', 'Group', 'Conditional', 'ConditionalGroup', 'NotFound', 'PathTranslate', 'ResolveDhandler', 'ResolveUpwards', 'URICache', 'ResolvePathModule'] class Resolution(object): """represents a resolved uri, including the component source object, a list of resolution detail messages, and an optional regexp match object.""" def __init__(self, csource, resolution_detail, match = None, can_cache = True, **params): self.csource = csource self.detail = resolution_detail self.match = match self.can_cache = can_cache for key, value in params.iteritems(): setattr(self, key, value) class ResolverRule(object): """base class for a single rule that attempts to resolve a uri into a Resolution object. acts inside a chain of ResolverRules where when a uri cannot be resolved, the next rule in the chain is called in a continuation pattern.""" def init_resolver(self, resolver, remaining_rules, **params): if hasattr(self, 'resolver'): raise exception.ConfigurationError("resolver rule %s already initialized with a Resolver" % repr(self)) self.resolver = resolver self.do_init_resolver(resolver, remaining_rules, **params) def do_init_resolver(self, resolver, remaining_rules, **params): """ called when the resolver first receives the new rule. objects usually will want to do most of their initialization at this stage. """ pass def do(self, uri, remaining, resolution_detail, **params): """ performs resolution or translation on the given path. "remaining" is an Iterator referencing an element in the full list of ResolverRule objects. the method either returns a Resolution object, or passes control to the next ResolverRule in the iterator.""" raise NotImplementedError() def modifies_uri(self): """ returns True if the rule makes a modification to the URI. if so, the URICache rule wont allow this rule to be cached as a single rule, since it becomes ineffective. As part of a cached chain is still OK.""" return False class ResolveFile(ResolverRule): """performs file based resolution. looks up files within one or more component roots.""" name = 'resolvefile' def __init__(self, *component_roots, **params): self._adjust = params.get('adjust', None) or params.get('component_root_adjust', None) if len(component_roots): self.component_root = component_roots else: self.component_root = None def do_init_resolver(self, resolver, remaining_rules, component_root = None, **params): if self.component_root is None: self.component_root = component_root if self.component_root is None: return if self._adjust is None: self._adjust = params.get('component_root_adjust', None) if not isinstance(self.component_root, types.ListType) and not isinstance(self.component_root, types.TupleType): self.component_root = [{'main':self.component_root}] self.component_root = util.OrderedDict(self.component_root) def do(self, uri, remaining, resolution_detail, **params): if self.component_root is None: return remaining.next().do(uri, remaining, resolution_detail, **params) if resolution_detail is not None: resolution_detail.append("resolvefile:") if self._adjust is not None: path = self._adjust(uri) if resolution_detail is not None: resolution_detail.append("adjust: %s -> %s" % (uri, path)) else: path = uri # internal path, trim off leading '/' if path and path[0] == '/': path = path[1:] for key, root in self.component_root.iteritems(): srcfile = unixpath.join(root, path) if resolution_detail is not None: resolution_detail.append(srcfile) if os.access(srcfile, os.F_OK): break else: return remaining.next().do(uri, remaining, resolution_detail, **params) st = os.stat(srcfile) (mode, modtime) = (st[stat.ST_MODE], st[stat.ST_MTIME]) if stat.S_ISDIR(mode): if resolution_detail is not None: resolution_detail.append("isdirectory: " + uri) return remaining.next().do(uri, remaining, resolution_detail, **params) # the resolved uri, insure theres a leading '/' if not uri or uri[0] != '/': uri = '/' + uri return Resolution( csource.FileComponentSource( file_path = srcfile, last_modified = modtime, path = uri, path_id = key, id = "%s|%s" % (key, path), ), resolution_detail ) class ResolvePathModule(ResolverRule): """resolves a callable object or class instance inside a module, based on a traversal of path tokens corresponding to the module's file location, and then of the object paths defined within the module.""" name = 'pathmodule' def __init__(self, *module_roots, **params): self._adjust = params.get('adjust', None) or params.get('module_root_adjust', None) self._require_publish = params.get('require_publish', None) self.stringtokens = params.get('path_stringtokens', None) self.moduletokens = params.get('path_moduletokens', None) if len(module_roots): self.module_root = list(module_roots) else: self.module_root = None def do_init_resolver(self, resolver, remaining_rules, module_root = None, **params): self.use_static_source = resolver.use_static_source if self.module_root is None: self.module_root = module_root if self.module_root is None: return if self._adjust is None: self._adjust = params.get('module_root_adjust', None) if self._require_publish is None: self._require_publish = params.get('require_publish', False) if self.stringtokens is None: self.stringtokens = params.get('path_stringtokens', []) if self.moduletokens is None: self.moduletokens = params.get('path_moduletokens', ['index']) for i in range(0, len(self.module_root)): value = self.module_root[i] if not isinstance(value, types.ModuleType): try: st = os.stat(value) if stat.S_ISREG(st[stat.ST_MODE]): self.module_root[i] = importer.filemodule(value) except OSError: try: self.module_root[i] = importer.module(value) except Exception, e: raise exception.ConfigurationError("Path %s is not a file, directory, or importable module (%s: %s)" % (value, e.__class__.__name__, e.args[0])) def do(self, uri, remaining, resolution_detail, dhandler_path = None, **params): if self.module_root is None: return remaining.next().do(uri, remaining, resolution_detail, **params) if resolution_detail is not None: resolution_detail.append("resolvemodulepath:") if self._adjust is not None: path = self._adjust(uri) if resolution_detail is not None: resolution_detail.append("adjust: %s -> %s" % (uri, path)) else: path = uri # internal path, trim off leading '/' if path and path[0] == '/': path = path[1:] if dhandler_path is not None: path = path + '/' + dhandler_path if (path == '/' or not path) and len(self.stringtokens): path= '/' + self.stringtokens[0] for root in self.module_root: if resolution_detail is not None: resolution_detail.append(repr(root) + "/" + path) iterator = importer.ObjectPathIterator(root, reload = not self.use_static_source) unit = root for token in path.split('/'): if not token: continue if len(token) > 0 and token[0] == '_': token = re.sub(r'^_+', '', token) try: while True: (unit, matched) = iterator.get_unit([token], stringtokens = self.stringtokens, moduletokens = self.moduletokens) if matched == token: break except StopIteration: pass if unit is not None: while unit is not None and (isinstance(unit, types.ModuleType) or isinstance(unit, str)): try: (unit,matched) = iterator.get_unit([], stringtokens=self.stringtokens, moduletokens=self.moduletokens) if unit is None: break except StopIteration: break if isinstance(unit, types.FunctionType) or isinstance(unit, types.MethodType) or callable(unit): if self._require_publish and not getattr(unit, 'publish', False): return remaining.next().do(uri, remaining, resolution_detail, **params) cs = csource.ModuleComponentSource(module = iterator.module, objpath = iterator.objpath, last_modified = iterator.last_modified, arg = unit) break else: continue else: return remaining.next().do(uri, remaining, resolution_detail, **params) return Resolution(cs, resolution_detail) def adjust_path(adjust, path): for a in adjust: path = re.sub(a[0], a[1], path) return path class AdjustedResolveFile(ResolveFile): """a ResolveFile rule that adds a pre-path-concatenation adjustment step, so the uri can be translated before determining the file path. unlike path_translate, this translated uri is only used to determine a filesystem path, and is not propigated anywhere else.""" def __init__(self, adjust, *component_roots): ResolveFile.__init__(self, adjust = lambda p: adjust_path(adjust, p), *component_roots) class AdjustedResolvePathModule(ResolvePathModule): """a ResolvePathModule rule that adds a pre-path-concatenation adjustment step, so the uri can be translated before determining the file path. unlike path_translate, this translated uri is only used to determine a filesystem path, and is not propigated anywhere else.""" def __init__(self, adjust, *module_roots): ResolvePathModule.__init__(self, adjust = lambda p: adjust_path(adjust, p), *module_roots) class ResolveModule(ResolverRule): """resolves a Module Component based on information in a given list of dictionaries, containing regular expressions for keys which are matched against the URI. The value for each key references either a class, a callable object or function, or a string in the form ":".""" name = 'resolvemodule' def __init__(self, *module_components): if len(module_components): self.module_components = module_components else: self.module_components = None self.csource_cache = {} def do_init_resolver(self, resolver, remaining_rules, module_components = None, **params): self.use_static_source = resolver.use_static_source if self.module_components is None: self.module_components = module_components if self.module_components is None: return self.module_components = util.OrderedDict(self.module_components) for key, value in self.module_components.iteritems(): if not isinstance(value, types.DictType): value = {'component':value} self.module_components[key] = value def do(self, uri, remaining, resolution_detail, context = None, **params): if self.module_components is None: return remaining.next().do(uri, remaining, resolution_detail, **params) if resolution_detail is not None: resolution_detail.append("resolvemodule: " + uri) for reg in self.module_components.keys(): match = re.match(reg, uri) if match: info = self.module_components[reg] compsource = csource.ModuleComponentSource(arg = info['component'], use_static_source = self.use_static_source, arg_cache=self.csource_cache) return Resolution( compsource, resolution_detail, match, args = info ) else: return remaining.next().do(uri, remaining, resolution_detail, **params) class Group(ResolverRule): """creates a subgroup of resolver strategies.""" def __init__(self, rules = []): self.rules = rules def do_init_resolver(self, resolver, remaining_rules, **params): for i in range(0, len(self.rules)): self.rules[i].init_resolver(resolver, self.rules[i + 1:], **params) def do(self, uri, remaining, resolution_detail, **params): def iterator(): for rule in self.rules: yield rule for rule in remaining: yield rule i = iterator() return i.next().do(uri, i, resolution_detail, **params) class Conditional(ResolverRule): """conditionally executes a rule, only executes if the uri matches a certain regexp, or a passed-in context string matches one of the contexts set up for this rule.""" def __init__(self, rule, regexp = None, context = None): self.rule = rule if context is not None: self.contexts = context.split(',') else: self.contexts = [] self.regexp = regexp def do_init_resolver(self, resolver, remaining_rules, **params): self.rule.init_resolver(resolver, remaining_rules, **params) def _match_context(self, context): for c in self.contexts: if c == context: return True return False def test_condition(self, uri, resolver_context, **params): return ( (resolver_context is not None and self._match_context(resolver_context)) or (self.regexp is not None and re.match(self.regexp, uri)) ) def do(self, uri, remaining, resolution_detail, resolver_context = None, **params): if self.test_condition(uri, resolver_context, **params): if resolution_detail is not None: if self.regexp is not None: resolution_detail.append("conditional: " + self.regexp) else: resolution_detail.append("conditional: " + resolver_context) return self.rule.do(uri, remaining, resolution_detail, resolver_context = resolver_context, **params) else: return remaining.next().do(uri, remaining, resolution_detail, resolver_context = resolver_context, **params) class ConditionalGroup(Conditional): """combines a Conditional and a Group to make a conditionally-executing Group.""" def __init__(self, regexp = None, context = None, rules = []): Conditional.__init__(self, Group(rules = rules), regexp, context) class NotFound(ResolverRule): """returns not found. place at the bottom of Group and Match chains to have them terminate.""" name = 'notfound' def __init__(self, silent = False): """silent = True indicates that a resulting TopLevelNotFound exception should be 'silent', i.e. not logged. this is used when a 404 error is being propigated to another application layer.""" self.silent = silent def do(self, uri, remaining, resolution_detail, **params): if resolution_detail is not None: resolution_detail.append("notfound") raise exception.ComponentNotFound("cant locate component %s" % uri, resolution_detail, silent = self.silent) class PathTranslate(ResolverRule): """ performs path translation rules on the given uri and sends control off to the next rule.""" name = 'pathtranslate' def __init__(self, *translations): if len(translations): self.path_translate = translations else: self.path_translate = None def modifies_uri(self):return True def do_init_resolver(self, resolver, remaining_rules, path_translate = None, **params): if self.path_translate is None: self.path_translate = path_translate if self.path_translate is not None and not callable(self.path_translate): pt = self.path_translate def trans(url): for rule in pt: url = re.sub(rule[0], rule[1], url) return url self.path_translate = trans def do(self, uri, remaining, resolution_detail, **params): if self.path_translate is None: return remaining.next().do(uri, remaining, resolution_detail, **params) olduri = uri uri = self.path_translate(uri) if resolution_detail is not None: resolution_detail.append("translate: %s -> %s" % (olduri, uri)) return remaining.next().do(uri, remaining, resolution_detail, **params) class URICache(ResolverRule): """caches the result of either a given nested rule, or the remaining rules in its chain, based on the incoming URI.""" name = 'uricache' def __init__(self, source_cache_size = None, rule = None): """ rule is a single ResolverRule (or Group) whos results will be cached on a per-uri basis. if rule is None, then the result of remaining rules in the current chain will be cached based on the incoming URI. This rule cannot be a PathTranslation rule or other uri-modifying rule, since the translated uri is not stored. source_cache_size is the size of the LRUCache to create. if source_cache_size is None, it will use the Resolver's source cache, sharing it with any other URICaches that also use the Resolver's source cache. When using the Resolver's source cache, if the Resolver has use_static_source disabled, then caching is disabled in this URICache. """ self.source_cache_size = source_cache_size self.rule = rule def do_init_resolver(self, resolver, remaining_rules, **params): if self.rule is not None: if self.rule.modifies_uri(): raise exception.ConfigurationError("can't cache single rule %s - it is a URI-modifying rule" % str(self.rule)) self.rule.init_resolver(resolver, remaining_rules, **params) if self.source_cache_size is None: self.source_cache = resolver.source_cache else: self.source_cache = util.LRUCache(self.source_cache_size) def do(self, uri, remaining, resolution_detail, **params): if self.source_cache is None: if self.rule is not None: return self.rule.do(uri, remaining, resolution_detail, **params) else: return remaining.next().do(uri, remaining, resolution_detail, **params) # create a key that has a stringified version of the parameters plus the URI. # parameters are expected to be pretty much true/false, strings, and maybe lists of strings. key = repr(params) + "|" + uri try: # try to pull results from the cache cached = self.source_cache[key] # update detail with the cached detail if resolution_detail is not None: resolution_detail += cached['detail'] if cached.has_key('exception'): # single rule, which was run with an empty chain - so StopIteration # corresponds to just continuing on the rest of the chain. if self.rule is not None and isinstance(cached['exception'], StopIteration): return remaining.next().do(uri, remaining, resolution_detail, **params) else: # else it was a single rule that raised ComponentNotFound, or # it was the remainder of the rule chain, so raise exception raise cached['exception'] else: # got a cached result - return it resolution = copy.copy(cached['resolution']) resolution.detail = resolution_detail return resolution except KeyError: # ok, nothing in the cache, so run the rule or rules and cache the result cached_detail = [] cached_detail.append("cached: " + str(time.time())) cached = None can_cache = True try: try: if self.rule is not None: # single rule - run it in an empty chain resolution = self.rule.do(uri, iter([]), cached_detail, **params) else: # remaining rules - run the rest of the chain resolution = remaining.next().do(uri, remaining, cached_detail, **params) can_cache = resolution.can_cache cached = {'resolution': resolution, 'detail': cached_detail} if resolution_detail is not None: resolution_detail += cached_detail return resolution except StopIteration, si: # normal end of chain reached cached = {'exception': si, 'detail': cached_detail} if self.rule is not None: # single rule - continue on with the rest of the chain if resolution_detail is not None: resolution_detail += cached_detail return remaining.next().do(uri, remaining, resolution_detail, **params) else: # remaining chain - raise the StopIteration if resolution_detail is not None: resolution_detail += cached_detail raise si except exception.ComponentNotFound, cf: # a rule wants to force not found cached = {'exception': cf, 'detail': cached_detail} if resolution_detail is not None: resolution_detail += cached_detail raise cf finally: # cache the result if can_cache is True: self.source_cache[key] = cached class ResolveDhandler(ResolverRule): """collects all the resolver rules below it and runs them for the requested uri, and if not found re-runs them again for the "dhandler" filename, pruning off path tokens until the root is reached.""" name = 'resolvedhandler' def __init__(self, dhandler_name = 'dhandler'): self.dhandler_name = dhandler_name def do(self, uri, remaining, resolution_detail, enable_dhandler = False, declined_components = None, **params): if not enable_dhandler: return remaining.next().do(uri, remaining, resolution_detail, enable_dhandler = False, declined_components = declined_components, **params) list = [elem for elem in remaining] searchuri = uri parentdir = None raisecfound = False dhandler_path = None while True: iterator = iter(list) try: if parentdir is not None: dhandler_path = uri[len(parentdir):] resolution = iterator.next().do(searchuri, iterator, resolution_detail, dhandler_path = dhandler_path, **params) if resolution: if declined_components and declined_components.has_key(resolution.csource.id): if resolution_detail is not None: resolution_detail.append("declined") else: resolution.dhandler_path = dhandler_path return resolution # keep track on which type of exception the chain raises when it reaches the end # this is usually based on if a NotFound element is at the end of the chain except StopIteration: pass except exception.ComponentNotFound: raisecfound = True # csource not found, or found but its been declined # modify path to be a dhandler path, or move dhandler path up one token (parentdir, name) = unixpath.split(searchuri) if name == self.dhandler_name: # already looked for dhandler, and no more tokens. not found if not parentdir or parentdir == '/': # raise the exception that would normally have happened # in the resolution chain if raisecfound: raise exception.ComponentNotFound("cant locate uri %s" % uri, resolution_detail) else: raise StopIteration (parentdir, token) = unixpath.split(parentdir) else: # entering "dhandler" mode, append to detail if resolution_detail is not None: resolution_detail.append("dhandler") searchuri = unixpath.join(parentdir, self.dhandler_name) assert(False, "enableDhandler loop failed") class ResolveUpwards(ResolverRule): """takes the rules below it and applies "search upwards" logic to them, where it iteratively breaks off path tokens behind the filename and searches upwards until the root path is reached. if require_context is True, then this rule only takes effect if "search_upwards" is sent within the resolution options. this indicates that the upwards resolve is context-sensitive, and therefore should not be cached based on a URI alone. however, by default it allows its results to be cached since in practice its used strictly for autohandlers, which are always searched upwards. """ name = 'resolveupwards' def __init__(self, require_context = True): self.require_context = require_context def do(self, uri, remaining, resolution_detail, search_upwards = False, **params): if self.require_context and not search_upwards: return remaining.next().do(uri, remaining, resolution_detail, **params) if resolution_detail is not None: resolution_detail.append("resolveupwards") list = [elem for elem in remaining] searchuri = uri parentdir = None raisecfound = False while True: iterator = iter(list) resolution = None try: resolution = iterator.next().do(searchuri, iterator, resolution_detail, **params) if resolution: if parentdir: resolution.uri_truncated = uri[len(parentdir):] return resolution # keep track on which type of exception the chain raises when it reaches the end # this is usually based on if a NotFound element is at the end of the chain except StopIteration: pass except exception.ComponentNotFound: raisecfound = True (parentdir, name) = unixpath.split(searchuri) if not parentdir or parentdir == '/': # raise the exception that would normally have happened # in the resolution chain if raisecfound: raise exception.ComponentNotFound("cant locate uri %s" % uri, resolution_detail) else: raise StopIteration (parentdir, token) = unixpath.split(parentdir) searchuri = unixpath.join(parentdir, name) assert(False, "UpwardsResolve loop failed") class Resolver(object): """resolves incoming URIs against a list of rules, and returns Resolution objects and/or raises ComponentNotFound exceptions.""" def __init__(self, resolver_strategy = None, request_resolver = None, use_static_source = False, source_cache_size = 1000, track_resolution_detail = True, debug_file = None, **params ): if resolver_strategy is None: resolver_strategy = [ PathTranslate(), ResolveDhandler(), URICache(), ResolveUpwards(), ResolvePathModule(), ResolveModule(), ResolveFile() ] # if request_resolver is not None: # resolver_strategy.insert() self.debug_file = debug_file self.use_static_source = use_static_source if use_static_source: self.source_cache = util.LRUCache(source_cache_size) else: self.source_cache = None self.track_resolution_detail = track_resolution_detail for i in range(0, len(resolver_strategy)): if type(resolver_strategy[i]) == str: try: resolver_strategy[i] = rulelookup[resolver_strategy[i]]() except KeyError: raise exception.ConfigurationError("No such resolution rule '%s'" % resolver_strategy[i]) for i in range(0, len(resolver_strategy)): resolver_strategy[i].init_resolver(self, resolver_strategy[i + 1:], **params) self.resolver_strategy = resolver_strategy def resolve(self, uri, raise_error = True, **params): iterator = iter(self.resolver_strategy) if self.track_resolution_detail: resolution_detail = [] else: resolution_detail = None try: try: return iterator.next().do(uri, iterator, resolution_detail, **params) except StopIteration: if raise_error: raise exception.ComponentNotFound("Cant locate component %s" % uri, resolution_detail) else: return None except exception.ComponentNotFound, cf: if raise_error: raise cf else: return None finally: if self.debug_file is not None and resolution_detail is not None: self.debug_file.write('"' + uri + '" ' + string.join(resolution_detail, ', ')) class FileResolver(Resolver):pass rulelookup = {} thismodule = sys.modules[__name__] for obj in thismodule.__dict__.values(): if (type(obj) == types.ClassType or type(obj) == types.TypeType) and issubclass(obj, ResolverRule) and hasattr(obj, 'name'): rulelookup[obj.name] = obj myghty-1.1/lib/myghty/session.py0000644000175000017500000002170310501064120016006 0ustar malexmalex# $Id: session.py 2041 2006-02-05 19:02:14Z zzzeek $ # session.py - session management for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # import Cookie import hmac, md5, time, random, os, re, UserDict, datetime from myghty.container import * from myghty.util import * __all__ = ['SignedCookie', 'Session', 'MyghtySessionArgs'] class SignedCookie(Cookie.BaseCookie): "extends python cookie to give digital signature support" def __init__(self, secret, input=None): self.secret = secret Cookie.BaseCookie.__init__(self, input) def value_decode(self, val): sig = val[0:32] value = val[32:] if hmac.new(self.secret, value).hexdigest() != sig: return None, val return val[32:], val def value_encode(self, val): return val, ("%s%s" % (hmac.new(self.secret, val).hexdigest(), val)) class Session(UserDict.DictMixin): "session object that uses container package for storage" def __init__(self, request, id = None, invalidate_corrupt = False, use_cookies = True, type = None, data_dir = None, key = 'myghty_session_id', timeout = None, cookie_expires=True, secret = None, log_file = None, namespace_class = None, **params): if type is None: if data_dir is None: self.type = 'memory' else: self.type = 'file' else: self.type = type if namespace_class is None: self.namespace_class = container_registry(self.type, 'NamespaceManager') else: self.namespace_class = namespace_class self.params = params self.request = request self.data_dir = data_dir self.key = key self.timeout = timeout self.use_cookies = use_cookies self.cookie_expires = cookie_expires self.log_file = log_file self.was_invalidated = False self.secret = secret self.id = id if self.use_cookies: try: cookieheader = request.headers_in['cookie'] except KeyError: cookieheader = '' if secret is not None: try: self.cookie = SignedCookie(secret, input = cookieheader) except Cookie.CookieError: self.cookie = SignedCookie(secret, input = None) else: self.cookie = Cookie.SimpleCookie(input = cookieheader) if self.id is None and self.cookie.has_key(self.key): self.id = self.cookie[self.key].value if self.id is None: self._create_id() else: self.is_new = False try: self.load() except: if invalidate_corrupt: self.invalidate() else: raise def _create_id(self): self.id = md5.new( md5.new("%f%s%f%d" % (time.time(), id({}), random.random(), os.getpid()) ).hexdigest(), ).hexdigest() self.is_new = True if self.use_cookies: self.cookie[self.key] = self.id self.cookie[self.key]['path'] = '/' if self.cookie_expires is not True: if self.cookie_expires is False: expires = datetime.datetime.fromtimestamp( 0x7FFFFFFF ) elif isinstance(self.cookie_expires, datetime.timedelta): expires = datetime.datetime.today() + self.cookie_expires elif isinstance(self.cookie_expires, datetime.datetime): expires = self.cookie_expires else: raise ValueError("Invalid argument for cookie_expires: %s" % repr(self.cookie_expires)) self.cookie[self.key]['expires'] = expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" ) self.request.headers_out.add('set-cookie', self.cookie[self.key].output(header='')) created = property(lambda self: self.dict['_creation_time']) def delete(self): """deletes the persistent storage for this session, but remains valid. """ self.namespace.acquire_write_lock() try: for k in self.namespace.keys(): if not re.match(r'_creation_time|_accessed_time', k): del self.namespace[k] self.namespace['_accessed_time'] = time.time() finally: self.namespace.release_write_lock() def __getitem__(self, key): return self.dict.__getitem__(key) def __setitem__(self, key, value): self.dict.__setitem__(key, value) def __delitem__(self, key): del self.dict[key] def keys(self): return self.dict.keys() def __contains__(self, key): return self.dict.has_key(key) def has_key(self, key): return self.dict.has_key(key) def __iter__(self): return iter(self.dict.keys()) def iteritems(self): return self.dict.iteritems() def invalidate(self): "invalidates this session, creates a new session id, returns to the is_new state" namespace = self.namespace namespace.acquire_write_lock() try: namespace.remove() finally: namespace.release_write_lock() self.was_invalidated = True self._create_id() self.load() def load(self): "loads the data from this session from persistent storage" self.namespace = self.namespace_class(NamespaceContext(log_file = self.log_file), self.id, data_dir = self.data_dir, digest_filenames = False, **self.params) namespace = self.namespace namespace.acquire_write_lock() try: self.debug("session loading keys") self.dict = {} now = time.time() if not namespace.has_key('_creation_time'): namespace['_creation_time'] = now try: self.accessed = namespace['_accessed_time'] namespace['_accessed_time'] = now except KeyError: namespace['_accessed_time'] = self.accessed = now if self.timeout is not None and now - self.accessed > self.timeout: self.invalidate() else: for k in namespace.keys(): self.dict[k] = namespace[k] finally: namespace.release_write_lock() def save(self): "saves the data for this session to persistent storage" self.namespace.acquire_write_lock() try: self.debug("session saving keys") todel = [] for k in self.namespace.keys(): if not self.dict.has_key(k): todel.append(k) for k in todel: del self.namespace[k] for k in self.dict.keys(): self.namespace[k] = self.dict[k] self.namespace['_accessed_time'] = time.time() finally: self.namespace.release_write_lock() def lock(self): """locks this session against other processes/threads. this is automatic when load/save is called. ***use with caution*** and always with a corresponding 'unlock' inside a "finally:" block, as a stray lock typically cannot be unlocked without shutting down the whole application. """ self.namespace.acquire_write_lock() def unlock(self): """unlocks this session against other processes/threads. this is automatic when load/save is called. ***use with caution*** and always within a "finally:" block, as a stray lock typically cannot be unlocked without shutting down the whole application. """ self.namespace.release_write_lock() def debug(self, message): if self.log_file is not None: self.log_file.write(message) class MyghtySessionArgs(PrefixArgs): def __init__(self, data_dir = None, **params): PrefixArgs.__init__(self, 'session_') self.set_prefix_params(**params) if not self.params.has_key('data_dir') and data_dir is not None: self.params['data_dir'] = os.path.join(data_dir, 'sessions') def get_session(self, request, **params): return Session(request, **self.get_params(**params)) def clone(self, **params): p = self.get_params(**params) arg = MyghtySessionArgs() arg.params = p return arg myghty-1.1/lib/myghty/synchronization.py0000644000175000017500000002750010501064120017565 0ustar malexmalex# $Id: synchronization.py 2013 2005-12-31 03:19:39Z zzzeek $ # synchronization.py - synchronization functions for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # __all__ = ["Synchronizer", "NameLock", "_threading", "_thread"] import os, weakref, tempfile, re, sys from myghty.util import * try: import thread as _thread import threading as _threading except ImportError: import dummy_thread as _thread import dummy_threading as _threading # check for fcntl module try: sys.getwindowsversion() has_flock = False except: try: import fcntl has_flock = True except ImportError: has_flock = False class NameLock: """a proxy for an RLock object that is stored in a name based registry. Multiple threads can get a reference to the same RLock based on the name alone, and synchronize operations related to that name. """ locks = WeakValuedRegistry() class NLContainer: """cant put Lock as a weakref""" def __init__(self, reentrant): if reentrant: self.lock = _threading.RLock() else: self.lock = _threading.Lock() def __call__(self): return self.lock def __init__(self, identifier = None, reentrant = False): self.lock = self._get_lock(identifier, reentrant) def acquire(self, wait = True): return self.lock().acquire(wait) def release(self): self.lock().release() def _get_lock(self, identifier, reentrant): if identifier is None: return NameLock.NLContainer(reentrant) return NameLock.locks.get(identifier, lambda: NameLock.NLContainer(reentrant)) synchronizers = WeakValuedRegistry() def Synchronizer(identifier = None, use_files = False, lock_dir = None, digest_filenames = True): """ returns an object that synchronizes a block against many simultaneous read operations and several synchronized write operations. Write operations are assumed to be much less frequent than read operations, and receive precedence when they request a write lock. uses strategies to determine if locking is performed via threading objects or file objects. the identifier identifies a name this Synchronizer is synchronizing against. All synchronizers of the same identifier will lock against each other, within the effective thread/process scope. use_files determines if this synchronizer will lock against thread mutexes or file locks. this sets the effective scope of the synchronizer, i.e. it will lock against other synchronizers in the same process, or against other synchronizers referencing the same filesystem referenced by lock_dir. the acquire/relase methods support nested/reentrant operation within a single thread via a recursion counter, so that only the outermost call to acquire/release has any effect. """ if not has_flock: use_files = False if use_files: # FileSynchronizer is one per thread return synchronizers.sync_get("file_%s_%s" % (identifier, _thread.get_ident()), lambda: FileSynchronizer(identifier, lock_dir, digest_filenames)) else: # ConditionSynchronizer is shared among threads return synchronizers.sync_get("condition_%s" % identifier, lambda: ConditionSynchronizer(identifier)) class SyncState: """used to track the current thread's reading/writing state as well as reentrant block counting""" def __init__(self): self.reentrantcount = 0 self.writing = False self.reading = False class SynchronizerImpl(object): """base for the synchronizer implementations. the acquire/release methods keep track of re-entrant calls within the current thread, and delegate to the do_XXX methods when appropriate.""" def __init__(self, *args, **params): pass def release_read_lock(self): state = self.state if state.writing: raise "lock is in writing state" if not state.reading: raise "lock is not in reading state" if state.reentrantcount == 1: self.do_release_read_lock() state.reading = False state.reentrantcount -= 1 def acquire_read_lock(self, wait = True): state = self.state if state.writing: raise "lock is in writing state" if state.reentrantcount == 0: x = self.do_acquire_read_lock(wait) if (wait or x): state.reentrantcount += 1 state.reading = True return x elif state.reading: state.reentrantcount += 1 return True def release_write_lock(self): state = self.state if state.reading: raise "lock is in reading state" if not state.writing: raise "lock is not in writing state" if state.reentrantcount == 1: self.do_release_write_lock() state.writing = False state.reentrantcount -= 1 def acquire_write_lock(self, wait = True): state = self.state if state.reading: raise "lock is in reading state" if state.reentrantcount == 0: x = self.do_acquire_write_lock(wait) if (wait or x): state.reentrantcount += 1 state.writing = True return x elif state.writing: state.reentrantcount += 1 return True def do_release_read_lock():raise NotImplementedError() def do_acquire_read_lock():raise NotImplementedError() def do_release_write_lock():raise NotImplementedError() def do_acquire_write_lock():raise NotImplementedError() class FileSynchronizer(SynchronizerImpl): """a synchronizer using lock files. as it relies upon flock(), which is not safe to use with the same file descriptor among multiple threads (one file descriptor per thread is OK), a separate FileSynchronizer must exist in each thread.""" def __init__(self, identifier, lock_dir, digest_filenames): self.state = SyncState() if lock_dir is None: lock_dir = tempfile.gettempdir() else: lock_dir = lock_dir self.encpath = EncodedPath(lock_dir, [identifier], extension = '.lock', digest = digest_filenames) self.filename = self.encpath.path self.opened = False self.filedesc = None def _open(self, mode): if not self.opened: try: self.filedesc = os.open(self.filename, mode) except OSError, e: self.encpath.verify_directory() self.filedesc = os.open(self.filename, mode) self.opened = True def do_acquire_read_lock(self, wait): self._open(os.O_CREAT | os.O_RDONLY) if not wait: try: fcntl.flock(self.filedesc, fcntl.LOCK_SH | fcntl.LOCK_NB) ret = True except IOError: ret = False return ret else: fcntl.flock(self.filedesc, fcntl.LOCK_SH) return True def do_acquire_write_lock(self, wait): self._open(os.O_CREAT | os.O_WRONLY) if not wait: try: fcntl.flock(self.filedesc, fcntl.LOCK_EX | fcntl.LOCK_NB) ret = True except IOError: ret = False return ret else: fcntl.flock(self.filedesc, fcntl.LOCK_EX); return True def do_release_read_lock(self): self.release_all_locks() def do_release_write_lock(self): self.release_all_locks() def release_all_locks(self): if self.opened: fcntl.flock(self.filedesc, fcntl.LOCK_UN) os.close(self.filedesc) self.opened = False def __del__(self): if os.access(self.filename, os.F_OK): try: os.remove(self.filename) except OSError: # occasionally another thread beats us to it pass class ConditionSynchronizer(SynchronizerImpl): """a synchronizer using a Condition. this synchronizer is based on threading.Lock() objects and therefore must be shared among threads.""" def __init__(self, identifier): self.tlocalstate = ThreadLocal(creator = lambda: SyncState()) # counts how many asynchronous methods are executing self.async = 0 # pointer to thread that is the current sync operation self.current_sync_operation = None # condition object to lock on self.condition = _threading.Condition(_threading.Lock()) state = property(lambda self: self.tlocalstate()) def do_acquire_read_lock(self, wait = True): self.condition.acquire() # see if a synchronous operation is waiting to start # or is already running, in which case we wait (or just # give up and return) if wait: while self.current_sync_operation is not None: self.condition.wait() else: if self.current_sync_operation is not None: self.condition.release() return False self.async += 1 self.condition.release() if not wait: return True def do_release_read_lock(self): self.condition.acquire() self.async -= 1 # check if we are the last asynchronous reader thread # out the door. if self.async == 0: # yes. so if a sync operation is waiting, notifyAll to wake # it up if self.current_sync_operation is not None: self.condition.notifyAll() elif self.async < 0: raise "Synchronizer error - too many release_read_locks called" self.condition.release() def do_acquire_write_lock(self, wait = True): self.condition.acquire() # here, we are not a synchronous reader, and after returning, # assuming waiting or immediate availability, we will be. if wait: # if another sync is working, wait while self.current_sync_operation is not None: self.condition.wait() else: # if another sync is working, # we dont want to wait, so forget it if self.current_sync_operation is not None: self.condition.release() return False # establish ourselves as the current sync # this indicates to other read/write operations # that they should wait until this is None again self.current_sync_operation = _threading.currentThread() # now wait again for asyncs to finish if self.async > 0: if wait: # wait self.condition.wait() else: # we dont want to wait, so forget it self.current_sync_operation = None self.condition.release() return False self.condition.release() if not wait: return True def do_release_write_lock(self): self.condition.acquire() if self.current_sync_operation != _threading.currentThread(): raise "Synchronizer error - current thread doesnt have the write lock" # reset the current sync operation so # another can get it self.current_sync_operation = None # tell everyone to get ready self.condition.notifyAll() # everyone go !! self.condition.release() myghty-1.1/lib/myghty/util.py0000644000175000017500000004276210501064120015310 0ustar malexmalex# $Id: util.py 2133 2006-09-06 18:52:56Z dairiki $ # util.py - utility functions for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # __all__ = ["OrderedDict", "ThreadLocal", "Value", "InheritedDict", "ConstructorClone", "Registry", "WeakValuedRegistry", "SyncDict", "LRUCache", "argdict", "EncodedPath", "pid", "thread_id", "verify_directory", "PrefixArgs", "module", "StringIO"] try: import thread as _thread import threading as _threading except ImportError: import dummy_thread as _thread import dummy_threading as _threading import weakref, inspect, sha, string, os, UserDict, copy, sys, imp, re, stat, types, time try: from cStringIO import StringIO except ImportError: from StringIO import StringIO def thread_id(): return _thread.get_ident() def pid(): return os.getpid() def verify_directory(dir): """verifies and creates a directory. tries to ignore collisions with other threads and processes.""" tries = 0 while not os.access(dir, os.F_OK): try: tries += 1 os.makedirs(dir, 0750) except: if tries > 5: raise def module(name): """imports a module, in the ordinary way, by string name""" mod = __import__(name) components = name.split('.') for comp in components[1:]: mod = getattr(mod, comp) return mod class argdict(dict): """supports the argument constructor form of dict which doesnt seem to be present in python 2.2""" def __init__(self, **params): dict.__init__(self) self.update(params) class Value: """allows pass-by-reference operations""" def __init__(self, value = None): self.value = value def __call__(self, *arg): if len(arg): self.assign(arg[0]) else: return self.value def __str__(self): return str(self.value) def assign(self, value): self.value = value class ThreadLocal: """stores a value on a per-thread basis""" def __init__(self, value = None, default = None, creator = None): self.dict = {} self.default = default self.creator = creator if value: self.put(value) def __call__(self, *arg): if len(arg): self.put(arg[0]) else: return self.get() def __str__(self): return str(self.get()) def assign(self, value): self.dict[_thread.get_ident()] = value def put(self, value): self.assign(value) def exists(self): return self.dict.has_key(_thread.get_ident()) def get(self, *args, **params): if not self.dict.has_key(_thread.get_ident()): if self.default is not None: self.put(self.default) elif self.creator is not None: self.put(self.creator(*args, **params)) return self.dict[_thread.get_ident()] def remove(self): del self.dict[_thread.get_ident()] class OrderedDict(UserDict.DictMixin): """A Dictionary that keeps its own internal ordering""" def __init__(self, values = None): self.list = [] self.dict = {} if values is not None: for val in values: self.update(val) def keys(self): return self.list def update(self, dict): for key in dict.keys(): self.__setitem__(key, dict[key]) def values(self): return map(lambda key: self[key], 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): del self.dict[key] del self.list[self.list.index(key)] def __setitem__(self, key, object): if not self.has_key(key): self.list.append(key) self.dict.__setitem__(key, object) def __getitem__(self, key): return self.dict.__getitem__(key) class InheritedDict(UserDict.DictMixin): """a dictionary that can defer lookups to a second dictionary if the key is not found locally.""" def __init__(self, dict, superfunc): self.dict = dict self.superfunc = superfunc def __call__(self, key = None, value = None): if key is None and value is None: return self.dict elif value is None: try: return self.__getitem__(key) except KeyError: return None else: self.__setitem__(key, value) def __getitem__(self, key): dict = self.dict if dict.has_key(key): return dict[key] else: parent = self.superfunc() if parent is not None: return parent[key] raise KeyError(key) def __setitem__(self, key, value): self.dict[key] = value def __delitem__(self, key): del self.dict[key] def keys(self): return self.dict.keys() def __contains__(self, key): return self.has_key(key) def has_key(self, key): if self.dict.has_key(key): return True parent = self.superfunc() if parent is not None: return parent.has_key(key) return False class ConstructorClone: """cloning methods that take additional parameters. one method is a straight shallow copy, the other recreates the object via its constructor. both methods assume a relationship between the given parameters and the attribute names of the object.""" def __init__(self, instance, **params): self.classobj = instance.__class__ self.instance = instance self.params = params def copyclone(self): cl = copy.copy(self.instance) for key, value in self.params.iteritems(): setattr(cl, key, value) return cl # store the argument specs in a static hash argspecs = {} def clone(self): """creates a new instance of the class using the regular class constructor. the arguments to the constructor are divined from inspecting the parameter names, and pulling those parameters from the original instance's attributes. this is essentially a quickie cheater way to get a clone of an object if you can name your instance variables the same as that of the constructor arguments. """ key = self.classobj.__module__ + "." + self.classobj.__name__ if not ConstructorClone.argspecs.has_key(key): argspec = inspect.getargspec(self.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):] ) ConstructorClone.argspecs[key] = (requiredargs, namedargs) (requiredargs, namedargs) = ConstructorClone.argspecs[key] newargs = [] newparams = {} addlparams = self.params.copy() for arg in requiredargs: if arg == 'self': continue elif self.params.has_key(arg): newargs.append(self.params[arg]) else: newargs.append(getattr(self.instance, arg)) if addlparams.has_key(arg): del addlparams[arg] for arg in namedargs: if addlparams.has_key(arg): del addlparams[arg] if self.params.has_key(arg): newparams[arg] = self.params[arg] else: if hasattr(self.instance, arg): newparams[arg] = getattr(self.instance, arg) else: raise "instance has no attribute '%s'" % arg newparams.update(addlparams) return self.classobj(*newargs, **newparams) class PrefixArgs: """extracts from the given argument dictionary all values with a key '' and stores a reference. """ def __init__(self, prefix): self.prefix = prefix self.params = {} self.prelen = len(prefix) def set_prefix_params(self, **params): """from the given dictionary, copies all values with keys in the form "" to this one.""" for key, item in params.iteritems(): if key[0:self.prelen] == self.prefix: self.params[key[self.prelen:]] = item def set_params(self, **params): """from the given dictionary, copies all key/values to this one.""" self.params.update(params) def get_params(self, **params): """returns a new dictionary with this object's values plus those in the given dictionary, with prefixes stripped from the keys.""" p = self.params.copy() for key, item in params.iteritems(): if key[0:self.prelen] == self.prefix: p[key[self.prelen:]] = item else: p[key] = item return p class SyncDict: """ an efficient/threadsafe singleton map algorithm, a.k.a. "get a value based on this key, and create if not found or not valid" paradigm: exists && isvalid ? get : create works with weakref dictionaries and the LRUCache to handle items asynchronously disappearing from the dictionary. use python 2.3.3 or greater ! a major bug was just fixed in Nov. 2003 that was driving me nuts with garbage collection/weakrefs in this section. """ def __init__(self, mutex, dictionary): self.mutex = mutex self.dict = dictionary def get(self, key, createfunc, mutex = None, isvalidfunc = None): """regular get method. returns the object asynchronously, if present and also passes the optional isvalidfunc, else defers to the synchronous get method which will create it.""" try: if self.has_key(key): return self._get_obj(key, createfunc, mutex, isvalidfunc) else: return self.sync_get(key, createfunc, mutex, isvalidfunc) except KeyError: return self.sync_get(key, createfunc, mutex, isvalidfunc) def sync_get(self, key, createfunc, mutex = None, isvalidfunc = None): if mutex is None: mutex = self.mutex mutex.acquire() try: try: if self.has_key(key): return self._get_obj(key, createfunc, mutex, isvalidfunc, create = True) else: return self._create(key, createfunc) except KeyError: return self._create(key, createfunc) finally: mutex.release() def _get_obj(self, key, createfunc, mutex, isvalidfunc, create = False): obj = self[key] if isvalidfunc is not None and not isvalidfunc(obj): if create: return self._create(key, createfunc) else: return self.sync_get(key, createfunc, mutex, isvalidfunc) else: return obj def _create(self, key, createfunc): obj = createfunc() self[key] = obj return obj def has_key(self, key): return self.dict.has_key(key) def __contains__(self, key): return self.dict.__contains__(key) def __getitem__(self, key): return self.dict.__getitem__(key) def __setitem__(self, key, value): self.dict.__setitem__(key, value) def __delitem__(self, key): return self.dict.__delitem__(key) class Registry(SyncDict): """a registry object.""" def __init__(self): SyncDict.__init__(self, _threading.Lock(), {}) class WeakValuedRegistry(SyncDict): """a registry that stores objects only as long as someone has a reference to them.""" def __init__(self): # weakrefs apparently can trigger the __del__ method of other # unreferenced objects, when you create a new reference. this can occur # when you place new items into the WeakValueDictionary. if that __del__ # method happens to want to access this same registry, well, then you need # the RLock instead of a regular lock, since at the point of dictionary # insertion, we are already inside the lock. SyncDict.__init__(self, _threading.RLock(), weakref.WeakValueDictionary()) class LRUCache(SyncDict): """a cache (mapping class) that stores only a certain number of elements, and discards its least recently used element when full.""" class ListElement: def __init__(self, key, value): self.key = key self.setvalue(value) def setvalue(self, value): self.value = value if hasattr(value, 'size'): self.size = value.size else: self.size = 1 def __init__(self, size, deletefunc = None, sizethreshhold = .2): SyncDict.__init__(self, _threading.Lock(), {}) self.size = size self.maxelemsize = sizethreshhold * size self.head = None self.tail = None self.deletefunc = deletefunc self.currentsize = 0 # inner mutex to synchronize list manipulation # operations independently of the SyncDict self.listmutex = _threading.Lock() def __setitem__(self, key, value): self.listmutex.acquire() try: existing = self.dict.get(key, None) if existing is None: element = LRUCache.ListElement(key, value) #if element.size > self.maxelemsize: return self.dict[key] = element self._insertElement(element) else: #if element.size > self.maxelemsize: #del self.dict[key] #self._removeElement(element) oldsize = existing.size existing.setvalue(value) self.currentsize += (existing.size - oldsize) self._updateElement(existing) self._manageSize() finally: self.listmutex.release() def __getitem__(self, key): self.listmutex.acquire() try: element = self.dict[key] self._updateElement(element) return element.value finally: self.listmutex.release() def __contains__(self, key): return self.dict.has_key(key) def has_key(self, key): return self.dict.has_key(key) def _insertElement(self, element): # zero-length elements are not managed in the LRU queue since they # have no affect on the total size if element.size == 0: return element.previous = None element.next = self.head if self.head is not None: self.head.previous = element else: self.tail = element self.head = element self.currentsize += element.size self._manageSize() def _manageSize(self): # TODO: dont remove one element at a time, remove the # excess in one step while self.currentsize > self.size: oldelem = self.dict[self.tail.key] if self.deletefunc is not None: self.deletefunc(oldelem.value) self.currentsize -= oldelem.size del self.dict[self.tail.key] if self.tail != self.head: self.tail = self.tail.previous self.tail.next = None else: self.tail = None self.head = None def _updateElement(self, element): # zero-length elements are not managed in the LRU queue since they # have no affect on the total size if element.size == 0: return if self.head == element: return e = element.previous e.next = element.next if element.next is not None: element.next.previous = e else: self.tail = e element.previous = None element.next = self.head self.head.previous = element self.head = element # TODO: iteration class EncodedPath: """generates a unique file-accessible path from the given list of identifiers starting at the given root directory.""" def __init__(self, root, identifiers, extension = ".enc", depth = 3, verify = True, digest = True): ident = string.join(identifiers, "_") if digest: ident = sha.new(ident).hexdigest() tokens = [] for d in range(1, depth): tokens.append(ident[0:d]) dir = os.path.join(root, *tokens) if verify: verify_directory(dir) self.dir = dir self.path = os.path.join(dir, ident + extension) def verify_directory(self): verify_directory(self.dir) def get_path(self): return self.path myghty-1.1/lib/Myghty.egg-info/0000755000175000017500000000000010501065764015417 5ustar malexmalexmyghty-1.1/lib/Myghty.egg-info/dependency_links.txt0000644000175000017500000000000110501065763021464 0ustar malexmalex myghty-1.1/lib/Myghty.egg-info/entry_points.txt0000644000175000017500000000032610501065763020715 0ustar malexmalex [paste.paster_create_template] myghty_routes=myghty.paste.templates:RoutesTemplate myghty_simple=myghty.paste.templates:SimpleTemplate myghty_modulecomponents=myghty.paste.templates:MCTemplate myghty-1.1/lib/Myghty.egg-info/PKG-INFO0000644000175000017500000000170010501065763016511 0ustar malexmalexMetadata-Version: 1.0 Name: Myghty Version: 1.1 Summary: View/Controller Framework and Templating Engine Home-page: http://www.myghty.org Author: Mike Bayer Author-email: mike@myghty.org License: MIT License Download-URL: http://www.myghty.org/links.myt?linkid=download Description: A Python-based template and view-controller framework derived from HTML::Mason. Supports the full featureset of Mason, allowing component-based web development with Python-embedded HTML, and includes many new concepts and features not found in Mason. `Development SVN `_ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Text Processing :: Markup myghty-1.1/lib/Myghty.egg-info/requires.txt0000644000175000017500000000005310501065763020014 0ustar malexmalexRoutes >= 1.0 Paste PasteDeploy PasteScriptmyghty-1.1/lib/Myghty.egg-info/SOURCES.txt0000644000175000017500000002252210501065763017305 0ustar malexmalexCHANGES LICENSE README ez_setup.py setup.py doc/genhtml.py doc/components/autohandler doc/components/configuration.myt doc/components/doclib.myt doc/components/formatting.myt doc/components/index.myt doc/components/printsection.myt doc/components/pydoc.myt doc/components/section_wrapper.myt doc/content/cache.myt doc/content/components.myt doc/content/docstrings.myt doc/content/document_base.myt doc/content/embedding.myt doc/content/filtering.myt doc/content/globals.myt doc/content/inheritance.myt doc/content/installation.myt doc/content/modulecomponents.myt doc/content/otherblocks.myt doc/content/params.myt doc/content/programmatic.myt doc/content/request.myt doc/content/resolver.myt doc/content/scopedpython.myt doc/content/session.myt doc/content/specialtempl.myt doc/content/technical.myt doc/content/unicode.myt doc/content/whatsitdo.myt doc/html/docs.css doc/html/style.css doc/html/syntaxhighlight.css doc/lib/documentgen.py doc/lib/highlight.py examples/favicon.ico examples/index.myt examples/myghty_small.png examples/common/autohandler examples/common/header.myc examples/common/modulecomponents.py examples/components/README examples/components/autohandler examples/components/component.myt examples/components/index.myt examples/formcontrols/README examples/formcontrols/components.myt examples/formcontrols/field.myt examples/formcontrols/index.myt examples/formvisitor/formfields.myc examples/formvisitor/register.myt examples/formvisitor/registration.myt examples/formvisitor/lib/formvisitor.py examples/myghtyjax/hourglass.gif examples/myghtyjax/hourglass.myt examples/myghtyjax/index.myt examples/myghtyjax/myghtyjax.js examples/myghtyjax/myghtyjax.myt examples/myghtyjax/docs/document_base.myt examples/myghtyjax/docs/index.myt examples/myghtyjax/docs/intro.myt examples/shoppingcart/run_cart.py examples/shoppingcart/components/address.myc examples/shoppingcart/components/autohandler examples/shoppingcart/components/breadcrumb.myc examples/shoppingcart/components/ccard.myc examples/shoppingcart/components/common.myc examples/shoppingcart/components/dhandler examples/shoppingcart/components/forms.myc examples/shoppingcart/components/header.myc examples/shoppingcart/components/sidebar.myc examples/shoppingcart/htdocs/architecture.myt examples/shoppingcart/htdocs/index.myt examples/shoppingcart/htdocs/shopping.css examples/shoppingcart/htdocs/syntaxhighlight.css examples/shoppingcart/htdocs/images/hat_myghty.gif examples/shoppingcart/htdocs/images/hat_python.gif examples/shoppingcart/htdocs/images/hat_snake.gif examples/shoppingcart/htdocs/images/logo.gif examples/shoppingcart/htdocs/images/mug_myghty.gif examples/shoppingcart/htdocs/images/mug_python.gif examples/shoppingcart/htdocs/images/mug_snake.gif examples/shoppingcart/htdocs/images/tshirt_myghty.gif examples/shoppingcart/htdocs/images/tshirt_python.gif examples/shoppingcart/htdocs/images/tshirt_snake.gif examples/shoppingcart/lib/form.py examples/shoppingcart/lib/sample_httpd.conf examples/shoppingcart/lib/shoppingcontroller.py examples/shoppingcart/lib/shoppingdata.py examples/shoppingcart/lib/shoppingmodel.py examples/shoppingcart/lib/statemachine.py examples/shoppingcart/templates/cart.myt examples/shoppingcart/templates/catalog.myt examples/shoppingcart/templates/checkout.myt examples/shoppingcart/templates/confirm.myt examples/shoppingcart/templates/item.myt examples/shoppingcart/templates/viewsource.myt examples/template/README examples/template/another.myt examples/template/autohandler examples/template/index.myt examples/template/layout.myt examples/template/layout2.myt examples/template/style.css examples/template/toolbar.myt examples/zblog/README examples/zblog/bin/server.py examples/zblog/components/components.myc examples/zblog/components/data.myc examples/zblog/components/form.myc examples/zblog/components/toolbar.myc examples/zblog/config/server_config.py examples/zblog/htdocs/autohandler examples/zblog/htdocs/favicon.ico examples/zblog/htdocs/global.js examples/zblog/htdocs/index.myt examples/zblog/htdocs/login.myt examples/zblog/htdocs/register.myt examples/zblog/htdocs/style.css examples/zblog/htdocs/admin/blog.myt examples/zblog/htdocs/admin/index.myt examples/zblog/htdocs/admin/user.myt examples/zblog/htdocs/ajax/myghtyjax.js examples/zblog/htdocs/ajax/myghtyjax.myt examples/zblog/htdocs/blog/forms.myt examples/zblog/htdocs/blog/index.myt examples/zblog/htdocs/blog/post.myt examples/zblog/htdocs/blog/postcomment.myt examples/zblog/htdocs/blog/views.myt examples/zblog/htdocs/bootstrap/complete.myt examples/zblog/htdocs/bootstrap/index.myt examples/zblog/lib/zblog/__init__.py examples/zblog/lib/zblog/controller/__init__.py examples/zblog/lib/zblog/controller/bootstrap.py examples/zblog/lib/zblog/controller/front.py examples/zblog/lib/zblog/controller/index.py examples/zblog/lib/zblog/controller/login.py examples/zblog/lib/zblog/controller/blog/__init__.py examples/zblog/lib/zblog/controller/blog/comments.py examples/zblog/lib/zblog/controller/blog/index.py examples/zblog/lib/zblog/controller/manage/__init__.py examples/zblog/lib/zblog/controller/manage/blog.py examples/zblog/lib/zblog/controller/manage/index.py examples/zblog/lib/zblog/controller/manage/user.py examples/zblog/lib/zblog/database/__init__.py examples/zblog/lib/zblog/database/mappers.py examples/zblog/lib/zblog/database/tables.py examples/zblog/lib/zblog/domain/__init__.py examples/zblog/lib/zblog/domain/actions.py examples/zblog/lib/zblog/domain/blog.py examples/zblog/lib/zblog/domain/user.py examples/zblog/lib/zblog/util/__init__.py examples/zblog/lib/zblog/util/form.py lib/Myghty.egg-info/PKG-INFO lib/Myghty.egg-info/SOURCES.txt lib/Myghty.egg-info/dependency_links.txt lib/Myghty.egg-info/entry_points.txt lib/Myghty.egg-info/requires.txt lib/Myghty.egg-info/top_level.txt lib/myghty/__init__.py lib/myghty/args.py lib/myghty/buffer.py lib/myghty/cache.py lib/myghty/compiler.py lib/myghty/component.py lib/myghty/container.py lib/myghty/csource.py lib/myghty/escapes.py lib/myghty/exception.py lib/myghty/importer.py lib/myghty/interp.py lib/myghty/lexer.py lib/myghty/objgen.py lib/myghty/request.py lib/myghty/requestbuffer.py lib/myghty/resolver.py lib/myghty/session.py lib/myghty/synchronization.py lib/myghty/util.py lib/myghty/ext/__init__.py lib/myghty/ext/memcached.py lib/myghty/ext/routeresolver.py lib/myghty/http/ApacheHandler.py lib/myghty/http/CGIHandler.py lib/myghty/http/HTTPHandler.py lib/myghty/http/HTTPServerHandler.py lib/myghty/http/WSGIHandler.py lib/myghty/http/__init__.py lib/myghty/paste/__init__.py lib/myghty/paste/templates.py lib/myghty/paste/wsgiapp.py lib/myghty/paster_templates/__init__.py lib/myghty/paster_templates/modulecomponents/server.conf_tmpl lib/myghty/paster_templates/modulecomponents/setup.py_tmpl lib/myghty/paster_templates/modulecomponents/+package+/__init__.py lib/myghty/paster_templates/modulecomponents/+package+/webconfig.py lib/myghty/paster_templates/modulecomponents/+package+/wsgiapp.py_tmpl lib/myghty/paster_templates/modulecomponents/+package+/components/autohandler lib/myghty/paster_templates/modulecomponents/+package+/components/footer.myc lib/myghty/paster_templates/modulecomponents/+package+/components/header.myc lib/myghty/paster_templates/modulecomponents/+package+/components/leftnav.myc lib/myghty/paster_templates/modulecomponents/+package+/htdocs/myghty_small.png lib/myghty/paster_templates/modulecomponents/+package+/htdocs/style.css lib/myghty/paster_templates/modulecomponents/+package+/lib/controller.py lib/myghty/paster_templates/modulecomponents/+package+/templates/index.myt lib/myghty/paster_templates/modulecomponents/+package+/templates/login.myt lib/myghty/paster_templates/modulecomponents/+package+/templates/logout.myt lib/myghty/paster_templates/routes/server.conf_tmpl lib/myghty/paster_templates/routes/setup.py_tmpl lib/myghty/paster_templates/routes/+package+/__init__.py lib/myghty/paster_templates/routes/+package+/webconfig.py_tmpl lib/myghty/paster_templates/routes/+package+/wsgiapp.py_tmpl lib/myghty/paster_templates/routes/+package+/controllers/__init__.py lib/myghty/paster_templates/routes/+package+/controllers/application_controller.py lib/myghty/paster_templates/routes/+package+/lib/__init__.py lib/myghty/paster_templates/routes/+package+/public/index.html_tmpl lib/myghty/paster_templates/routes/+package+/templates/autohandler_tmpl lib/myghty/paster_templates/simple/server.conf_tmpl lib/myghty/paster_templates/simple/setup.py_tmpl lib/myghty/paster_templates/simple/+package+/__init__.py lib/myghty/paster_templates/simple/+package+/webconfig.py_tmpl lib/myghty/paster_templates/simple/+package+/wsgiapp.py_tmpl lib/myghty/paster_templates/simple/+package+/components/autohandler_tmpl lib/myghty/paster_templates/simple/+package+/components/footer.myc_tmpl lib/myghty/paster_templates/simple/+package+/components/header.myc_tmpl lib/myghty/paster_templates/simple/+package+/htdocs/index.myt_tmpl lib/myghty/paster_templates/simple/+package+/htdocs/style.css_tmpl test/__init__.py test/alltests.py test/perf_test_container.py test/perf_test_memusage.py test/perf_test_syncdict.py test/test_cache_self.py test/test_component.py test/test_component_encoding.py test/test_component_recoding.py test/test_component_reloading.py test/test_escapes.py test/test_filter.py test/test_html_errors.py test/test_logger.py test/test_lru_cache.py test/test_requestbuffer.py test/test_resolver.py test/test_subrequest.py test/test_testbase.py test/testbase.py tools/gen.py tools/myghty.cgi tools/run_docs.py myghty-1.1/lib/Myghty.egg-info/top_level.txt0000644000175000017500000000000710501065763020145 0ustar malexmalexmyghty myghty-1.1/LICENSE0000644000175000017500000000226310501064125012674 0ustar malexmalexThis is the MIT license: http://www.opensource.org/licenses/mit-license.php Copyright (c) 2004, 2005, 2006 Michael Bayer and contributors. Myghty is a trademark of Michael Bayer. 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.myghty-1.1/PKG-INFO0000644000175000017500000000170010501065765012772 0ustar malexmalexMetadata-Version: 1.0 Name: Myghty Version: 1.1 Summary: View/Controller Framework and Templating Engine Home-page: http://www.myghty.org Author: Mike Bayer Author-email: mike@myghty.org License: MIT License Download-URL: http://www.myghty.org/links.myt?linkid=download Description: A Python-based template and view-controller framework derived from HTML::Mason. Supports the full featureset of Mason, allowing component-based web development with Python-embedded HTML, and includes many new concepts and features not found in Mason. `Development SVN `_ Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Text Processing :: Markup myghty-1.1/README0000644000175000017500000000451610501064125012552 0ustar malexmalexQuick run of Myghty, browse docs, try examples: python ./tools/run_docs.py Then point your browser over to: http://localhost:8000/ To install, change to your superuser's account, and type: python setup.py install This uses a setuptools, which is a new Python Eggs-based install and requires network access to install dependencies. The above setup will detect previous versions of Myghty installed by the old distutils and produce a "conflict warning". If an old version of Myghty is still present, you can delete it manually, or you can run the ez_setup script to remove it: python setup.py easy_install -D Myghty If frustrated, network access is not working, dependencies crashing, etc., the old version of the setup script is present, which will install just the Myghty core libraries: python setup.py install --old-and-unmanageable Note that installing the old way (distutils_setup.py) will create conflicts with the new way (setup.py), which would need to be resolved by removing the installed files before running the new way. To build documentation: cd doc; python ./genhtml.py which will build docs as flat html files in doc/html. Configuration for mod_python, CGI, others are in the "Configuration" chapter of the documentation. Brand new Python Paste setup templates, which automatically build sample configurations that are runnable via command line, are present too, and are outlined in the "Installation" chapter of the documentation. Basically, install Myghty, which will also download and install Python Paste. Then install the .egg version of WSGIUtils, via: python ez_setup.py http://pylons.groovie.org/files/WSGIUtils-0.6-py2.4.egg Then cd into a target directory and type: /path/to/python/bin/paster create --template=myghty_simple myprojectname cd myprojectname paster serve server.conf The site is then available via http://localhost:8000 . Available templates are "myghty_simple", "myghty_modulecomponents" and "myghty_routes", which shows off the new Routes extension resolver. More examples are available in ./examples. Most recent docs at: http://www.myghty.org/ Myghty is licensed under an MIT-style license (see LICENSE). Other incorporated projects may be licensed under different licenses. All licenses allow for non-commercial and commercial use. - mike(at)myghty.org . myghty-1.1/setup.cfg0000644000175000017500000000007310501065765013520 0ustar malexmalex[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 myghty-1.1/setup.py0000644000175000017500000000411310501064125013375 0ustar malexmalexfrom ez_setup import use_setuptools use_setuptools() from setuptools import setup, find_packages setup(name = "Myghty", version = "1.1", description = "View/Controller Framework and Templating Engine", author = "Mike Bayer", author_email = "mike@myghty.org", url = "http://www.myghty.org", download_url = "http://www.myghty.org/links.myt?linkid=download", package_dir = {'':'lib'}, packages = find_packages('lib'), license = "MIT License", long_description = """\ A Python-based template and view-controller framework derived from HTML::Mason. Supports the full featureset of Mason, allowing component-based web development with Python-embedded HTML, and includes many new concepts and features not found in Mason. `Development SVN `_ """, classifiers = ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Text Processing :: Markup" ], package_data={ 'myghty': [ 'paster_templates/*/*.py', 'paster_templates/*/*_tmpl', 'paster_templates/*/+package+/*.py', 'paster_templates/*/+package+/*.my?', 'paster_templates/*/+package+/*_tmpl', 'paster_templates/*/+package+/*/*.py', 'paster_templates/*/+package+/*/*.my?', 'paster_templates/*/+package+/*/*_tmpl', 'paster_templates/*/+package+/*/*.css', 'paster_templates/*/+package+/*/*.png', 'paster_templates/*/+package+/*/*handler', ], }, install_requires=["Routes >= 1.0", "Paste", "PasteDeploy", "PasteScript"], entry_points=""" [paste.paster_create_template] myghty_routes=myghty.paste.templates:RoutesTemplate myghty_simple=myghty.paste.templates:SimpleTemplate myghty_modulecomponents=myghty.paste.templates:MCTemplate """, ) myghty-1.1/test/0000755000175000017500000000000010501065765012656 5ustar malexmalexmyghty-1.1/test/__init__.py0000644000175000017500000000000010501064113014737 0ustar malexmalexmyghty-1.1/test/alltests.py0000644000175000017500000000077310501064113015054 0ustar malexmaleximport unittest, os import testbase def suite(): modules_to_test = tuple( [ name for name, ext in map(os.path.splitext, os.listdir(os.path.dirname(__file__))) if ext == '.py' and name.startswith('test_') ]) alltests = unittest.TestSuite() for module in map(__import__, modules_to_test): alltests.addTest(unittest.findTestCases(module)) return alltests if __name__ == '__main__': testbase.runTests(suite()) myghty-1.1/test/perf_test_container.py0000644000175000017500000001071210501064113017250 0ustar malexmalexfrom myghty.container import * import random, time, weakref, sys, re import myghty.buffer import testbase import unittest, sys # container test - # tests the container's get_value() function mostly, to insure # that items are recreated when expired, and that create function # is called exactly once per expiration try: import thread except: raise "this test requires a thread-enabled python" class item: def __init__(self, id): self.id = id def __str__(self): return "item id %d" % self.id def test_item(self): return True class context(ContainerContext): pass #def __init__(self): # ContainerContext.__init__(self, log_file = myghty.buffer.LogFormatter(myghty.buffer.LinePrinter(sys.stdout), "test", id_threads = True)) # keep running indicator running = False starttime = time.time() # creation func entrance detector to detect non-synchronized access # to the create function baton = None context = context() def create(id, delay = 0): global baton if baton is not None: raise "baton is not none , ident " + repr(baton) + " this thread " + repr(thread.get_ident()) baton = thread.get_ident() try: i = item(id) time.sleep(delay) global totalcreates totalcreates += 1 return i finally: baton = None def test(cclass, id, statusdict, expiretime, delay, params): print "create thread %d starting" % id statusdict[id] = True try: container = cclass(context = context, namespace = 'test', key = 'test', createfunc = lambda: create(id, delay), expiretime = expiretime, data_dir='./cache', starttime = starttime, **params) global running global totalgets try: while running: item = container.get_value() if not item.test_item(): raise "item did not test" item = None totalgets += 1 time.sleep(random.random() * .00001) except: e = sys.exc_info()[0] running = False print e raise finally: print "create thread %d exiting" % id statusdict[id] = False def runtest(cclass, totaltime, expiretime, delay, nthreads=20, **params): statusdict = {} global totalcreates totalcreates = 0 global totalgets totalgets = 0 container = cclass(context = context, namespace = 'test', key = 'test', createfunc = lambda: create(id, delay), expiretime = expiretime, data_dir='./cache', starttime = starttime, **params) container.clear_value() global running running = True for t in range(1, nthreads): thread.start_new_thread(test, (cclass, t, statusdict, expiretime, delay, params)) time.sleep(totaltime) failed = not running running = False pause = True while pause: time.sleep(1) pause = False for v in statusdict.values(): if v: pause = True break if failed: raise "test failed" print "total object creates %d" % totalcreates print "total object gets %d" % totalgets class ContainerTest(testbase.MyghtyTest): def _runtest(self, cclass, totaltime, expiretime, delay, **params): print "\ntesting %s for %d secs with expiretime %s delay %d" % ( cclass, totaltime, expiretime, delay) runtest(cclass, totaltime, expiretime, delay, **params) if expiretime is None: self.assert_(totalcreates == 1) else: self.assert_(abs(totaltime / expiretime - totalcreates) <= 2) def testMemoryContainer(self, totaltime=10, expiretime=None, delay=0): self._runtest(container_registry('memory', 'Container'), totaltime, expiretime, delay) def testMemoryContainer2(self): self.testMemoryContainer(expiretime=2) def testMemoryContainer3(self): self.testMemoryContainer(expiretime=5, delay=2) def testDbmContainer(self, totaltime=10, expiretime=None, delay=0, **kw): self._runtest(container_registry('dbm', 'Container'), totaltime, expiretime, delay, **kw) def testDbmContainer2(self): self.testDbmContainer(expiretime=2, nthreads=8) def testDbmContainer3(self): self.testDbmContainer(expiretime=5, delay=2) if __name__ == "__main__": unittest.main() myghty-1.1/test/perf_test_memusage.py0000644000175000017500000001124410501064113017072 0ustar malexmaleximport myghty.interp as interp import myghty.buffer as buffer import myghty.exception as exception import os, sys, gc, random, StringIO, time, thread, traceback import testbase import unittest use_static_source = False try: import threadframe has_threadframe = True except: has_threadframe = False # creates file based components with unique names, executes them, then deletes them. # watch the memory usage to see if it creeps up. with the # advent of the FileComponent __del__ method # cleaning its module from sys.modules, it should stay constant. threadcount = 0 gtime = time.time() ccount = 240 use_files = True def doit(i, regen): filename = "comp%s.myt" % i resolution = interpreter.resolve_component(filename) csource = resolution.csource if (regen > 6): # force a reload now and then csource.last_modified = time.time() pass comp = interpreter.load_component(csource) outbuf = StringIO.StringIO() m = interpreter.make_request(comp, out_buffer = outbuf) interpreter.debug("Executing.... %s id %d num elements in cache is %d sys.modules size is %d " % ( filename, id(comp), len(interpreter.code_cache.dict), len(sys.modules) )) m.execute() interpreter.debug("Done executing %s" % filename) comp = None def do_test(): global threadcount global gtime threadcount += 1 # now randomly recreate files and execute the components in them try: for i in range(1,1000): doit(random.randint(1, ccount), random.randint(1,8)) gtime = time.time() time.sleep(.000005) except exception.Error, e: print "thread id %s" % thread.get_ident() e.initTraceback(interpreter) print e.textformat() except Exception, e2: import traceback traceback.print_exc(file=sys.stdout) print "THREAD EXITING !!!! threadcount is %d" % threadcount threadcount -= 1 class TestMemUsage(testbase.MyghtyTest): def setUp(self): # now make (ccount) myghty files for i in range(1,ccount + 1): self.makefile(i) print "created %d files" % ccount # make an interpreter, with a really freekin small cache global interpreter if use_files: data_dir = self.cache else: data_dir = None interpreter = interp.Interpreter(delete_modules = True, component_root = self.htdocs, data_dir=data_dir, code_cache_size=5000, raise_error = True, debug_file=buffer.LinePrinter(sys.stdout), debug_threads = True, debug_elements=['codecache'], use_static_source = use_static_source) def makefile(self, i): filename = "comp%s.myt" % i c = """ <%global> import os i am component #%s\n" % i # this kind of call breaks if sys.modules isnt populated with # this component's module os info is: <% os.getcwd() %> """ self.create_file(self.htdocs, filename, c) def delete(self, i): filename = "comp%s.myt" % i resolution = interpreter.resolve_component(filename) csource = resolution.csource if use_files: object_files = \ interpreter.get_component_object_files(csource) #print "deleting %s, %s" % (filename, repr(object_files)) for f in object_files: try: os.unlink(f) except OSError, e: # (SIC): print this? ignore this? "Error deleting %s, probably never called" % f self.remove_file(self.htdocs, filename) def testmemory(self): print "starting memory test" # now..threads ! for t in range(1, 10): thread.start_new_thread(do_test, ()) time.sleep(5) while threadcount > 0: time.sleep(1) if time.time() - gtime > 10: print "deadlock !" if has_threadframe: frames = threadframe.dict() for thread_id, frame in frames.iteritems(): print '-' * 72 print '[%s] %d' % (thread_id, sys.getrefcount(frame)) traceback.print_stack(frame) sys.exit(-1) # look for garbage gc.collect() print repr(gc.garbage) def tearDown(self): for i in range(1, ccount): self.delete(i) print "deleted %d files" % ccount if __name__ == "__main__": testbase.runTests(unittest.findTestCases(__import__('__main__'))) myghty-1.1/test/perf_test_syncdict.py0000644000175000017500000000775710501064113017125 0ustar malexmalexfrom myghty.util import SyncDict import random, time, weakref, sys # this script tests SyncDict for its thread safety, # ability to always return a value even for a dictionary # that loses data randomly, and # insures that when used as a registry, only one instance # of a particular key/value exists at any one time. try: import thread except: raise "this test requires a thread-enabled python" class item: def __init__(self, id): self.id = id def __str__(self): return "item id %d" % self.id # keep running indicator running = False # one item is referenced at a time (to insure singleton pattern) theitem = weakref.ref(item(0)) # creation func entrance detector to detect non-synchronized access # to the create function baton = None def create(id): global baton if baton is not None: raise "baton is not none !" baton = True try: global theitem if theitem() is not None: raise "create %d old item is still referenced" % id i = item(id) theitem = weakref.ref(i) global totalcreates totalcreates += 1 return i finally: baton = None def test(s, id, statusdict): print "create thread %d starting" % id statusdict[id] = True try: global running global totalgets try: while running: s.get('test', lambda: create(id)) totalgets += 1 time.sleep(random.random() * .00001) except: e = sys.exc_info()[0] running = False print e finally: print "create thread %d exiting" % id statusdict[id] = False def runtest(s): statusdict = {} global totalcreates totalcreates = 0 global totalremoves totalremoves = 0 global totalgets totalgets = 0 global running running = True for t in range(1, 10): thread.start_new_thread(test, (s, t, statusdict)) time.sleep(1) for x in range (0,10): if not running: break print "Removing item" totalremoves += 1 try: del s['test'] except KeyError: pass time.sleep(random.random() * .89) failed = not running running = False pause = True while pause: time.sleep(1) pause = False for v in statusdict.values(): if v: pause = True break if failed: raise "test failed" print "total object creates %d" % totalcreates print "total object gets %d" % totalgets print "total object removes %d" % totalremoves # normal dictionary test, where we will remove the value # periodically. the number of creates should be equal to # the number of removes plus one. print "\ntesting with normal dict" runtest(SyncDict(thread.allocate_lock(), {})) assert(totalremoves + 1 == totalcreates) # the goofydict is designed to act like a weakvaluedictionary, # where its values are dereferenced and disposed of # in between a has_key() and a # __getitem__() operation 50% of the time. # the number of creates should be about half of what the # number of gets is. class goofydict(dict): def has_key(self, key): if dict.has_key(self, key): if random.random() > 0.5: del self[key] return True else: return False print "\ntesting with goofy dict" runtest(SyncDict(thread.allocate_lock(), goofydict())) assert(float(totalcreates) / float(totalgets) < .52 and float(totalcreates) / float(totalgets) > .48) # the weakvaluedictionary test involves newly created items # that are instantly disposed since no strong reference exists to them. # the number of creates should be equal to the number of gets. print "\ntesting with weak dict" runtest(SyncDict(thread.allocate_lock(), weakref.WeakValueDictionary())) assert(totalcreates == totalgets) myghty-1.1/test/test_cache_self.py0000644000175000017500000000617710501064113016340 0ustar malexmalex# test/cache_self.py """Tests for m.cache_self """ import unittest, os, shutil, stat, sys, time, warnings import testbase from test_component_reloading import ComponentReloadTests, purge_module class CacheSelfTests(ComponentReloadTests): """Abstract base class of tests for component reloading.""" def setUp(self): # Blow away cache shutil.rmtree(os.path.join(self.cache, 'container_dbm'), ignore_errors=True) #time.sleep(1) ComponentReloadTests.setUp(self) def reinitializeInterpreter(self): params = self.resolverArgs() self.module_runner = self.module_runner_class(data_dir=self.cache, **params) def testComponent(self, text="howdy"): """Test that we can resolve and execute our component. """ self.failUnlessEqual(self.executeComponent().strip(), text) def testCache(self): """Test that cache remains intact across interpreter incarnations. """ self.testComponent() time.sleep(1) self.changeComponentSource(text="changed") # Create a new myghty interpreter self.reinitializeInterpreter() # We should still see the cached value, not the changed value self.testComponent() def testStaleCache(self): """Test that cache gets invalidated if the component source changes. """ #os.system("ls -lR " + self.cache) self.testComponent() time.sleep(1) self.createComponentSource(text="changed") # Create a new myghty interpreter self.reinitializeInterpreter() self.testComponent("changed") ################################################################ # # (The actual unittest.TestCase sub-classes begin here.) # ################################################################ class FileComponentTests(CacheSelfTests, testbase.MyghtyTest): def srcFile(self): return os.path.join(self.htdocs, "comp.myt") def srcText(self, text="howdy"): return """ <%%init> if m.cache_self(): return %s """ % text def resolverArgs(self): return {'component_root': self.htdocs} def componentPath(self): return "/comp.myt" class ModuleComponentTests(CacheSelfTests, testbase.MyghtyTest): def setUp(self): purge_module('module') CacheSelfTests.setUp(self) def reinitializeInterpreter(self): purge_module('module') CacheSelfTests.reinitializeInterpreter(self) def srcFile(self): return os.path.join(self.lib, "module.py") def srcText(self, text="howdy"): return """ def comp(m): if not m.cache_self(): m.write('%s')""" % text def resolverArgs(self): try: del sys.modules['module'] except KeyError: pass return {'module_components': [{'/mycomp': 'module:comp'}]} def componentPath(self): return "/mycomp" if __name__ == '__main__': runner = unittest.TextTestRunner(verbosity=2, descriptions=False) unittest.main(testRunner=runner) myghty-1.1/test/test_component.py0000644000175000017500000002275710501064113016270 0ustar malexmaleximport testbase import myghty.interp as interp import myghty.component as component import myghty.csource as csource import myghty.resolver as resolver import myghty.request as request import myghty.exception as exception import myghty.cache as cache import unittest, sys, string, StringIO import posixpath as unixpath class ResolverStub: def __init__(self, result): self.result = result def resolve(self, uri, **params): return self.result class ComponentRunner(testbase.MyghtyTest): def assert_exec(self, csource, args, output, interpreter = None): if interpreter is None: interpreter = interp.Interpreter() comp = interpreter.load_component(csource) buf = StringIO.StringIO() interpreter.execute(comp, request_args = args, out_buffer = buf) self.failUnlessEqual(buf.getvalue(), output) def assert_component(self, component, args, output, interpreter): buf = StringIO.StringIO() interpreter.execute(component, request_args = args, out_buffer = buf) self.assert_(buf.getvalue() == output) class InheritTest(testbase.MyghtyTest): def setUp(self): self.create_file(self.components, 'autohandler', 'im an autohandler') def tearDown(self): self.remove_file(self.components, 'autohandler') def testrecursiveparent(self): """test that a root autohandler component wont let itself be a parent of itself""" acs = csource.FileComponentSource("fooid", "main", "autohandler", unixpath.join(self.components, "autohandler")) i = interp.Interpreter(resolver = ResolverStub(resolver.Resolution(acs, None))) comp = i.load_component(acs) self.assert_(comp is not comp.parent_component, "test failed, returned recursive parent") self.assert_(comp.parent_component is None, "test failed, returned a parent component") class AttrTest(ComponentRunner): def setUp(self): self.create_file(self.htdocs, "compa.myt", """<%attr> foo = 'fooval' % m.call_next()""") self.create_file(self.htdocs, "compb.myt", """<%flags> inherit = '/compa.myt' \ <%attr> bar = 'barval' % m.call_next()""") self.create_file(self.htdocs, "compc.myt", """<%flags> inherit = '/compb.myt' \ <% m.base_component.attributes['bar'] %> <% m.base_component.attributes['foo'] %>""") def testattrinherit(self): interpreter = interp.Interpreter(data_dir=self.cache, component_root=self.htdocs) self.assert_component('compc.myt', {}, "barval\nfooval", interpreter = interpreter) def tearDown(self): self.remove_file(self.htdocs, 'compa.myt') self.remove_file(self.htdocs, 'compb.myt') self.remove_file(self.htdocs, 'compc.myt') class CacheTest(ComponentRunner): def setUp(self): self.create_file(self.htdocs, "memorytest.myt", """ <%flags> use_cache=True cache_type='memory' im the memory test. """) self.create_file(self.htdocs, "filetest.myt", """ <%flags> use_cache=True cache_type='file' im the file test. """) def testmemory(self): acs = csource.FileComponentSource("memid", "main", "memorytest.myt", unixpath.join(self.htdocs, "memorytest.myt")) interpreter = interp.Interpreter() self.assert_exec(acs, {}, "\nim the memory test.\n", interpreter = interpreter) self.assert_exec(acs, {}, "\nim the memory test.\n", interpreter = interpreter) comp = interpreter.code_cache['memid'] self.assert_(interpreter.make_request(comp).get_cache(comp, type='memory')['_self'][0].getvalue() == "\nim the memory test.\n") def testfile(self): acs = csource.FileComponentSource("fileid", "main", "filetest.myt", unixpath.join(self.htdocs, "filetest.myt")) interpreter = interp.Interpreter(data_dir=self.cache) self.assert_exec(acs, {}, "\nim the file test.\n", interpreter = interpreter) self.assert_exec(acs, {}, "\nim the file test.\n", interpreter = interpreter) comp = interpreter.code_cache['fileid'] self.assert_(interpreter.make_request(comp).get_cache(comp, type='file')['_self'][0].getvalue() == "\nim the file test.\n") def tearDown(self): self.remove_file(self.htdocs, 'memorytest.myt') self.remove_file(self.htdocs, 'filetest.myt') ################################################################ class MoreMemoryCacheTests(ComponentRunner): cache_type = 'memory' def setUp(self): # Basically we just need a component... self.create_file(self.htdocs, "cachetest.myt", "im the cache tester.") self.interp = interp.Interpreter(data_dir=self.cache) acs = csource.FileComponentSource("id", "main", "cachetest.myt", unixpath.join(self.htdocs, "cachetest.myt")) self.comp = self.interp.load_component(acs) def tearDown(self): self.remove_file(self.htdocs, 'cachetest.myt') def get_cache(self): """Get a fresh Cache object for the component """ return cache.Cache(component=self.comp, type=self.cache_type, data_dir=self.cache) def cache_keys(self): """List the keys in our components cache. This lists the keys in the underlying NamespaceManager, not just those keys which are cached in the Cache object. """ cache = self.get_cache() nm = cache.get_container(None).get_namespace_manager() # We introduced a bogus None key with the above get_container, # so filter it out... nm.acquire_read_lock() try: return [ x for x in nm.keys() if x is not None ] finally: nm.release_read_lock() def testClear(self): """Test that cache.clear() really works. We test that all entries in the underlying NamespaceManager actually get cleared. """ cache = self.get_cache() cache.get_value('key1', createfunc=lambda: 'value1') self.failUnlessEqual(cache.dict.keys(), ['key1']) self.failUnlessEqual(self.cache_keys(), ['key1']) cache = self.get_cache() self.failUnlessEqual(cache.dict.keys(), []) self.failUnlessEqual(self.cache_keys(), ['key1']) cache.clear() self.failUnlessEqual(self.cache_keys(), []) class MoreFileCacheTests(MoreMemoryCacheTests): cache_type = 'file' class MoreDbmCacheTests(MoreMemoryCacheTests): cache_type = 'dbm' class MoreMemcachedCacheTests(MoreMemoryCacheTests): cache_type = 'ext:memcached' def testClear(self): # FIXME: self.fail("MemcachedNamespaceManager.do_remove() is currently broken." " (It is a no-op.)") ################################################################ class myclass(component.ModuleComponent): def do_run_component(self, m, **params): m.write('hello world') class ClassComponentTest(ComponentRunner): def testrun(self): acs = csource.ModuleComponentSource(arg = myclass) self.assert_exec(acs, {}, "hello world") class FunctionComponentTest(ComponentRunner): def testmarg(self): def mymethod(m): m.write("hello world") acs = csource.ModuleComponentSource(arg = mymethod) self.assert_exec(acs, {}, "hello world") def testnoarg(self): def mymethod(): request.instance().write("hello world") acs = csource.ModuleComponentSource(arg = mymethod) self.assert_exec(acs, {}, "hello world") def testmandarg(self): def mymethod(m, ARGS): m.write("hello world, args: " + repr(ARGS)) acs = csource.ModuleComponentSource(arg = mymethod) self.assert_exec(acs, {'foo': 'bar'}, "hello world, args: " + repr({'foo': 'bar'})) def testcustomargs(self): def mymethod(x, y, z = 7): request.instance().write("hello world, args: %s %s %s" % (repr(x), repr(y), repr(z))) acs = csource.ModuleComponentSource(arg = mymethod) args = dict(x = 5, y = 2) self.assert_exec(acs, args, "hello world, args: 5 2 7") class MethodComponentTest(ComponentRunner): def class_set_up(self): self.create_file(self.lib, 'mymodule3.py', """ import myghty.component as component def dostuff(m): m.write("this is dostuff") def index(m): m.write("this is index") class foo: def __init__(self): self.x = 7 def __call__(self, m): m.write("this is foo __call__") def dofoo(self, m): m.write("this is foo dofoo(), x is " + str(self.x) ) bar = foo() """) self.mymod2 = __import__('mymodule3') def class_tear_down(self): self.remove_file(self.lib, 'mymodule3.py') def testmodfunc(self): dostuff = self.mymod2.dostuff acs = csource.ModuleComponentSource(arg= dostuff) self.assert_exec(acs, {}, "this is dostuff") def testmodmethod(self): bar = self.mymod2.bar acs = csource.ModuleComponentSource(arg = bar.dofoo) self.assert_exec(acs, {}, "this is foo dofoo(), x is 7") def testmodcallable(self): acs = csource.ModuleComponentSource(arg = self.mymod2.bar) self.assert_exec(acs, {}, "this is foo __call__") if __name__ == '__main__': runner = unittest.TextTestRunner(verbosity=2, descriptions=False) unittest.main(testRunner=runner) myghty-1.1/test/test_component_encoding.py0000644000175000017500000001610510501064113020124 0ustar malexmalex# test/component_encoding.py """Test that component's encodings are determined correctly. """ import unittest, os, shutil, sys, warnings, random, StringIO, time, codecs, re import testbase from myghty.interp import Interpreter from myghty.resolver import NotFound from myghty import importer, exception some_encodings = """ascii big5 big5hkscs cp037 cp424 cp437 cp500 cp737 cp775 cp850 cp852 cp855 cp856 cp857 cp860 cp861 cp862 cp863 cp864 cp865 cp866 cp869 cp874 cp875 cp932 cp949 cp950 cp1006 cp1026 cp1140 cp1250 cp1251 cp1252 cp1253 cp1254 cp1255 cp1256 cp1257 cp1258 euc_jp euc_jis_2004 euc_jisx0213 euc_kr gb2312 gbk gb18030 hz iso2022_jp iso2022_jp_1 iso2022_jp_2 iso2022_jp_2004 iso2022_jp_3 iso2022_jp_ext iso2022_kr latin_1 iso8859_2 iso8859_3 iso8859_4 iso8859_5 iso8859_6 iso8859_7 iso8859_8 iso8859_9 iso8859_10 iso8859_13 iso8859_14 iso8859_15 johab koi8_r koi8_u mac_cyrillic mac_greek mac_iceland mac_latin2 mac_roman mac_turkish ptcp154 shift_jis shift_jis_2004 shift_jisx0213 utf_16 utf_16_be utf_16_le utf_7""".split() def isAsciiSuperset(encoding): ascii_chars = ''.join(map(chr, range(0x80))) try: return ascii_chars.decode(encoding) == ascii_chars.decode('ascii') except (ValueError, LookupError): return False some_encodings = [ enc for enc in some_encodings if isAsciiSuperset(enc) and enc != sys.getdefaultencoding() ] random.shuffle(some_encodings) def random_encoding(): enc = some_encodings.pop() some_encodings.insert(0, enc) return enc def parse_magic_comment(text): m = re.match(r'(?:#.*\n)?#.*coding[:=][ \t]*([-\w.]+)', text) if m: return m.group(1) return None def resetwarnings(): warnings.resetwarnings() try: import myghty.compiler del myghty.compiler.__warningregistry__ except AttributeError: pass class FileComponentEncodingTests(testbase.ComponentTester): def getComponent(self, head=""): return self.makeFileBasedComponent(head + "\nbody\n") def getCompiled(self, head=""): """Get the python source for the compiled component. """ return self._getCompiledFromComp(self.getComponent(head)) def _getCompiledFromComp(self, comp): return file(comp.component_source.module.__file__).read() def failUnlessComponentEncodingEqual(self, comp, encoding): #self.failUnlessEqual(comp.component_source.module._ENCODING, # Encoding(encoding)) compiled = self._getCompiledFromComp(comp) enc = parse_magic_comment(compiled) or sys.getdefaultencoding() self.failUnlessEqual(enc, encoding) self.failUnlessEqual(codecs.lookup(enc), codecs.lookup(encoding)) def testSystemDefaultEncoding(self): """Test that component get system default encoding when appropriate. """ comp = self.getComponent() self.failUnlessComponentEncodingEqual(comp, sys.getdefaultencoding()) def testExplicitEncoding(self): """Test that component gets encoding from magic comment. """ enc = random_encoding() comp = self.getComponent(head = "# encoding: %s\n" % enc) self.failUnlessComponentEncodingEqual(comp, enc) def testEncodingFromBOM(self): """Test that UTF-8 BOM results in utf-8 encoding """ comp = self.getComponent(head = codecs.BOM_UTF8) self.failUnlessComponentEncodingEqual(comp, 'utf_8') def testBadEncodingFromBOM(self): """Test that UTF-8 BOM results in utf-8 encoding """ expected = (SyntaxError, # For bad module source exception.Syntax, # For bad file component source exception.ConfigurationError # XXX: Bad module root? ) self.failUnlessRaises(expected, self.getComponent, head = codecs.BOM_UTF8 + "# encoding: ascii\n" ) def testEncodingFromFlagsEncoding(self): """Test that ``<%flags> encoding`` still works. """ warnings.filterwarnings("ignore", category=DeprecationWarning, module=r'myghty\.compiler') enc = random_encoding() comp = self.getComponent("<%%flags> encoding = %s \n" % repr(enc)) self.failUnlessComponentEncodingEqual(comp, enc) def x_testFlagsEncodingWarning(self): """Test that ``<%flags> encoding`` results in a warning. """ resetwarnings() warnings.filterwarnings("error", category=DeprecationWarning, module=r'myghty\.compiler') self.failUnlessRaises(DeprecationWarning, self.getComponent, "<%flags> encoding='ascii' \n") def testMagicCommentAndFlagsEncodingIsError(self): """Specifying a magic comment and an encoding flag is an error. Test that specifying a magic comment and a ``<%flags> encoding`` is an error. """ warnings.filterwarnings("ignore", category=DeprecationWarning, module=r'myghty\.compiler') self.failUnlessRaises(exception.Syntax, self.getComponent, head="# encoding: latin1\n" "<%flags> encoding='ascii' \n") def testMagicCommentInCompiledComponent(self): """Test that the magic comment get generated in compiled code. """ encoding = random_encoding() compiled = self.getCompiled("# -*- encoding: %s -*-\n" % encoding) self.failUnlessEqual(parse_magic_comment(compiled), encoding) def testNoMagicCommentInCompiledComponent(self): """Test that the magic comment doesn't get generated if there's no magic comment in the source file. """ compiled = self.getCompiled() self.failUnlessEqual(parse_magic_comment(compiled), None) class MemoryComponentEncodingTests(FileComponentEncodingTests): def getComponent(self, head=""): return self.makeMemoryComponent(head + "\nbody\n") def _getCompiledFromComp(self, comp): buf = StringIO.StringIO() comp.component_source.get_object_code(self.interpreter.compiler.get(), buf) return buf.getvalue() if __name__ == '__main__': runner = unittest.TextTestRunner(verbosity=2, descriptions=False) unittest.main(testRunner=runner) myghty-1.1/test/test_component_recoding.py0000644000175000017500000004314610501064113020135 0ustar malexmalex# encoding: ascii # component_recoding.py """Test handling of components of mixed character encodings. Also test unicode output from components. """ import re, unittest import testbase from myghty import escapes class ComponentRecodingTests(testbase.ComponentTester): config = dict(output_encoding="utf_8") srcFiles = { 'simple.myt': '''# -*- coding: iso-8859-15 -*- <%flags>trim = "both" Howdy\xa0there ''' } def testSimple(self): self.runComponent('/simple.myt') self.failUnlessEqual(self.output, 'Howdy\xc2\xa0there') def testFilter(self): """Test that output gets it output all in one whack """ self.runComponent('''# -*- coding: latin1 -*- <%flags> trim = "both" <%filter> return "f(%s)" % f Pre<& /simple.myt &>Post ''') self.failUnlessEqual(self.output, 'f(PreHowdy\xc2\xa0therePost)') def testAutoFlush(self): self.runComponent(''' <%flags> autoflush = True Pre % m.write(m.request_impl.out_buffer.getvalue()) Post ''') self.failUnlessEqual(self.tidyoutput, 'Pre Pre Post') def testEncodingError(self): self.failUnlessRaisesWrappedError( UnicodeEncodeError, self.runComponent, '/simple.myt', config={'output_encoding': 'ascii'}) self.failUnlessEqual(self.output, '') self.runComponent('/simple.myt', config={'output_encoding': 'ascii', 'encoding_errors': 'xmlcharrefreplace'}) self.failUnlessEqual(self.output, 'Howdy there') def testDecodingError(self): comp = '''# encoding: ascii %m.write("\\xa0") ''' self.failUnlessRaisesWrappedError(UnicodeDecodeError, self.runComponent, comp) self.failUnlessEqual(self.output, '') def testValidCharset(self): self.failUnlessRaisesWrappedError( UnicodeDecodeError, self.runComponent, '''# encoding: ascii % m.write("\\xa0") ''', config=dict(output_encoding="ascii")) def testSetOutputEncoding(self): self.runComponent("""% m.write(u'\\xa0') % m.set_output_encoding('utf8') % m.write(u'\\xa0') % m.set_output_encoding('ascii', 'replace') % m.write(u'\\xa0') """, config=dict(output_encoding='latin1', auto_flush=True)) self.failUnlessEqual(self.output, "\xa0\xc2\xa0?") ################################################################ # # Test various sorts of encoding/decoding errors # ################################################################ class EncodingErrorsTests(testbase.ComponentTester): config = dict(output_encoding="latin1") srcFiles = { 'htdocs/badencoding.myt': '''# encoding: ascii % m.write("\\xa0") ''' } def testDecodeError(self): self.failUnlessRaisesWrappedError( UnicodeDecodeError, self.runComponent, "/badencoding.myt") self.failUnlessEqual(self.output, '') def testBadSyntax(self): # The compiled component contains non-ascii characters, # thought the encoding is declared to be ASCII. # The Python compiler generates a SyntaxError. self.failUnlessRaises( SyntaxError, self.loadComponent, '''# encoding: ascii \xa0 ''') srcFiles['badchar.myt'] = ( '# encoding: ascii\n' '% m.write(u"howdy\u20ac")\n') # EURO SIGN - not encodable to latin1 def testEncodeError(self): self.failUnlessRaisesWrappedError( UnicodeEncodeError, self.runComponent, "/badchar.myt") self.failUnlessEqual(self.output, '') def testEncodeErrorIgnore(self): self.runComponent("/badchar.myt", config={'encoding_errors': 'ignore'}) self.failUnlessEqual(self.tidyoutput, 'howdy') def testEncodeErrorReplace(self): self.runComponent("/badchar.myt", config={'encoding_errors': 'replace'}) self.failUnlessEqual(self.tidyoutput, 'howdy?') def testEncodeErrorXmlCharrefReplace(self): self.runComponent("/badchar.myt", config={'encoding_errors': 'xmlcharrefreplace'}) self.failUnlessEqual(self.tidyoutput, 'howdy&#%d;' % 0x20ac) def testEncodeErrorHtmlEntityReplace(self): self.runComponent("/badchar.myt", config={'encoding_errors': 'htmlentityreplace'}) self.failUnlessEqual(self.tidyoutput, 'howdy€') ################################################################ # # Tests that filters get either ASCII strs or unicodes. # and that they can return any type acceptable to m.write() # ################################################################ class FilterTests(testbase.ComponentTester): config = dict(output_encoding="latin1") filter_myt = '''# encoding: latin1 <%args> greeting = "howdy" <%flags> trim = "both" <%filter> return "f(%s)" % repr(f) <% greeting %> ''' def testFilterGetsUnicodeEvenIfInputIsAscii(self): self.runComponent(self.filter_myt) self.failUnlessEqual(self.output, "f(u'howdy')") def testFilterGetsUnicode(self): # write a non-ascii string, filter should get a unicode self.runComponent(self.filter_myt, greeting=u'foo\u20ac') self.failUnlessEqual(self.output, "f(u'foo\u20ac')") class UnicodeOutputFilterTests(FilterTests): filter_myt='''# encoding: latin1 <%args> greeting = "howdy" <%flags> trim = "both" <%filter> return u"f(%s)" % repr(f) <% greeting %> ''' class ObjectOutputFilterTests(FilterTests): filter_myt='''# encoding: latin1 <%args> greeting = "howdy" <%flags> trim = "both" <%filter> class Retval: def __init__(self, val): self.val = val def __unicode__(self): return unicode(self.val) return Retval("f(%s)" % repr(f)) <% greeting %> ''' class ObjectOutputFilterTests2(FilterTests): filter_myt='''# encoding: latin1 <%args> greeting = "howdy" <%flags> trim = "both" <%filter> class Retval: def __init__(self, val): self.val = val def __str__(self): return str(self.val) return Retval("f(%s)" % repr(f)) <% greeting %> ''' ################################################################ # # Test capture buffers get the right values (either strs # or unicodes.) # ################################################################ class TestBuffer(object): def __init__(self): self.buf = [] def write(self, text): self.buf.append(text) class CaptureBufferTests(testbase.ComponentTester): config = dict(output_encoding='latin1') srcFiles = { 'ascii.myt': 'hello', 'unicode.myt': r'%m.write(u"\u20ac")', 'latin1.myt': '''# encoding: latin1 hi\xa0there ''', } def testCapture(self, bufencoding=None): buf = TestBuffer() if bufencoding: buf.encoding = bufencoding def mycomp(m): m.execute_component("/ascii.myt", store=buf) m.execute_component("/latin1.myt", store=buf) m.execute_component("/unicode.myt", store=buf) comp = self.makeModuleComponent(mycomp) self.runComponent(comp) self.failUnlessEqual(self.output, "") expected = [u'hello', u'hi\u00a0there', u'\u20ac'] self.failUnlessEqual(buf.buf, expected) self.failUnlessEqual(map(type, buf.buf), map(type, expected)) def testCaptureIgnoresAsciiBufferEncodingAttribute(self): self.testCapture(bufencoding='ascii') def testScomp(self, comp="/ascii.myt", expect=u'hello'): result = self.runComponent('''# encoding: utf8 <%args> comp % return m.scomp(comp) ''', comp=comp) self.failUnlessEqual(self.output, "") self.failUnlessEqual(result, expect) self.failUnlessEqual(type(result), type(expect)) def testScompLatin1(self): self.testScomp(comp='/latin1.myt', expect=u'hi\xa0there') def testScompUnicode(self): self.testScomp(comp='/unicode.myt', expect=u'\u20ac') ################################################################ # # Test closures # ################################################################ class ClosureTests(testbase.ComponentTester): config = dict(output_encoding='utf8') def testClosure(self): self.runComponent('''# encoding: latin1 <%closure show_foo> <% foo %> % foo = "foo1" <& show_foo &> % foo = "\xa0foo2".decode("latin1") <& show_foo &> ''') self.failUnlessEqual(self.tidyoutput, "foo1 \xc2\xa0foo2") def testClosureWithContent(self): self.runComponent('''# encoding: ascii <%closure double> % for i in 1, 2: <% m.content(i=i) %> % # end <&| double &> cow<% u"\u20ac" %><% m.content_args["i"] %> ''') self.failUnlessEqual(self.tidyoutput, "cow\xe2\x82\xac1 cow\xe2\x82\xac2") ################################################################ # # Output escaping. # ################################################################ class OutputEscapingTest(testbase.ComponentTester): config = dict(escapes={'H': escapes.html_entities_escape}) def do_test(self, escape, expected): self.runComponent(''' <%% u"\\u20ac" | %(escape)s %%> <%% "\'" | %(escape)s %%> ''' % dict(escape=escape), config={'encoding_errors': 'backslashreplace'}) self.failUnlessEqual(self.tidyoutput, expected) def test_h(self): self.do_test('h', r"\u20ac '") def test_u(self): self.do_test('u', r"%E2%82%AC %27") def test_x(self): #self.do_test('x', r"\u20ac '") self.do_test('x', r"\u20ac '") def test_H(self): self.do_test('H', r"€ '") ################################################################ # # Test aborting # ################################################################ class AbortTest(testbase.ComponentTester): def testAbort(self): self.runComponent(r''' <%flags> autoflush = True <%def subcomp> <%flags> autoflush = False output % m.flush_buffer() a mistake % m.abort() head <& subcomp &> tail ''') self.failUnlessEqual(self.tidyoutput, 'head output') def testFail(self): class MyAbort(Exception): pass from myghty.request import DefaultRequestImpl class MyRequestImpl(DefaultRequestImpl): def send_abort(self, code, reason): assert code == 404 assert reason == "not found" raise MyAbort(code, reason) request_impl=MyRequestImpl(out_buffer=self.outputBuffer) self.failUnlessRaises( MyAbort, self.runComponent, ''' head % m.flush_buffer() body % m.abort(404, "not found") tail ''', config=dict(request_impl=request_impl)) self.failUnlessEqual(self.tidyoutput, "head") ################################################################ # # Test m.send_redirect() # ################################################################ class RedirectTest(testbase.ComponentTester): srcFiles = { 'comp2.myt': """Comp2 here.""" } def testSoftRedirect(self): self.runComponent(r''' <%flags> autoflush = False Leading junk (should be discarded) % m.send_redirect("/comp2.myt", hard=False) Trailing junk (should be disregarded) ''') self.failUnlessEqual(self.tidyoutput, 'Comp2 here.') def testHardRedirect(self): class MyRedirect(Exception): pass from myghty.request import DefaultRequestImpl class MyRequestImpl(DefaultRequestImpl): def send_redirect(self, path): assert path == '/other.html' raise MyRedirect(path) request_impl=MyRequestImpl(out_buffer=self.outputBuffer) self.failUnlessRaises( MyRedirect, self.runComponent, ''' head % m.send_redirect("/other.html") tail ''', config=dict(request_impl=request_impl)) self.failUnlessEqual(self.tidyoutput, "") ################################################################ # # Test that cache_self output is stored in encoding neutral manner # ################################################################ class CacheSelfTests(testbase.ComponentTester): config = dict(output_encoding='ascii') srcFiles = {'htdocs/cache_money.myt': '''# encoding: latin1 <%flags> use_cache = True # A random number of EUROs, with a decoding error % import random <% u"\\xa4" %><% str(random.randint(1,1000)) %> ''' } setup_done = False def class_set_up(self): if not self.setup_done: testbase.ComponentTester.class_set_up(self) self.setUp() self.runComponent('<& cache_money.myt &>', config=dict(encoding_errors='replace')) self.__class__.euros = int(re.search(r'\d+', self.output).group()) self.tearDown() self.__class__.setup_done = True def testLenient(self): self.runComponent('<& cache_money.myt &>', config=dict(encoding_errors='replace')) self.failUnlessEqual(self.tidyoutput, '?%d' % self.euros) def testStrict(self): self.failUnlessRaisesWrappedError( UnicodeEncodeError, self.runComponent, '<& cache_money.myt &>', config=dict(encoding_errors='strict')) ################################################################ # # Test that trimming filter works with either unicode or str input # ################################################################ class TrimTests(testbase.ComponentTester): config = dict(output_encoding='utf8') def testStrStrip(self): self.runComponent( '''# encoding: ascii <%flags> trim = "both" foo ''') self.failUnlessEqual(self.output, 'foo') self.failUnlessEqual(type(self.output), str) def testUnicodeStrip(self): self.runComponent( '''# encoding: latin1 <%flags> trim = "both" \xa0 bar % m.write(u"\\u20ac") ''') self.failUnlessEqual(self.output, 'bar\n\xe2\x82\xac') ################################################################ # # Test captured subrequest can have their own output_encoding # ################################################################ class SubrequestTest(testbase.ComponentTester): config = dict(output_encoding='utf-8') srcFiles = {'htdocs/subreqbody.myt': r'% m.write(u"\u20ac")'} def testCanSetOutputEncoding(self): import StringIO buf = StringIO.StringIO() self.runComponent( '''# encoding: ascii <%args> buf <%init> subreq = m.create_subrequest("subreqbody.myt", out_buffer=buf, output_encoding="iso-8859-15") subreq.execute() ''', buf = buf) self.failUnlessEqual(buf.getvalue(), "\xa4") self.failUnlessEqual(self.output, '') def testCurrentEncoding(self): # If not output_encoding is specified for capture buffer, it should # default to the current value of m.encoding (not m.output_encoding). self.failUnlessRaisesWrappedError( UnicodeEncodeError, self.runComponent, '''# encoding: ascii <%init> import StringIO subreq = m.create_subrequest("subreqbody.myt", out_buffer=StringIO.StringIO()) subreq.execute() ''') if __name__ == '__main__': unittest.main() myghty-1.1/test/test_component_reloading.py0000644000175000017500000002147710501064113020312 0ustar malexmalex# test/reload.py """Test that components reload properly when their source is touched. """ import unittest, os, shutil, stat, sys, time, warnings from StringIO import StringIO import testbase from myghty.interp import Interpreter from myghty.http.WSGIHandler import WSGIHandler from myghty.resolver import NotFound import myghty.importer class ModuleRunner: """A minimal context for executing components. """ def __init__(self, **params): self.interpreter = Interpreter(**params) def executeComponent(self, comp): buf = StringIO() self.interpreter.execute(comp, out_buffer=buf) return buf.getvalue() class WSGIModuleRunner: """An WSGI context for executing components. We need this to test Routes components, since they require a request (r) object. """ def __init__(self, **params): self.app = WSGIHandler(**params).handle def executeComponent(self, comp): environ = { 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'PATH_INFO': comp, 'SERVER_NAME': 'localhost', 'SERVER_PORT': 8000, 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_HOST': 'localhost:8000', 'wsgi.version': (1,0), 'wsgi.url_scheme': 'http', 'wsgi.input': StringIO(), 'wsgi.errors': sys.stderr, 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': False, } buf = StringIO() def start_response(status, response_headers): return buf.write for s in self.app(environ, start_response): buf.write(s) return buf.getvalue() def purge_module(module_name): try: module = sys.modules[module_name] except KeyError: return base, ext = os.path.splitext(module.__file__) for ext in '.pyc', '.pyo': try: os.unlink(base + ext) except OSError: pass del sys.modules[module_name] class ComponentReloadTests: """Abstract base class of tests for component reloading.""" module_runner_class = ModuleRunner def setUp(self): # Blow away cache shutil.rmtree(os.path.join(self.cache, 'obj'), ignore_errors=True) # Clear importer cache myghty.importer.modules.clear() self.createComponentSource() params = self.resolverArgs() self.module_runner = self.module_runner_class(data_dir=self.cache, **params) def tearDown(self): self.removeComponentSource() def srcFile(self): """The full path the the module source file. """ raise RuntimeError, "pure virtual" def srcText(self, text="howdy"): """Get the contents of the module source. The module, when run, will output the value of text. return text """ raise RuntimeError, "pure virtual" def resolverArgs(self): """Get the resolver arguments to pass to the Myghty interperter. """ raise RuntimeError, "pure virtual" def componentPath(self): """Get the (URL) path to the component. """ raise RuntimeError, "pure virtual" def createComponentSource(self, text="howdy"): file(self.srcFile(), "w").write(self.srcText(text)) def changeComponentSource(self, text="changed"): """Covertly changes the component source without changing mtime. """ srcfile = self.srcFile() mtime = os.stat(srcfile)[stat.ST_MTIME] self.createComponentSource(text) os.utime(srcfile, (mtime, mtime)) def removeComponentSource(self): os.unlink(self.srcFile()) base, ext = os.path.splitext(self.srcFile()) if ext == '.py': for ext in '.pyc', '.pyo': try: os.unlink(base + ext) except OSError: pass def executeComponent(self): return self.module_runner.executeComponent(self.componentPath()) def testComponent(self, text="howdy"): """Test that we can resolve and execute our component. """ self.failUnlessEqual(self.executeComponent(), text) def testReload(self): """Test that component gets reloaded when it is modified. """ self.testComponent() time.sleep(1) self.removeComponentSource() self.createComponentSource(text="changed") self.testComponent("changed") def testCompileCache(self): """Test that component doesn't get reloaded when it is not modified. (Actually, tests that component doesn't get reloaded when the source is modified, but the source's mtime remains unchanged.) """ self.testComponent() time.sleep(1) self.changeComponentSource(text="changed") self.testComponent() ################################################################ # # (The actual unittest.TestCase sub-classes begin here.) # ################################################################ class FileComponentReloadTests(ComponentReloadTests, testbase.MyghtyTest): def srcFile(self): return os.path.join(self.htdocs, "comp.myt") def srcText(self, text="howdy"): return text def resolverArgs(self): return {'component_root': self.htdocs} def componentPath(self): return "/comp.myt" class ModuleComponentReloadTests(ComponentReloadTests, testbase.MyghtyTest): def setUp(self): purge_module('module') ComponentReloadTests.setUp(self) def srcFile(self): return os.path.join(self.lib, "module.py") def srcText(self, text="howdy"): return "def comp(m): m.write('%s')" % text def resolverArgs(self): try: del sys.modules['module'] except KeyError: pass return {'module_components': [{'/mycomp': 'module:comp'}]} def componentPath(self): return "/mycomp" class ModuleRootComponentReloadTests(ComponentReloadTests, testbase.MyghtyTest): def setUp(self): purge_module('modroot') ComponentReloadTests.setUp(self) def srcFile(self): return os.path.join(self.lib, "modroot.py") def srcText(self, text="howdy"): return """ class Top: def comp(self, m): m.write('%s') top = Top() """ % text def resolverArgs(self): try: del sys.modules['modroot'] pass except KeyError: pass return {'module_root': ['modroot']} def componentPath(self): return "top/comp" class WSGIModuleRootComponentReloadTests(ModuleRootComponentReloadTests): """Run the module_root test in a WSGI context. This exercised a buglet in myghty.http.HTTPHandler.py. """ module_runner_class = WSGIModuleRunner os.environ['MYGHTY_ENV'] = 'debug' # Otherwise RoutesResolver pukes try: from routes.base import Mapper except ImportError: # Routes appears not to be installed class RoutesComponentReloadTests(unittest.TestCase): def testRoutesMissing(self): warnings.warn("Cannot import routes.base. Skipping Routes tests.") else: # Routes seems to be installed from myghty.ext.routeresolver import RoutesResolver class RoutesComponentReloadTests(ComponentReloadTests, testbase.MyghtyTest): module_runner_class = WSGIModuleRunner def srcFile(self): return os.path.join(self.lib, "routes_controller.py") def srcText(self, text="howdy"): return """ class RoutesController: def index(self, m, r): m.write('%s') routes = RoutesController() """ % text def resolverArgs(self): mapper = Mapper() mapper.connect(':controller/:action') routes_resolver = RoutesResolver( mapper=mapper, controller_root = self.lib) return {'resolver_strategy': [routes_resolver, NotFound()]} def componentPath(self): return "/routes" class AnonymousFunctionComponentReloadTests(testbase.ComponentTester): def test(self): def mycomp(m): return 1 comp1 = self.makeModuleComponent(mycomp) def mycomp(m): return 2 comp2 = self.makeModuleComponent(mycomp) print comp1.component_source.id self.failIfEqual(comp1, comp2, "(Currently broken, expect failure.)") self.failUnlessEqual(self.runComponent(comp1), 1) self.failUnlessEqual(self.runComponent(comp2), 2) if __name__ == '__main__': runner = unittest.TextTestRunner(verbosity=2, descriptions=False) unittest.main(testRunner=runner) myghty-1.1/test/test_escapes.py0000644000175000017500000000664410501064113015706 0ustar malexmaleximport codecs, re, sys, unittest, warnings from myghty import escapes LATIN1_CHARS = ''.join(map(chr, range(256))).decode('latin1') class EscapeTests(object): def escape(self, text): raise NotImplementedError def unescape(self, text): raise NotImplementedError TEST_CHARS = LATIN1_CHARS def testEscapedChars(self): escaped = ''.join([char for char in self.TEST_CHARS if self.escape(char) != char]) should_escape = ''.join(re.findall(self.should_escape_re, self.TEST_CHARS)) if escaped != should_escape: missing = ''.join([char for char in should_escape if char not in escaped]) shouldnt = ''.join([char for char in escaped if char not in should_escape]) self.failUnless(missing or shouldnt) if missing: self.fail("Should have escaped: %s" % repr(missing)) if shouldnt: self.fail("Should not have escaped: %s" % repr(shouldnt)) def testTakesUnicode(self): frappe = self.escape(u'frapp\u00C9') self.failUnless(isinstance(frappe, basestring)) self.failUnlessEqual(frappe[:5], "frapp") def testTakesStr(self): hello = self.escape('hello') self.failUnless(isinstance(hello, basestring)) self.failUnlessEqual(hello, "hello") def testStrMustBeAscii(self): if codecs.lookup(sys.getdefaultencoding()) != codecs.lookup('ascii'): warnings.warn("Skipping testStrMustBeAscii") return self.failUnlessRaises(UnicodeError, self.escape, '\xa0') def testUnescape(self): escaped = self.escape(self.TEST_CHARS) try: unescaped = self.unescape(escaped) except NotImplementedError: return unicode(unescaped) self.failUnlessEqual(unescaped, self.TEST_CHARS) class html_escape_tests(EscapeTests, unittest.TestCase): escape = staticmethod(escapes.html_escape) should_escape_re = r'["&<>]' def testStrMustBeAscii(self): pass # Is this right? class xml_escape_tests(EscapeTests, unittest.TestCase): escape = staticmethod(escapes.xml_escape) should_escape_re = '["&\'<>]' def testStrMustBeAscii(self): pass # Is this right? class url_escape_tests(EscapeTests, unittest.TestCase): escape = staticmethod(escapes.url_escape) unescape = staticmethod(escapes.url_unescape) should_escape_re = r'[^-.\w]' def testPlus(self): self.failUnlessEqual(self.unescape('+'), ' ') self.failUnlessEqual(self.unescape('%2b'), '+') self.failUnlessEqual(self.escape(' '), '+') self.failUnlessEqual(self.escape('+'), '%2B') class html_entities_escape_tests(EscapeTests, unittest.TestCase): escape = staticmethod(escapes.html_entities_escape) unescape = staticmethod(escapes.html_entities_unescape) should_escape_re = '["&<>\xa0-\xff]' class htmlentityreplace_errors_tests(unittest.TestCase): def testEuro(self): self.failUnlessEqual( u'The cost was \u20ac12.'.encode('latin1', 'htmlentityreplace'), 'The cost was €12.' ) def testNumeric(self): self.failUnlessEqual(u'\x81'.encode('ascii', 'htmlentityreplace'), '') if __name__ == '__main__': unittest.main() myghty-1.1/test/test_filter.py0000644000175000017500000000271510501064113015543 0ustar malexmalexfrom myghty import interp import re, sys, StringIO, unittest def runComponent(componentSrc, **config): interpreter = interp.Interpreter(**config) comp = interpreter.make_component(componentSrc) buf = StringIO.StringIO() interpreter.execute(comp, out_buffer=buf) return buf.getvalue() # A component with "stacked filters" # Any of the output from subcomponent "a", should be passed # through a\'s filter, and then the main components filter. stackedFilterSrc=''' <&a&> <%filter> return "filter(%s)" % f <%def a> hello <%filter> return "a(%s)" % f ''' # The resulting output should contain a string # something like "filter(a(" which indicates that the # main filter has been applied to the output of the subcomponents # filter. stackedFilterCheck="filter(a(" class FilterTests(unittest.TestCase): """Test that two stacked filters get properly called. """ auto_flush = False def testStackedFilters(self): output = runComponent(stackedFilterSrc, auto_flush=self.auto_flush) output = re.sub(r'\s', '', output) self.failUnless(stackedFilterCheck in output, "output %s does not contain %s" % (repr(output), repr(stackedFilterCheck))) class AutoFlushFilterTests(FilterTests): auto_flush = True if __name__ == '__main__': unittest.main() myghty-1.1/test/test_html_errors.py0000644000175000017500000001147210501064113016616 0ustar malexmalex# encoding: latin1 # test_html_errors.py """Test HTML formatting of errors. """ import re, unittest import testbase from myghty.exception import Error, Syntax class HTMLDecodingErrorBase(object): expected_error = Error wrapped_error = None expected_html = "euro:€" def _testComponent(self, component_maker): try: self.runComponent(component_maker(self.csource)) except Error, ex: if self.expected_error: self.failUnless(isinstance(ex, self.expected_error)) if self.wrapped_error: self.failUnless(isinstance(ex.wrapped, self.wrapped_error)) else: self.fail() if self.expected_html not in ex.htmlformat(): print ex.htmlformat() self.failUnless(self.expected_html in ex.htmlformat()) def testFileComponent(self): self._testComponent(self.makeFileBasedComponent) def testMemoryComponent(self): self._testComponent(self.makeMemoryComponent) class PythonErrorInComponentTests(HTMLDecodingErrorBase, testbase.ComponentTester): csource = """# encoding: iso-8859-15 % m.setTYPO_output_encoding('UTF-8') euro:\xa4
encoding is: <% m.output_encoding %> """ wrapped_error = AttributeError class PythonErrorInComponentCodeline(HTMLDecodingErrorBase, testbase.ComponentTester): csource = """# encoding: iso-8859-15 % u'euro:\xa4' + 1 """ wrapped_error = TypeError class SyntaxErrorInComponentTests(HTMLDecodingErrorBase, testbase.ComponentTester): csource = """# encoding: iso-8859-15 <%python scope='bogus'> euro:\xa4 """ expected_error = Syntax class ErrorInModuleComponent(HTMLDecodingErrorBase, testbase.ComponentTester): csource = """# encoding: iso-8859-15 def f(m): return u'euro:\xa4' + 1 """ def testFileComponent(self): def comp_maker(csource): module = self.makeModule(csource) return module.f self._testComponent(comp_maker) def testMemoryComponent(self): pass class AnonymousPythonErrorInComponentTests(HTMLDecodingErrorBase, testbase.ComponentTester): csource = """# encoding: iso-8859-15 <%python> g = dict() eval(compile('# encoding: iso-8859-15\\n' 'def f(): return u\"euro:\xa4\" + 1\\n', '', 'exec', 0, True), g, g) f = g['f'] % f() """ wrapped_error = TypeError expected_html = "2: None" ################################################################ import codecs, StringIO from myghty.exception import _parse_encoding as parse_encoding class parse_encoding_Tests(testbase.MyghtyTest): def testParseEncoding(self, src="", coding=None): f = StringIO.StringIO(src) try: self.failUnlessEqual(parse_encoding(f), coding) finally: self.failUnlessEqual(f.read(), src) # test that file is rewound def testSimple(self): self.testParseEncoding("# -*- coding: foo -*-\n", 'foo') self.testParseEncoding("# First line \n" "# vim:fileencoding=bar\n", 'bar') self.testParseEncoding("# First line\n" "# Second line\n" "# -*- coding: foo -*-\n", None) def testBom(self): self.testParseEncoding(codecs.BOM_UTF8 + "# Howdy\n", 'utf_8') def testBadBom(self): self.failUnlessRaises(SyntaxError, self.testParseEncoding, codecs.BOM_UTF8 + "# coding: ascii\n", 'utf_8') self.failUnlessRaises(SyntaxError, self.testParseEncoding, codecs.BOM_UTF8 + "'line1'\n# coding: ascii\n", 'utf_8') def testEsoteric(self): self.testParseEncoding('print """\n' '# coding: foo\n' '"""\n', None) self.testParseEncoding('(\n' '# coding: foo\n' ')\n', None) if __name__ == '__main__': unittest.main() myghty-1.1/test/test_logger.py0000644000175000017500000000375110501064113015536 0ustar malexmaleximport sys, StringIO, unittest from myghty import request, interp import testbase class LoggingRequestImpl(request.DefaultRequestImpl): def __init__(self, logger=sys.stderr, **params): request.DefaultRequestImpl.__init__(self, **params) self.logger = logger class TestRequestLogging(unittest.TestCase): def setUp(self): self.logger = StringIO.StringIO() self.m = interp.Interpreter().make_request( None, request_impl=LoggingRequestImpl(logger=self.logger)) logOutput = property(lambda self: self.logger.getvalue()) def testLog(self): self.m.log('howdy') self.failUnlessEqual(self.logOutput, "howdy\n") def testLogger(self): self.m.logger.write('foo') self.failUnlessEqual(self.logOutput, "foo\n") def testLoggerWritelines(self): self.m.logger.writelines(['foo', 'bar']) self.failUnlessEqual(self.logOutput, "foo\nbar\n") class TestWarningsGetLogged(testbase.ComponentTester): def test(self): log = StringIO.StringIO() self.runComponent('''% import warnings % warnings.warn("Boo!") ''', config={'request_impl': LoggingRequestImpl(logger=log)}) output = log.getvalue() self.failUnlessEqual(output[output.rindex('.myt'):], '.myt:2: UserWarning: Boo!\n' ' % warnings.warn("Boo!")\n') class TestWarningsGetLoggedMemory(TestWarningsGetLogged): """Same as above, except with no compile cache. """ # In this case, the keys in interpreter.reverse_lookup # start with 'memory:'. config = dict(data_dir=None) def testSanity(self): self.runComponent(' ') self.failUnless(filter(lambda s: s.startswith('memory:'), self.interpreter.reverse_lookup.keys())) if __name__ == '__main__': unittest.main() myghty-1.1/test/test_lru_cache.py0000644000175000017500000000162210501064113016177 0ustar malexmalexfrom myghty.util import LRUCache import string, unittest import testbase class item: def __init__(self, id): self.id = id def __str__(self): return "item id %d" % self.id class LRUTest(testbase.MyghtyTest): def setUp(self): self.cache = LRUCache(10) def print_cache(l): for item in l: print item, print def testlru(self): l = self.cache for id in range(1,13): l[id] = item(id) self.assert_(not l.has_key(1)) self.assert_(not l.has_key(2)) for id in range(3,12): self.assert_(l.has_key(id)) l[4] l[5] l[13] = item(13) self.assert_(not l.has_key(3)) for id in (4,5,6,7,8,9,10,11,12, 13): self.assert_(l.has_key(id)) if __name__ == "__main__": unittest.main() myghty-1.1/test/test_requestbuffer.py0000644000175000017500000002470610501064113017144 0ustar malexmalex# test/test_encoding.py """Test that stuff in myghty.requestbuffer """ import unittest, os, sys, codecs, StringIO, cStringIO, cPickle, warnings import testbase from myghty import importer, util from myghty.requestbuffer import \ UnicodeRequestBuffer, StrRequestBuffer, MismatchedPop def _is_ascii_superset(encoding): ASCII_CHARS = ''.join(map(chr, range(128))) try: return ASCII_CHARS.decode(encoding) == ASCII_CHARS.decode('ascii') except UnicodeDecodeError: return False if not _is_ascii_superset(sys.getdefaultencoding()): warnings.warn("System default encoding is not a subset of ASCII. " "Many of these tests will fail.", UserWarning) REPLACEMENT_CHARACTER = u'\ufffd' class TestBuffer: """A StringIO which supports only a limited set of file operations. """ def __init__(self): self.buf = StringIO.StringIO() def write(self, text): self.buf.write(text) def __str__(self): return self.buf.getvalue() def test_filter(text): return "f(%s)" % text class _RequestBufferTests(object): def setUp(self): self.outputBuffer = TestBuffer() output = property(lambda self: str(self.outputBuffer)) def testBuffering(self): buf = self.getRequestBuffer() buf.write("a") self.failUnlessEqual(self.output, "a") buf.push_buffer() buf.write("b") self.failUnlessEqual(self.output, "a") buf.push_buffer() buf.write("c") self.failUnlessEqual(self.output, "a") buf.pop_buffer() self.failUnlessEqual(self.output, "a") buf.pop_buffer() self.failUnlessEqual(self.output, "abc") def testBufferNoFlush(self): buf = self.getRequestBuffer() buf.write("a") self.failUnlessEqual(self.output, "a") buf.push_buffer() buf.write("b") buf.pop_buffer(discard=True) self.failUnlessEqual(self.output, "a") def testFilter(self): buf = self.getRequestBuffer() buf.push_filter(test_filter) buf.write("a") buf.write("b") self.failUnlessEqual(self.output, "f(a)f(b)") buf.push_buffer() buf.write("c") buf.write("d") buf.pop_buffer() self.failUnlessEqual(self.output, "f(a)f(b)f(cd)") buf.pop_filter() buf.write("e") self.failUnlessEqual(self.output, "f(a)f(b)f(cd)e") def testCaptureBuffer(self): buf = self.getRequestBuffer() buf.push_buffer() buf.write('a') self.failUnlessEqual(self.output, '') cbuf = util.StringIO() buf.push_capture_buffer(cbuf) buf.write('b') self.failUnlessEqual(cbuf.getvalue(), "b") buf.flush() self.failUnlessEqual(self.output, '') buf.push_capture_buffer() buf.write("c") cbuf2 = buf.pop_capture_buffer() self.failUnlessEqual(cbuf2.getvalue(), "c") self.failUnlessEqual(cbuf, buf.pop_capture_buffer()) self.failUnlessEqual(cbuf.getvalue(), "b") self.failUnlessEqual(self.output, '') buf.pop_buffer() self.failUnlessEqual(self.output, 'a') def testFlush(self): buf = self.getRequestBuffer() buf.push_buffer() buf.write("howdy") self.failUnlessEqual(self.output, "") buf.flush() self.failUnlessEqual(self.output, "howdy") def testMismatchedPop(self): buf = self.getRequestBuffer() self.failUnlessRaises(MismatchedPop, buf.pop_buffer) buf.push_buffer() self.failUnlessRaises(MismatchedPop, buf.pop_filter) buf.pop_buffer() def testSaveAndRestoreState(self): buf = self.getRequestBuffer() buf.push_filter(test_filter) state = buf.get_state() buf.push_buffer() buf.write('b') self.failUnlessEqual(self.output, '') buf.pop_to_state(state) self.failUnlessEqual(self.output, 'f(b)') buf.write('a') self.failUnlessEqual(self.output, 'f(b)f(a)') class StrRequestBufferTests(_RequestBufferTests, unittest.TestCase): def getRequestBuffer(self): return StrRequestBuffer(self.outputBuffer) def testWriteUnicodeFails(self): buf = self.getRequestBuffer() self.failUnlessRaises(UnicodeEncodeError, buf.write, REPLACEMENT_CHARACTER) def testBinarySafe(self): buf = self.getRequestBuffer() all_chars = ''.join(map(chr, range(256))) buf.write(all_chars) self.failUnlessEqual(self.output, all_chars) def testWriteUnicodeOkay(self): buf = self.getRequestBuffer() buf.write(u'abc') self.failUnlessEqual(self.output, 'abc') def testFilterUnicodeOutput(self): def filt(text): if text == 'a': return REPLACEMENT_CHARACTER else: return unicode(text) * 2 buf = self.getRequestBuffer() buf.push_filter(filt) self.failUnlessRaises(UnicodeEncodeError, buf.write, "a") buf.write('b') self.failUnlessEqual(self.output, "bb") def testChangeOutputEncodingFails(self): buf = self.getRequestBuffer() self.failUnless(buf.encoding is None) self.failUnlessRaises(AttributeError, setattr, buf, 'encoding', 'ascii') def testChangeEncodingErrorsFails(self): buf = self.getRequestBuffer() self.failUnless(buf.errors is None) self.failUnlessRaises(AttributeError, setattr, buf, 'errors', 'strict') class UnicodeRequestBufferTests(_RequestBufferTests, unittest.TestCase): def setUp(self): self.outputBuffer = TestBuffer() output = property(lambda self: str(self.outputBuffer)) def getRequestBuffer(self, encoding='ascii', errors='strict'): return UnicodeRequestBuffer(self.outputBuffer, encoding, errors) def testBufferMixedEncoding(self): buf = self.getRequestBuffer('latin1') buf.push_buffer() buf.write("a") buf.write(u'b') buf.write(unicode("\xc2\xa0", 'utf8')) self.failUnlessEqual(self.output, '') buf.pop_buffer() self.failUnlessEqual(self.output, 'ab\xa0') def testBufferMixedEncodingNoFlush(self): buf = self.getRequestBuffer('latin1') buf.push_buffer() buf.write("a") buf.write(u'b') buf.write(unicode("\xc2\xa0", 'utf_8')) self.failUnlessEqual(self.output, '') buf.pop_buffer(discard=True) self.failUnlessEqual(self.output, '') def testFilterUnicodeOutput(self): def filt(text): return u'\u20ac' buf = self.getRequestBuffer('iso-8859-15') buf.push_filter(filt) buf.write("a") self.failUnlessEqual(self.output, "\xa4") def testRecoding(self): buf = self.getRequestBuffer('utf_8') buf.write(unicode('\xa0', 'latin1')) self.failUnlessEqual(self.output, '\xc2\xa0') def testRecodingError(self): buf = self.getRequestBuffer('utf_8') buf.push_capture_buffer(buffer=cStringIO.StringIO()) # Can't recode   to ascii self.failUnlessRaises(UnicodeError, buf.write, unicode('\xa0', 'latin1')) buf.write('a') cbuf = buf.pop_capture_buffer() self.failUnlessEqual(cbuf.getvalue(), 'a') self.failUnlessEqual(self.output, '') def testBufferIsEncodingTransparent(self): buf = self.getRequestBuffer('utf_8') buf.push_buffer() # This buffer does not encode to ascii buf.write(unicode("\xa0", 'latin1')) self.failUnlessEqual(self.output, '') buf.flush() self.failUnlessEqual(self.output, '\xc2\xa0') def testUnicodeWrites(self): buf = self.getRequestBuffer('iso-8859-15') EURO_SIGN = u'\u20ac' buf.write(EURO_SIGN) self.failUnlessEqual(self.output, '\xa4') # There's no euro symbol in latin1 buf = self.getRequestBuffer('iso-8859-1') self.failUnlessRaises(UnicodeEncodeError, buf.write, EURO_SIGN) def testChangeOutputEncoding(self): nbsp = unicode('\xa0', 'latin1') buf = self.getRequestBuffer('latin1') self.failUnlessEqual(buf.encoding, 'latin1') buf.write(nbsp) self.failUnlessEqual(self.output, '\xa0') buf.set_encoding('utf-8') self.failUnlessEqual(buf.encoding, 'utf-8') buf.write(nbsp) self.failUnlessEqual(self.output, '\xa0\xc2\xa0') def testChangeOutputEncodingBufferFlushing(self): buf = self.getRequestBuffer('latin1') buf.push_buffer() buf.write(unicode('\xa0', 'latin1')) self.failUnlessEqual(self.output, '') cbuf = util.StringIO() buf.push_capture_buffer(cbuf) buf.push_buffer() buf.write('foo') buf.set_encoding('utf-8') self.failUnlessEqual(buf.encoding, 'utf-8') # Test that nothing is flushed. self.failUnlessEqual(cbuf.getvalue(), '') self.failUnlessEqual(self.output, '') buf.pop_buffer() buf.pop_capture_buffer() buf.write(unicode(' \xa0', 'latin1')) buf.pop_buffer() self.failUnlessEqual(self.output, '\xc2\xa0 \xc2\xa0') def testReplaceErrors(self): buf = self.getRequestBuffer(errors='replace') buf.push_filter(test_filter) state = buf.get_state() buf.push_buffer() buf.write(REPLACEMENT_CHARACTER) self.failUnlessEqual(self.output, '') buf.pop_to_state(state) self.failUnlessEqual(self.output, 'f(?)') buf.write(u'a') self.failUnlessEqual(self.output, 'f(?)f(a)') def testFlushWithErrors(self): buf = self.getRequestBuffer() buf.push_buffer() buf.write(unicode('\xa4', 'latin1')) self.failUnlessRaises(UnicodeEncodeError, buf.pop_buffer) self.failUnlessEqual(self.output, '') ################################################################ class TestIsAsciiSuperset(unittest.TestCase): def test(self): self.failUnless(_is_ascii_superset('latin1')) self.failUnless(_is_ascii_superset('ascii')) self.failUnless(_is_ascii_superset('utf8')) self.failIf(_is_ascii_superset('utf7')) if __name__ == '__main__': runner = unittest.TextTestRunner(verbosity=2, descriptions=False) unittest.main(testRunner=runner) myghty-1.1/test/test_resolver.py0000644000175000017500000003207510501064113016121 0ustar malexmaleximport testbase import myghty.resolver as resolver import myghty.exception as exception import unittest, sys, string class FileResolveTest(testbase.MyghtyTest): def class_set_up(self): self.resolver = resolver.Resolver( component_root = [{'htdocs': self.htdocs}, {'comp': self.components}], use_static_source = True ) self.create_file(self.htdocs, 'hello.myt', "hello component") self.create_file(self.components, 'searchup.myt', "searchup component") self.create_directory(self.htdocs, './foo/lala') self.create_file(self.htdocs, './foo/searchup2.myt', " foo") self.create_file(self.htdocs, './foo/lala/hi.myt', "im hi.myt") self.create_file(self.components, 'dhandler', "im a dhandler") def class_tear_down(self): self.remove_file(self.components, "dhandler") self.remove_file(self.components, "searchup.myt") self.remove_file(self.htdocs, './foo/searchup2.myt') self.remove_file(self.htdocs, './foo/lala/hi.myt') self.remove_file(self.htdocs, 'hello.myt') def testcomponent(self): "locates a component in the root directory" resolution = self.resolver.resolve('/hello.myt', search_upwards = False, raise_error = True) csource = resolution.csource dumpresolution(resolution) self.assert_(csource is not None and csource.path == '/hello.myt' and csource.dir_name == '/', "hello.myt not located") def testdircomponent(self): "locates a component in a subdirectory" resolution = self.resolver.resolve('/foo/lala/hi.myt', search_upwards = False, raise_error = True) csource = resolution.csource dumpresolution(resolution) self.assert_(csource is not None and csource.path == '/foo/lala/hi.myt' and csource.dir_name == '/foo/lala' and csource.name=='hi.myt', "hi.myt not properly located") def testcomponentnoslash(self): "locates a component with no directory information in the URI" resolution = self.resolver.resolve('hello.myt', search_upwards = False, raise_error = True) csource = resolution.csource dumpresolution(resolution) self.assert_(csource is not None, "hello.myt not located") self.assert_(csource.path == '/hello.myt' and csource.dir_name == '/', "hello.myt has wrong path info, path %s dir_name %s" % (csource.path, csource.dir_name)) def testcache(self): "locates a component twice with caching on, checks that the second resolution succeeded" resolution = self.resolver.resolve('/hello.myt', search_upwards = False, raise_error = True) csource = resolution.csource dumpresolution(resolution) resolution = self.resolver.resolve('/hello.myt', search_upwards = False, raise_error = True) csource = resolution.csource dumpresolution(resolution) self.assert_(csource is not None and csource.path == '/hello.myt' and csource.dir_name == '/', "hello.myt not located") def testupwardsnonexistentdir(self): "locates a component searching upwards, with the initial directory nonexistent" resolution = self.resolver.resolve('/foo/bar/searchup.myt', search_upwards = True, raise_error = True) csource = resolution.csource dumpresolution(resolution) self.assert_(csource is not None and csource.path == '/searchup.myt' and csource.dir_name == '/', "search upwards test #1 failed") def testupwardsrealdir(self): "locates a component searching upwards, with a real initial directory" resolution = self.resolver.resolve('/foo/lala/searchup2.myt', search_upwards = True, raise_error = True) csource = resolution.csource dumpresolution(resolution) self.assert_(csource is not None and csource.path == '/foo/searchup2.myt' and csource.dir_name == '/foo', "search upwards test #2 failed, " + csource.path + " " + csource.dir_name) def testnotfound(self): "tries to locate a component that doesnt exist" resolution = self.resolver.resolve('/foo/hi.myt', search_upwards = False, raise_error = False) self.assert_(resolution is None, "not found test failed") def testupwardsnotfound(self): "tries to locate a component searching upwards that does not exist" resolution = self.resolver.resolve('/foo/autohandler', search_upwards = True, raise_error = False) self.assert_(resolution is None, "not found upwards test failed") def testupwardsnoslash(self): "locates a component upwards, where the uri has no directory information" resolution = self.resolver.resolve('autohandler', search_upwards = True, raise_error = False) self.assert_(resolution is None, "not found upwards test with no slashes failed") def testdhandler(self): "locates a dhandler" resolution = self.resolver.resolve('/foo/bar/lala/foo.myt', enable_dhandler = True, raise_error = False) dumpresolution(resolution) self.assert_(resolution is not None, "dhandler test failed") csource = resolution.csource self.assert_(csource.path == '/dhandler' and csource.dir_name == '/', "dhandler test failed") self.assert_(resolution.dhandler_path == 'foo/bar/lala/foo.myt', "dhandler_path is %s" % resolution.dhandler_path) def testdhandlernoslash(self): "attempts to locate a dhandler with an initial uri that has no directory information, and no dhandler" self.remove_file(self.components, "dhandler") resolution = self.resolver.resolve('hoho.myt', enable_dhandler = True, raise_error = False) self.assert_(resolution is None, "dhandler test with no slashes failed") def testdir(self): "attempts to locate a component with a uri that resolves to a real directory" try: resolution = self.resolver.resolve('/foo/lala/', search_upwards = False, raise_error = True) self.assert_(False, "didnt detect directory") # we sort of liked when this was PathIsDirectory except exception.ComponentNotFound, e: #print e.message() self.assert_(True) class PathModuleResolveTest(testbase.MyghtyTest): def class_set_up(self): self.create_file(self.components, 'pathmodule1.py', """ import myghty.component as component def dostuff(m): pass """) self.create_file(self.lib, 'pathmodule2.py', """ import myghty.component as component def lala(m): pass """) pm2 = __import__('pathmodule2') self.create_directory(self.components, 'foo/bar') self.create_file(self.components, 'foo/bar/pathmodule3.py', """ import myghty.component as component def dostuff(m): pass def index(): pass class lala: def __call__(): pass def foofoo(m): pass bar = lala() """) self.create_file(self.components, 'foo/dhandler.py', """ import myghty.component as component def dostuff(m): pass def index(m): pass class lala: def __call__(): pass hoho = lala() """) self.resolver = resolver.Resolver( module_root = [ # a file path self.components, # an actual module pm2, ], ) def class_tear_down(self): self.remove_file(self.components, 'pathmodule1.py') self.remove_file(self.components, 'pathmodule2.py') self.remove_file(self.components, 'foo/dhandler.py') self.remove_file(self.components, 'foo/bar/pathmodule3.py') def testpathmod1(self): resolution = self.resolver.resolve('/pathmodule1/dostuff', raise_error = True) dumpresolution(resolution) csource = resolution.csource self.assert_(csource is not None) def testpathmod2(self): resolution = self.resolver.resolve('/lala', raise_error = False) dumpresolution(resolution) csource = resolution.csource self.assert_(csource is not None) def testpathmod3(self): resolution = self.resolver.resolve('/foo/bar/pathmodule3/dostuff', raise_error = False) dumpresolution(resolution) csource = resolution.csource self.assert_(csource is not None) def testpathmod4(self): resolution = self.resolver.resolve('/foo/bar/pathmodule3/lala', raise_error = False) dumpresolution(resolution) csource = resolution.csource self.assert_(csource is not None) def testpathmod5(self): resolution = self.resolver.resolve('/foo/hoho', raise_error = False, enable_dhandler = True) dumpresolution(resolution) csource = resolution.csource self.assert_(csource is not None) def testpathmod6(self): resolution = self.resolver.resolve('/foo/bar/pathmodule3/', raise_error = False) dumpresolution(resolution) csource = resolution.csource self.assert_(csource is not None) def testpathmod7(self): resolution = self.resolver.resolve('/foo/bar/pathmodule3/bar/foofoo', raise_error = False) dumpresolution(resolution) csource = resolution.csource self.assert_(csource is not None) class ModuleResolveTest(testbase.MyghtyTest): def class_set_up(self): self.create_file(self.lib, '__init__.py', "") self.create_file(self.lib, 'mymodule1.py', """ import myghty.component as component def mod1dostuff(m): pass class TestComp(component.ModuleComponent): def do_component_init(self, **params): pass def do_run_component(self, m, ARGS, **params): pass """) self.create_file(self.lib, 'mymodule2.py', """ import myghty.component as component def dostuff(m): pass def index(m): pass class TestComp(component.ModuleComponent): def do_component_init(self, **params): pass def do_run_component(self, m, ARGS, **params): pass class foo(object): def run(self, m): pass lala = foo() """) self.mod2 = __import__('mymodule2') self.resolver = resolver.Resolver( module_components = [ {'/index.myt' : 'mymodule1:TestComp'}, {'/foo/.*': self.mod2.TestComp}, {'/bar/.*': "mymodule2:dostuff"}, {'/foo2/.*': self.mod2.lala.run}, {'/foo3/.*': "mymodule2:lala.run"}, ], ) def class_tear_down(self): self.remove_file(self.lib, '__init__.py') self.remove_file(self.lib, 'mymodule1.py') self.remove_file(self.lib, 'mymodule1.pyc') self.remove_file(self.lib, 'mymodule2.py') self.remove_file(self.lib, 'mymodule2.pyc') try: del sys.modules['mymodule1'] except KeyError:pass try: del sys.modules['mymodule2'] except KeyError:pass def testresolvestring(self): resolution = self.resolver.resolve('/index.myt', search_upwards = False, raise_error = False) csource = resolution.csource dumpresolution(resolution) self.assert_(csource is not None and csource.id == 'module|mymodule1:class:TestComp', "unexpected id: " + csource.id) self.assert_(sys.modules.has_key('mymodule1'), 'module1 module not loaded into sys.modules') def testresolvemod(self): resolution = self.resolver.resolve('/foo/lala.myt', search_upwards = False, raise_error = False) csource = resolution.csource dumpresolution(resolution) self.assert_(csource is not None and csource.id == 'module|mymodule2:class:TestComp', "unexpected id: " + csource.id) self.assert_(sys.modules.has_key('mymodule2'), 'module2 module not loaded into sys.modules') def testresolvemodfunction(self): resolution = self.resolver.resolve('/bar/lala', raise_error = False) csource = resolution.csource dumpresolution(resolution) self.assert_(csource is not None) self.assert_(sys.modules.has_key('mymodule2'), 'module2 module not loaded into sys.modules') def testresolvemodmethod(self): resolution = self.resolver.resolve('/foo2/lala', raise_error = False) csource = resolution.csource dumpresolution(resolution) self.assert_(csource is not None) def testresolvemodmethodstring(self): resolution = self.resolver.resolve('/foo3/lala', raise_error = False) csource = resolution.csource dumpresolution(resolution) self.assert_(csource is not None) def testnotfound(self): resolution = self.resolver.resolve('/foob/lala.myt', search_upwards = True, raise_error= False) self.assert_(resolution is None) def dumpresolution (resolution): # TODO: echo flags if False: print "\nID: " + resolution.csource.id print "resolve: " + string.join(resolution.detail, ',') print "match: " + repr(resolution.match) if __name__ == '__main__': runner = unittest.TextTestRunner(verbosity=2, descriptions=False) unittest.main(testRunner=runner) myghty-1.1/test/test_subrequest.py0000644000175000017500000000520010501064113016450 0ustar malexmalex# subrequest.py """Test subrequests """ import StringIO, unittest from myghty import interp, request import testbase class SubrequestTests(testbase.ComponentTester): srcFiles = { 'subreq.myt': 'Howdy' } def testCapture(self): self.runComponent(''' <%flags> trim = "both" <%python> import StringIO buf = StringIO.StringIO() subreq = m.create_subrequest("/subreq.myt", out_buffer=buf, request_args={}) subreq.execute() Subrequest said: "<% buf.getvalue() %>" ''') self.failUnlessEqual(self.output, 'Subrequest said: "Howdy"') def testNoCapture(self): self.runComponent(''' <%flags> trim = "both" Subrequest said: "<% m.subexec("subreq.myt") or '' %>" ''') self.failUnlessEqual(self.output, 'Subrequest said: "Howdy"') def testSubreq(self): self.runComponent("/subreq.myt") self.failUnlessEqual(self.output, 'Howdy') def testNoFlushOnAbort(self): self.runComponent(''' <%flags> trim = "both" % m.subexec("subreq.myt") % m.abort() ''') self.failUnlessEqual(self.output, '') def testAutoFlush(self): self.runComponent('''# <%flags> autoflush = True % m.subexec("subreq.myt") % m.abort() ''') self.failUnlessEqual(self.output, 'Howdy') def testDefaultOutputEncoding(self): encoding, subreq_encoding = self.runComponent(''' <%flags> trim = "both" <%python> import StringIO buf = StringIO.StringIO() subreq = m.create_subrequest("/subreq.myt", out_buffer=buf, request_args={}) return ( (m.output_encoding, m.encoding_errors), (subreq.output_encoding, subreq.encoding_errors) ) ''', config={'output_encoding': 'iso-8859-15', 'encoding_errors': 'ignore'}) self.failUnlessEqual(subreq_encoding, (request.DEFAULT_OUTPUT_ENCODING, request.DEFAULT_ENCODING_ERRORS)) self.failIfEqual(encoding, (request.DEFAULT_OUTPUT_ENCODING, request.DEFAULT_ENCODING_ERRORS)) if __name__ == '__main__': unittest.main() myghty-1.1/test/test_testbase.py0000644000175000017500000000455110501064113016070 0ustar malexmalex"""test/test_testbase.py Tests for stuff in test/testbase.py. """ import re, sys, unittest import testbase from myghty.component import Component class ComponentTesterTests(testbase.ComponentTester): """Test cases for ComponentTester """ srcFiles = { 'htdocs/foo.myt': 'foo', 'lib/barmod.py': 'def f(m): m.write("bar")', } config = dict(module_components=[{'bar': 'barmod:f'}]) def testFoo(self): self.runComponent("foo.myt") self.failUnlessEqual(self.output, 'foo') def testBar(self): self.runComponent("bar") self.failUnlessEqual(self.output, 'bar') def testRunInlineComponent(self): self.runComponent( """# comment howdy """) self.failUnlessEqual(self.tidyoutput, 'howdy') def testRunInlineModuleComponent(self): def comp(m): m.write("output") self.runComponent(comp) self.failUnlessEqual(self.output, 'output') def testMakeModule(self): testmod = self.makeModule("def f(m): m.write('boo')") self.runComponent(testmod.f) self.failUnlessEqual(self.output, "boo") def testMakeMemoryComponent(self): comp = self.makeMemoryComponent( ''' <%flags> trim = "both" from memory ''') self.failUnless(isinstance(comp, Component)) self.runComponent(comp) self.failUnlessEqual(self.output, "from memory") class TestUnwrapErrors(testbase.ComponentTester): """Test advice added to unittest.TestResult.addError() Exceptions which are wrapped in a myghty.Error should be unwrapped before the error is reported. """ def x_test(self): self.runComponent("""% message = 'Boo' % raise RuntimeError, message""") print self.output def test(self): mytest = unittest.defaultTestLoader.loadTestsFromName( '%s.TestUnwrapErrors.x_test' % __name__) result = unittest.TestResult() mytest.run(result) self.failUnlessEqual(len(result.errors), 1) failed, message = result.errors[0] last_line = message.rstrip().split('\n')[-1] self.failUnlessEqual("RuntimeError: Boo", last_line) if __name__ == '__main__': unittest.main() myghty-1.1/test/testbase.py0000644000175000017500000001605010501064113015026 0ustar malexmaleximport unittest, os, sys, StringIO, itertools, inspect, re, tempfile, time from myghty import util, interp, exception, csource, importer from myghty.component import Component class MyghtyTest(unittest.TestCase): def __init__(self, *args, **params): unittest.TestCase.__init__(self, *args, **params) # make ourselves a Myghty environment self.root = os.path.abspath(os.path.join(os.getcwd(), 'testroot', self.__class__.__module__, self.__class__.__name__)) # some templates self.htdocs = os.path.join(self.root, 'htdocs') # some more templates self.components = os.path.join(self.root, 'components') # data dir for cache, sessions, compiled self.cache = os.path.join(self.root, 'cache') # lib dir for some module components self.lib = os.path.join(self.root, 'lib') sys.path.insert(0, self.lib) for path in (self.htdocs, self.components, self.cache, self.lib): util.verify_directory(path) self.class_set_up() def class_set_up(self): pass def class_tear_down(self): pass def __del__(self): self.class_tear_down() def create_file(self, dir, name, contents): file = os.path.join(dir, name) f = open(file, 'w') f.write(contents) f.close() def create_directory(self, dir, path): util.verify_directory(os.path.join(dir, path)) def remove_file(self, dir, name): if os.access(os.path.join(dir, name), os.F_OK): os.remove(os.path.join(dir, name)) def deindent(text): """De-indent docstring like text. """ # Hack use inspect.getdoc() to do the de-indentation return inspect.getdoc(property(doc=text)) class ComponentTester(MyghtyTest): config = {} srcFiles = {} def __init__(self, *args, **params): MyghtyTest.__init__(self, *args, **params) # Clean up from previous runs now = time.time() for root, dirs, files in os.walk(self.root): try: for fn in [ os.path.join(root, f) for f in files ]: if os.stat(fn).st_mtime < now - 60: os.unlink(fn) except IOError: pass def class_set_up(self): MyghtyTest.class_set_up(self) for name, content in self.srcFiles.items(): self.createSrcFile(name, content) def createSrcFile(self, filename, content): m = re.match(r'\A ( htdocs | lib | components ) /+', filename, re.X) if m: filename = filename[m.end():] srcdir = getattr(self, m.group(1)) else: srcdir = self.htdocs self.create_file(srcdir, filename, deindent(content)) def makeInterpreter(self): config = dict(component_root=self.htdocs, raise_error=True, data_dir=self.cache ) config.update(self.config) return interp.Interpreter(**config) def setUp(self): self.interpreter = self.makeInterpreter() self.outputBuffer = StringIO.StringIO() output = property(lambda self: self.outputBuffer.getvalue()) tidyoutput = property(lambda self: re.sub(r'\s+', ' ', self.output.strip())) def create_anonymous_file(self, src, dir=None, suffix='.myt'): if dir is None: dir = os.path.join(self.root, 'tmp') util.verify_directory(dir) fd, path = tempfile.mkstemp(prefix='anon', suffix=suffix, dir=dir, text=True) os.write(fd, deindent(src)) os.close(fd) return os.path.basename(path) def makeModule(self, src): # create a python module in self.lib name = self.create_anonymous_file(src, dir=self.lib, suffix='.py') return importer.module(os.path.splitext(name)[0]) def makeModuleComponent(self, arg): # src can be a string like "module:callable", etc... # or it can be an actually callable, etc... return self.interpreter.load_module_component(arg=arg) def makeFileBasedComponent(self, src): name = self.create_anonymous_file(src, dir=self.htdocs) return self.loadComponent("/%s" % name) def makeMemoryComponent(self, src): return self.interpreter.make_component(deindent(src)) def makeComponent(self, src): """Make a component of the default sort. The idea is that this can be specialized by subclasses to change the type of component that gets constructed by code like: self.runComponent('''# Here's source code for some component foo ''') """ return self.makeFileBasedComponent(src) def loadComponent(self, component): if not isinstance(component, basestring): # Wrap callable, etc... to a module component return self.makeModuleComponent(component) if re.search(r'\s', component): # if string contains white-space, interpret it as the # source for a myghty component, rather than the path # to be resolved. return self.makeComponent(component) return self.interpreter.load(component) def runComponent(self, component, config={}, **request_args): request_config = dict(request_args=request_args, out_buffer=self.outputBuffer ) request_config.update(config) if not isinstance(component, Component): component = self.loadComponent(component) return self.interpreter.execute(component, **request_config) def failUnlessRaisesWrappedError(self, wrapped_exception, func, *args, **kw): def _unwrap_error(func, args, kw): try: func(*args, **kw) except exception.Error, error: if error.wrapped is None: raise raise error.wrapped except: self.fail("expected myghty Error, got %s" % sys.exc_info()[1]) self.failUnlessRaises(wrapped_exception, _unwrap_error, func, args, kw) # Add advice to unittest.TestResult.addError to unwrap wrapped Errors TestResult_addError = unittest.TestResult.addError instancemethod = type(TestResult_addError) def addError(self, test, err): if isinstance(err[1], exception.Error): err = getattr(err[1], 'raw_excinfo', err) TestResult_addError.im_func(self, test, err) unittest.TestResult.addError = addError def runTests(suite): runner = unittest.TextTestRunner(verbosity = 2, descriptions =1) runner.run(suite) if __name__ == '__main__': unittest.main() myghty-1.1/tools/0000755000175000017500000000000010501065765013037 5ustar malexmalexmyghty-1.1/tools/gen.py0000644000175000017500000000370610501064114014153 0ustar malexmalex#!/usr/bin/env python import sys try: import optparse except: sys.stderr.write("gen.py requires the optparse module, available in python 2.3 or higher\n") sys.exit(-1) import myghty.interp import StringIO, re, os.path parser = optparse.OptionParser(usage = "usage: %prog [options] files...") parser.add_option("--croot", action="store", dest="component_root", default=".", help="set component root (default: ./)") parser.add_option("--dest", action="store", dest="destination", default=".", help="set destination directory (default: ./)") parser.add_option("--stdout", action="store_true", dest="stdout", help="send output to stdout") parser.add_option("--datadir", action="store", dest="datadir", help="set data directory (default: dont use data directory)") parser.add_option("--ext", action="store", dest="extension", default=".html", help="file extension for output files (default: .html)") parser.add_option("--source", action="store_true", dest="source", help="generate the source component to stdout") (options, args) = parser.parse_args() params = {} if options.datadir: params['data_dir'] = options.datadir interp = myghty.interp.Interpreter(component_root = options.component_root, **params ) if not len(args): parser.print_help() for arg in args: if options.source: source = interp.resolver.get_component_source(arg) source.get_object_code(interp.compiler(), sys.stdout) continue elif options.stdout: outbuf = sys.__stdout__ else: (dir, name) = os.path.split(arg) if options.destination: dir = options.destination outfile = re.sub(r"\..+$", "%s" % options.extension, name) outfile = os.path.join(dir, outfile) print "%s -> %s" % (arg, outfile) outbuf = open(outfile, "w") interp.execute(arg, out_buffer = outbuf) if not options.stdout: outbuf.close() myghty-1.1/tools/myghty.cgi0000644000175000017500000000132410501064114015027 0ustar malexmalex#!/usr/local/bin/python # myghty cgi runner. place in cgi-bin directory and address Myghty templates # with URLs in the form: # http://mysite.com/cgi-bin/myghty.cgi/path/to/template.myt # component root. this is where the Myghty templates are. component_root = '/path/to/croot' # data directory. this is where Myghty puts its object files. data_dir = '/path/to/datadir' # module components. module_components = [] # libraries. Put paths to additional custom Python libraries here. lib = ['/path/to/custom/libraries'] import sys [sys.path.append(path) for path in lib] import myghty.CGIHandler as handler handler.handle( component_root=component_root, module_components = module_components, data_dir=data_dir) myghty-1.1/tools/run_docs.py0000644000175000017500000001346310501064114015217 0ustar malexmalex""" tools/run_docs.py documentation runner. *** ADVANCED EXAMPLE !!! PUSHES CONFIG TO THE LIMIT ! *** for a simpler standalone example, please consult /examples/shoppingcart/run_cart.py . """ import os, sys, re # adjust path to use local directory [sys.path.insert(0, path) for path in ['./lib', './doc/lib', './examples/common', './examples/shoppingcart/lib', './examples/formvisitor/lib']] # myghty imports import myghty.http.HTTPServerHandler as HTTPServerHandler from myghty.resolver import * # determine port number try: port = int(sys.argv[1]) except: port = 8000 # create local cache directory to store generated files + sessions if not os.access('./cache', os.F_OK): os.mkdir('./cache') # now set up standalone server httpd = HTTPServerHandler.HTTPServer( port = port, # send all request content types through the full resolution chain text_only = False, # handlers. a list of regular expressions matched to directories to serve # or Interpreter configurations. handlers = [ {r'.*/$|.*\.myt$|/source/.*|/examples/shoppingcart/store/.*' : HTTPServerHandler.HSHandler( data_dir = './cache', #use_static_source = True, #debug_elements = ['resolution'], #disable_unicode=True, resolver_strategy = [ # caching. if static_source is turned on, everything below this rule # is cached based on the URI as well as contextual modifiers. URICache(), # create a group that handles all shopping cart URIs ConditionalGroup(regexp='/examples/shoppingcart/store/.*', rules = [ # create a "request-only" subgroup for resolving the lead module # components. this isolates request-level resolutions from # subrequest and component resolutions, greatly reducing the # overall number of lookups required per-component. ConditionalGroup(context='request', rules = [ #ResolveModule( # {r'.*/catalog/.*' : 'shoppingcontroller:index.catalog'}, # {r'.*/item/.*' : 'shoppingcontroller:index.item'}, # {r'.*/cart/.*' : 'shoppingcontroller:index.cart'}, # {r'.*/source/.*' : 'shoppingcontroller:index.source'}, # ), ResolvePathModule( 'shoppingcontroller', adjust = lambda u: re.sub(r'/examples/shoppingcart/store/', '/', u) ), ]), ResolveDhandler(), ResolveUpwards(), ResolveFile( {'store_comp':'./examples/shoppingcart/components'}, {'store_templ':'./examples/shoppingcart/templates'}, {'store_htdocs':'./examples/shoppingcart/htdocs'}, adjust = lambda u: re.sub(r'/examples/shoppingcart/store/', '/', u) ), NotFound() ]), # resolution rules for the /source/ browser Conditional( ResolveModule({r'.*' : 'modulecomponents:ViewSource'}), regexp='/source/.*' ), # resolution rules for the documentation viewer ConditionalGroup(regexp='^/doc/$|^/doc/.*\.myt$', rules = [ PathTranslate( (r'^/doc/$', '/doc/index.myt'), ), # upwards search, i.e. for /autohandler. the remaining rules # in this chain will be used to match against successive # /autohandler uris. ResolveUpwards(), AdjustedResolveFile( [(r'^/doc/', r'/')], {'common':'./examples/common'}, {'doc_comp':'./doc/components'}, {'doc_content':'./doc/content'}, ), NotFound() ]), # default resolution rules, after the above conditionals have not been # met PathTranslate( (r'^/examples', ''), (r'/$', '/index.myt'), ), # if a dhandler is being searched, the remainder of the rules will # be queried with successive /dhandler uris ResolveDhandler(), ResolveUpwards(), ResolveFile( {'common':'./examples/common'}, {'example_htdocs':'./examples'}, ), ], attributes = { 'store_uri' : '/examples/shoppingcart/store/', 'store_document_uri' : '/examples/shoppingcart/docs/', 'store_path' : '/examples/shoppingcart/store/', 'source_uri' : '/source/', 'source_root' : './', 'docs_static_cache': True, }, ) }, # docroots {r'/examples/shoppingcart/docs/(.*)' : './examples/shoppingcart/htdocs'}, {r'/examples/shoppingcart/store/source/(.*)' : './examples/shoppingcart/'}, {r'/doc/(.*)' : './doc/html'}, {r'/examples/(.*)' : './examples'}, {r'/source/(.*)' : '.'}, {r'/(.*)':'./examples'}, ] ) print "HTTPServer listening on port %d" % httpd.port httpd.serve_forever()