albatross-1.36/0000755000175000017500000000000010577422307013320 5ustar andrewmandrewmalbatross-1.36/doc/0000755000175000017500000000000010577422307014065 5ustar andrewmandrewmalbatross-1.36/doc/twolayer.dia0000644000175000017500000000216107660434616016417 0ustar andrewmandrewmZn6}Wʫe˖7k/E lm Zbl)Ptlۗ8nHV]@Xps8#} EL8çg%Ph('fZem[@1\J6 ֿRd)#˜?ƩI")Yn$6 \"Unsʅ||I? ~PEm(*& ~Mn+#?0z̆8h# XVl"9s!C!D$^D\H,9 I n%s. /DJ~!Dkbnm[7J^,X4xْ@s3OW}ߓW%u'Lyސ2]iͬK Uҩ=Bdo:mNߎT}^7⺺@9*j(YMٕ(υ:/۝g0MXu' (ơ<mM (k$bg!qW"F=ycvcubm5gڲJZU "wVHp$O3wwg}Q-)MZ|}o9<\.`n}P,f:{ڪ_S{ú\`̧Ԧvy` Ӹmɩi)6,N0vȊM?7*ԓާޗq Q._f5&%{'=}ȫ|6ۭ[S'Mu4Xkekc<4K"iQf V= XGi[ 7fi(clar`(TJ,MQ=}=[7^{f{W]ܔ=&1C\rRe\x@_s]LnNH,~{} e~ \2Gd^ʛ4פCmpASyyE4'bF2rcnP""Y; ?],ziMen{F՗oݼ;W!͢r1-S@2Lh`uyB)YFR"eO֏d?JZX9T5&b\זh>'grf(ݶ,dx#;G>+I$<)I?G{BGXT7 ]F' h/_{c';gTṴ=h&<++±޸hUB^ ՚x+h;jUU,K-8afWH̵f7)`[Oq큝(TǎkH* Sbشب4IVjadvUy _פeW5QѢ|pHM[h1XS ʕ {IW ue8]gQA0tmuYJ3<=k-k-k-k-k-k-N1N[wڊ|TK ~@?VpRZXYJ(@=@Ыz CD|ӾX{8K"aཇzu6P1K7~xW \1bpŀx@>+&j'߅WK09?i{nyeL:=Uo)ݼ V u'fE2iiiiiiUW^ " P(>xw|o(A<KB0<06 m1 }4l# HwҰFѨОSPlql~WȰ^K{i`/ 쥁4^K1VXcx5DH7R~DZi%'jH=p) ;YzCC! O|e0ưpj䳿)) r ' NwJU ;^́?FD@q0xaw;t)vzz9∇1zzޅg1sf1$fi3Js6Vڨ4f7,fN,6n0HՒh, Y̐ Y̐ Y̐ Y̐ Y̐ YbE!KbF=AW]P<0H v<0<̥a.WLcLMc_Ә6n~6M205{{WOBzwɜ ̬̬̬̬̬̬D\qE"3SwvIc) {y<+5>>L)b; +*n>Pn>aFralbatross-1.36/doc/simplecontext.dia0000644000175000017500000000555110135141327017436 0ustar andrewmandrewm]]s8}_Aѯ 0n~u:+{wROu"`)ͤXFAw]jS'>t??fݦzJFWׯɤJ1.bp]D\zߕٔJth_חy㭮CZhD-(oGMm֝LƓ;t>9˚5/RIUThmU%n'iرfɠ,kb'x2n+%JZH&οsLT)QP3{9ց5ڟx§!;tSKH[O>1}ZFTqCWs7d=Pɸ63.gyg]*1̦̽ƚT1p[8/ O5)m?[bH2V> Z2Ǣb3_7s`qbBSK|qx`T6w0rI3 jfY¥I-\yƆؑQzI~p~ɩǣ9\g^={D,\P wץ'X:_Oz @ V $akA 䀵X^"7XҨsfþCVOpv((2Z^ ٫@Wt+=K|+%`}hKWx}߁wRHˀw|wQy0,1,?ߘGCNg? ~05 q|i߁w; <!;]|w^~ERLz?Ƹ9X- U7 g9sCAvHh9@??????v#"z[R݂RxT΀!`6a:.e2#=Hz`=rD @ze/"iyQB6m|ndwy͐IЀIA>A>A>A>A>A>B j4a^%k]P{ {=r(n<@| ?\W3ݞKl{VXpHyiM&"\bػ)vbr+) 4@J) 4@J) 4|h)ͣfmZEhUdN~(@ sGRy4vK7;yZ96w[ F协^аED CCS2䖞x*$&t ~mAԈG˸ʀuHVNH&+'IdɕKTPIC% 4TPIC% 4TPIC%qU4d{OT&PKC- 4ҥaB4H3MBXnX䋀8ã <BAHBB!"BD"Hu?)`0]vC:6ڹ 2 |`>0TU&"麲7 >ЬB8x7Nzok#zk6SvT6I1Z:'F+W \1b[@|`rӐ|3_̬/蟙m_>ialbatross-1.36/doc/customtags.tex0000644000175000017500000002352510445726615017012 0ustar andrewmandrewm%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Copyright 2001 by Object Craft P/L, Melbourne, Australia. % LICENCE - see LICENCE file distributed with this software for details. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{Developing Custom Tags\label{cust-ref}} In complex applications you may encounter presentation problems which the standard collection of tags cannot easily solve. Albatross allows you to register additional tags, which are then available for use in your templates. Custom tags are named with an \texttt{alx-} prefix to distinguish them from standard \texttt{al-} tags. Custom tags should subclass either \class{EmptyTag} or \class{EnclosingTag}, and have a \texttt{name} class attribute. The name should start with \texttt{alx-} and contain only letters, numbers and the underscore character. Custom tags that produce form inputs need to register the names of those inputs with the \class{NameRecorderMixin} via the \method{input\_add} method. For more information, see section~\ref{mixin-rec-name}, NameRecorderMixin in the Mixin Class Reference. The following is a simple calendar tag which formats a single month like the unix \program{cal(1)} program. \begin{verbatim} import time import calendar import albatross class Calendar(albatross.EmptyTag): name = 'alx-calendar' def to_html(self, ctx): year = self.get_attrib('year') if year is not None: year = ctx.eval_expr(year) month = self.get_attrib('month') if month is not None: month = ctx.eval_expr(month) if month is None or year is None: now = time.localtime(time.time()) if year is None: year = now[0] if month is None: month = now[1] ctx.write_content('\n') ctx.write_content('\n' \ % (calendar.month_name[month], year)) ctx.write_content('') for i in range(7): ctx.write_content('' \ % calendar.day_abbr[(i + 6) % 7][:2]) ctx.write_content('\n') calendar.setfirstweekday(6) for r in calendar.monthcalendar(year, month): ctx.write_content('') for i in range(7): if r[i]: ctx.write_content('' % r[i]) else: ctx.write_content('') ctx.write_content('\n') ctx.write_content('
%s %s
%s
%s
\n') \end{verbatim} To use the tag in your application you must make the class available to the execution context. If you are using an Albatross application object you can do this by passing the class to the \method{register_tagclasses()} method of the application object. \begin{verbatim} from albatross import SimpleApp app = SimpleApp('ext.py', '.', 'start') app.register_tagclasses(Calendar) \end{verbatim} All Albatross application classes inherit from the \class{ResourceMixin} in the \module{albatross.context} module. Execution contexts which are used with application objects inherit from the \class{AppContext} class from the \module{albatross.app} module which automatically retrieves all resources from the parent application object. If you are using the \class{SimpleContext} class for your execution context then you will need to call the \method{register_tagclasses()} method of the execution context immediately after construction. The following is an example template file which uses the \texttt{} tag. \begin{verbatim} Calendar for <al-value expr="year">

Calendar for

\end{verbatim} A complete program which uses this extension tag and template file can be found in the \texttt{samples/extension} directory. Use the \texttt{install.py} script to install the sample. \begin{verbatim} cd samples/extension python install.py \end{verbatim} The implementation of the standard tags also makes a good reference when writing custom tags. All standard tags are defined in \module{albatross.tags}. \section{\module{albatross.template} --- Base classes for implementing tags} \declaremodule{}{albatross.template} The module contains the following classes which are intended to be used in implementing custom tags. \begin{classdesc}{Tag}{ctx, filename, line_num, attribs} This is the base class upon which all tags are implemented. You are unlikely to ever subclass this directly. The \class{EmptyTag} and \class{EnclosingTag} classes inherit from this class. \end{classdesc} \begin{classdesc}{EmptyTag}{ctx, filename, line_num, attribs} Use this class as a subclass for all tags which do not require a closing tag and therefore do not enclose content. Examples of standard HTML tags which do not enclose content are \texttt{
} and \texttt{
}. \end{classdesc} \begin{classdesc}{EnclosingTag}{ctx, filename, line_num, attribs} Use this class as a subclass for all tags which enclose content. Examples of standard HTML tags which enclose content are \texttt{} and \texttt{}. \end{classdesc} \begin{classdesc}{Text}{text} A simple wrapper around the string passed in the \var{text} constructor argument which passes that string to the \method{to_html()} method when the object is converted to HTML. \end{classdesc} \begin{classdesc}{Content}{} A simple wrapper around a list which calls the \method{to_html()} method of all list elements when the object is converted to HTML. \end{classdesc} \subsection{Tag Objects\label{cust-tag}} \begin{methoddesc}[Tag]{raise_error}{msg} Raises a \exception{TemplateError} exception using the string in the \var{msg} argument. \end{methoddesc} \begin{methoddesc}[Tag]{has_attrib}{name} Returns \code{TRUE} if the attribute specified in the \var{name} argument was defined for the tag. All attribute names are converted to lower case by the template parser. \end{methoddesc} \begin{methoddesc}[Tag]{assert_has_attrib}{name} If the attribute specified in the \var{name} argument is not defined for the tag a \exception{TemplateError} exception will be raised. \end{methoddesc} \begin{methoddesc}[Tag]{assert_any_attrib}{*names} If none of the attributes specified by the arguments are defined for the tag a \exception{TemplateError} exception will be raised. \end{methoddesc} \begin{methoddesc}[Tag]{get_attrib}{name \optional{, default \code{= None}}} Retrieves the value of the attribute specified in the \var{name} argument. \end{methoddesc} \begin{methoddesc}[Tag]{set_attrib}{name, value} Sets the value of the attribute named in the \var{name} argument to the value in the \var{value} argument. \end{methoddesc} \begin{methoddesc}[Tag]{set_attrib_order}{order} Defines the order that the tag attributes will be written during conversion to HTML. The template parser captures the attribute sequence from the template file then calls this method. \end{methoddesc} \begin{methoddesc}[Tag]{attrib_items}{} Returns a list of attribute name, value tuples which are defined for the tag. \end{methoddesc} \begin{methoddesc}[Tag]{write_attribs_except}{ctx \optional{, \ldots}} Sends all tag attributes to the \method{write_content()} method of the execution context in the \var{ctx} argument. Any attributes named in additional arguments will not be written. \end{methoddesc} \subsection{EmptyTag Objects\label{cust-emptytag}} \begin{methoddesc}[EmptyTag]{has_content}{} Returns \code{0} to inform the template parser that the tag does not enclose content. \end{methoddesc} \begin{methoddesc}[EmptyTag]{to_html}{ctx} The template interpreter calls this method to convert the tag to HTML for the execution context in the \var{ctx} argument. The default implementation does nothing. You must override this method in your tag class to perform all actions which are necessary to ``execute'' the tag. \end{methoddesc} \subsection{EnclosingTag Objects\label{cust-enclosingtag}} \begin{memberdesc}[EnclosingTag]{content} An instance of the \class{Content} class which is created during the constructor. \end{memberdesc} \begin{methoddesc}[EnclosingTag]{has_content}{} Returns \code{1} to inform the template parser that the tag encloses content. \end{methoddesc} \begin{methoddesc}[EnclosingTag]{append}{item} Called by the template parser to append the content in the \var{item} argument to the tag. The method implementation simply passes \var{item} to the \method{append()} method of the \member{content} member. You should override this method if you need to maintain multiple content lists within your tag. \end{methoddesc} \begin{methoddesc}[EnclosingTag]{to_html}{ctx} The template interpreter calls this method to convert the tag to HTML for the execution context in the \var{ctx} argument. The default implementation passes \var{ctx} to the the \method{to_html()} method of the \member{content} member. You must override this method in your tag class to perform all actions which are necessary to ``execute'' the tag. \end{methoddesc} \subsection{Text Objects\label{cust-text}} \begin{methoddesc}[Text]{to_html}{ctx} Sends the wrapped text to the \method{write_content()} method of the execution context in the \var{ctx} argument. You should not ever need to subclass these objects. \end{methoddesc} \subsection{Content Objects\label{cust-content}} \begin{methoddesc}[Content]{append}{item} Appends the value in the \var{item} argument to the internal Python list. \end{methoddesc} \begin{methoddesc}[Content]{to_html}{ctx} Sequentially invokes the \method{to_html()} method of every item in the internal Python list passing the \var{ctx} argument. \end{methoddesc} albatross-1.36/doc/mixins.tex0000644000175000017500000017425210577416325016134 0ustar andrewmandrewm%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Copyright 2001 by Object Craft P/L, Melbourne, Australia. % LICENCE - see LICENCE file distributed with this software for details. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{Mixin Class Reference\label{mixin-ref}} Most of Albatross exists as a collection of plug compatible mixin classes which you select from to define the way your application should behave and how it will be deployed. Figure \ref{fig-toolkit} shows the organisation of the component types in the toolkit. \begin{figure}[h] \begin{center} \includegraphics{toolkit} \caption{Toolkit Components\label{fig-toolkit}} \end{center} \end{figure} The divisions in the diagram represent conceptually different functional areas within the toolkit. \begin{description} \item[Templating] This layer provides the Albatross templating functionality. Template classes make use of the methods defined in the next layer down to access application functionality and data. Classes in this layer are defined in the \module{albatross.template} and \module{albatross.tags} modules. \item[Template Execution] An execution context suitable for interpreting template files is constructed by combining one mixin of each type from this layer. \item[Application Model] Execution contexts which subclass a \class{PageMixin} class in addition to the mixins from the above layer are suitable for use in Albatross applications. The \class{PageMixin} class controls how the application locates code and template files for each page served by the application. The \class{PickleSignMixin} class is responsible for modifying pickles which are sent to the browser to prevent or detect modification. \item[Your Application] In this layer you will typically create your own application and execution context classes by subclassing a prepared application class. For the most part Albatross applications are independent of the method by which they are deployed. Depending upon which \class{Request} class you choose from this layer you can either deploy your application via CGI, \texttt{mod_python} (\url{http://www.modpython.org/}), FastCGI (\url{http://www.fastcgi.com/}) or as a stand-alone python HTTP server. \end{description} Wherever possible the Albatross mixin classes use member names which begin with double underscore to trigger Python name mangling. This protects your classes from having member name clashes with private members of the mixin classes. Any member names which are not mangled are intended to be accessed in your application code. \section{ResourceMixin Class\label{mixin-resource}} Albatross only supplies one class for this function; the \class{ResourceMixin} class. This mixin manages application resources which which do not change regardless of context. The resources managed are tag classes, HTML macros, and HTML lookup tables. The \class{SimpleContext} execution context class subclasses the \class{ResourceMixin} class. During the constructor it registers all of the standard Albatross tags. As HTML templates are executed the macros and lookup tables in those templates are registered. All standard Albatross application classes inherit from the \class{Application} class which in turn subclasses the \class{ResourceMixin} class. During the application class constructor all of the standard Albatross tags are registered. The \class{AppContext} class which is subclassed by all Albatross application execution context classes proxies all HTML macro and lookup table methods and directs them to the application object. \begin{methoddesc}[ResourceMixin]{__init__}{} When you inherit from the \class{ResourceMixin} class you must call this constructor to initialise the internal variables. \end{methoddesc} \begin{methoddesc}[ResourceMixin]{get_macro}{name} Returns the macro previously registered by the name in the \var{name} argument. If no such macro exists \code{None} is returned. \end{methoddesc} \begin{methoddesc}[ResourceMixin]{register_macro}{name, macro} Registers the HTML macro in the \var{macro} argument under the name in the \var{name} argument. \end{methoddesc} \begin{methoddesc}[ResourceMixin]{get_lookup}{name} Returns the lookup table previously registered by the name in the \var{name} argument. If no such lookup exists \code{None} is returned. \end{methoddesc} \begin{methoddesc}[ResourceMixin]{register_lookup}{name, lookup} Registers the HTML lookup table in the \var{lookup} argument under the name in the \var{name} argument. \end{methoddesc} \begin{methoddesc}[ResourceMixin]{discard_file_resources}{filename} Discards macros and lookups loaded from \var{filename}. This is called prior to reloading a template. \end{methoddesc} \begin{methoddesc}[ResourceMixin]{get_tagclass}{name} Returns the tag class in which the \member{name} member matches the name in the \var{name} argument. If no such tag class exists \code{None} is returned. \end{methoddesc} \begin{methoddesc}[ResourceMixin]{register_tagclasses}{\ldots} Registers one or more tag classes indexing them by the value in the \member{name} member of each class. \end{methoddesc} \section{ExecuteMixin Class\label{mixin-execute}} Albatross only supplies one class for this function; the \class{ExecuteMixin} class. This mixin provides a ``virtual machine'' which is used to execute HTML template files. All standard Albatross execution context classes inherit from this class. \begin{methoddesc}[ExecuteMixin]{__init__}{} When you inherit from the \class{ExecuteMixin} class you must call this constructor to initialise the internal variables. \end{methoddesc} \begin{methoddesc}[ExecuteMixin]{get_macro_arg}{name} Retrieves a macro argument from the macro execution stack. The stack is searched from the most recently pushed dictionary for an argument keyed by \var{name}. If no argument is found a \exception{MacroError} exception is raised. \end{methoddesc} \begin{methoddesc}[ExecuteMixin]{push_macro_args}{dict} Pushes a dictionary of macro arguments onto the macro execution stack. \end{methoddesc} \begin{methoddesc}[ExecuteMixin]{pop_macro_args}{} Pops the most recently pushed dictionary of macro arguments from the macro execution stack. \end{methoddesc} \begin{methoddesc}[ExecuteMixin]{push_content_trap}{} Saves accumulated content on the trap stack and then resets the content list. \end{methoddesc} \begin{methoddesc}[ExecuteMixin]{pop_content_trap}{} Joins all parts on the content list and returns the result. Sets the content list to the value popped off the trap stack. The content trap is useful for performing out-of-order execution of template text. The \texttt{} tag makes use of the content trap. \end{methoddesc} \begin{methoddesc}[ExecuteMixin]{write_content}{data} Appends the content in \var{data} to the content list. \end{methoddesc} \begin{methoddesc}[ExecuteMixin]{flush_content}{} Does nothing if a content trap stack is in effect, otherwise it joins all parts in the content list and sends it to the browser via the \method{send_content()} method. The content list is then reset via the \method{reset_content()} method. \end{methoddesc} \begin{methoddesc}[ExecuteMixin]{flush_html}{} This is an alias for \method{flush_content()}. \end{methoddesc} \begin{methoddesc}[ExecuteMixin]{send_content}{data} Sends the content passed in the \var{data} argument to standard output. This is overridden in the \class{AppContext} class to redirect \var{data} to the \method{write_content()} method of the application referenced in the \member{app} member. \end{methoddesc} \begin{methoddesc}[ExecuteMixin]{reset_content}{} Sets the content list to an empty list and clears the content trap stack. \end{methoddesc} \section{ResponseMixin Class\label{mixin-response}} The \class{ResponseMixin} class provides functionality to manage the delivery of the application response to the browser. The class maintains headers in a case insensitive ordered dictionary that ensures the headers are sent to the browser in the same sequence as they are set (via \method{set_header()} or \method{add_header}). The class automatically sends the headers to the browser when the first content is sent. Any attempt to modify or send headers after they have been sent will raise an \exception{ApplicationError} exception. All Albatross execution context classes except for \class{SimpleContext} inherit from this class. \begin{methoddesc}[ResponseMixin]{__init__}{} When you inherit from the \class{ResponseMixin} class you must call this constructor to initialise the internal variables. \end{methoddesc} \begin{methoddesc}[ResponseMixin]{get_header}{name} Returns a list of values of the \var{name} header from the internal store. If the header does not exist then \code{None} is returned. \end{methoddesc} \begin{methoddesc}[ResponseMixin]{set_header}{name, value} Sets the value of the \var{name} header to \var{value} in the internal store, replacing any existing headers of the same name. If headers have already been sent to the browser then an \exception{ApplicationError} exception will be raised. \end{methoddesc} \begin{methoddesc}[ResponseMixin]{add_header}{name, value} Sets the value of the \var{name} header to \var{value} in the internal store, appending the new header immediately after any existing headers of the same name. If headers have already been sent to the browser then an \exception{ApplicationError} exception will be raised. \end{methoddesc} \begin{methoddesc}[ResponseMixin]{del_header}{name} Removes the \var{name} header from the internal store. If headers have already been sent to the browser then an \exception{ApplicationError} exception will be raised. \end{methoddesc} \begin{methoddesc}[ResponseMixin]{write_headers}{} Writes all headers in ascending sequence to the browser. Each header is sent via the \class{Request} object \method{write_header()} method. At the end of headers the \class{Request} object \method{end_headers()} method is called. If headers have already been sent to the browser then an \exception{ApplicationError} exception will be raised. \end{methoddesc} \begin{methoddesc}[ResponseMixin]{send_content}{data} Sends the content in \var{data} to the browser via the \class{Request} object \method{write_content()} method. If headers have not already been delivered to the browser then the \method{write_headers()} method is called before the \var{data} is written. \end{methoddesc} \begin{methoddesc}[ResponseMixin]{send_redirect}{loc} If a cookie header has been set it is sent to the browser then the \method{redirect()} method of the \member{request} member is called and the result is returned. \end{methoddesc} \section{TemplateLoaderMixin Classes\label{mixin-template}} This mixin is responsible for loading template files. Albatross supplies two classes; \class{TemplateLoaderMixin} and \class{CachingTemplateLoaderMixin}. \subsection{TemplateLoaderMixin\label{mixin-templ-loader}} The \class{TemplateLoaderMixin} class is a simplistic loader which performs no caching. \begin{methoddesc}[TemplateLoaderMixin]{__init__}{base_dir} When you inherit from the \class{TemplateLoaderMixin} class you must call the constructor to define the root directory where template files will be loaded in the \var{base_dir} argument. \end{methoddesc} \begin{methoddesc}[TemplateLoaderMixin]{load_template}{name} Load and return the parsed template file specified in the \var{name} argument. The path to the template file is constructed by performing \code{os.path.join()} on the \var{base_dir} specified in the constructor and the \var{name} argument. If there is an error reading the template a \exception{TemplateLoadError} will be raised. The class remembers the names of all loaded templates. \end{methoddesc} \begin{methoddesc}[TemplateLoaderMixin]{load_template_once}{name} Returns \code{None} if the template specified in the \var{name} argument has been previously loaded. If not previously loaded it is loaded via the \method{load_template()} method and returned. \end{methoddesc} \subsection{CachingTemplateLoaderMixin\label{mixin-templ-cache}} The \class{CachingTemplateLoaderMixin} class caches loaded templates to and only reloads them if they have been modified since they were last loaded. \begin{methoddesc}[CachingTemplateLoaderMixin]{__init__}{base_dir} When you inherit from the \class{CachingTemplateLoaderMixin} class you must call the constructor to define the root directory where template files will be loaded in the \var{base_dir} argument. \end{methoddesc} \begin{methoddesc}[CachingTemplateLoaderMixin]{load_template}{name} Return the parsed template file specified in the \var{name} argument. The path to the template file is constructed by performing \code{os.path.join()} on the \var{base_dir} specified in the constructor and the \var{name} argument. If there is an error reading the template a \exception{TemplateLoadError} will be raised. If the template has been previously loaded it will only be reloaded if it has been modified since last load. \end{methoddesc} \begin{methoddesc}[CachingTemplateLoaderMixin]{load_template_once}{name} Call the \method{load_template()} method and return the template if it is either loaded for the first time or reloaded, else return \code{None}. \end{methoddesc} \section{RecorderMixin Classes\label{mixin-recorder}} This mixin is passed form and input field recording messages as \texttt{}, \texttt{}, \texttt{}, and \texttt{} tags are executed. Albatross supplies two classes; \class{StubRecorderMixin} and \class{NameRecorderMixin}. \subsection{StubRecorderMixin\label{mixin-rec-stub}} The \class{StubRecorderMixin} class ignores all form events. \begin{methoddesc}[StubRecorderMixin]{form_open}{} Does nothing. \end{methoddesc} \begin{methoddesc}[StubRecorderMixin]{form_close}{} Does nothing. \end{methoddesc} \begin{methoddesc}[StubRecorderMixin]{input_add}{itype, name \optional{, value \code{= None}}} Does nothing. \end{methoddesc} \begin{methoddesc}[StubRecorderMixin]{merge_request}{} Merges request fields into \code{ctx.locals}. \end{methoddesc} \subsection{NameRecorderMixin\label{mixin-rec-name}} The \class{NameRecorderMixin} class records details of all input fields used by a form. When the form element is closed, a hidden field named \texttt{__albform__} containing these details is added to the form. When processing a request, the \method{merge_request()} method only merges fields with \code{ctx.locals} when they match the details found in the submitted \texttt{__albform__} field. \begin{methoddesc}[NameRecorderMixin]{__init__}{} When you inherit from the \class{NameRecorderMixin} class you must call the constructor. \end{methoddesc} \begin{methoddesc}[NameRecorderMixin]{form_open}{} Called when the \texttt{} tag is opened. \end{methoddesc} \begin{methoddesc}[NameRecorderMixin]{form_close}{} Called just before the \texttt{} tag is closed. A hidden field named \texttt{__albform__} is written to the output. \end{methoddesc} \begin{methoddesc}[NameRecorderMixin]{input_add}{itype, name \optional{, value \code{= None}} \optional{, return_list \code{= 0}}} Called when an \texttt{} tag is executed. The \var{itype} argument contains the \texttt{type} attribute from the input tag, \var{name} contains the \texttt{name} tag attribute, and \var{value} contains the value of the input field if it is known and relevant. The \var{return_list} argument indicates the presence of the \texttt{list} attribute on the input tag. As fields are added to each form the value of the \var{return_list} argument is checked against any previous setting of the argument for the same field name. The argument value is also checked against whether or not there are multiple instances of the field name. An detected discrepancy between the argument value and actual fields will raise a \exception{ApplicationError} exception. \end{methoddesc} \begin{methoddesc}[NameRecorderMixin]{merge_request}{} Retrieves the \texttt{__albform__} value from the browser request decodes it and then merges the browser request into the local namespace accordingly. If an input field has been flagged to return a list (via the \texttt{list} tag attribute) then the method will create a list in \code{ctx.locals} for the field regardless of the number of values sent by the browser. An empty list is created when the field is missing from the browser request. Request fields not listed in \texttt{__albform__} are ignored. \end{methoddesc} \section{NamespaceMixin Class\label{mixin-namespace}} Albatross only supplies one class for this function; the \class{NamespaceMixin} class. This mixin provides a local and global namespace for evaluating expressions embedded in HTML template files. When the browser request is merged into the execution context the input field values are written to the local namespace. \begin{methoddesc}[NamespaceMixin]{__init__}{} When you inherit from the \class{NamespaceMixin} class you must call the constructor. The global namespace for evaluating Python expressions in HTML templates is initialised as an empty dictionary in the constructor. \end{methoddesc} \begin{memberdesc}[NamespaceMixin]{locals} An empty object which is used for the local namespace for evaluating expressions in HTML templates. It is initialised as an instance of an empty class in the constructor to allow values to simply be assigned to attributes of this member. Loading the session merges the session values into this member. \end{memberdesc} \begin{methoddesc}[NamespaceMixin]{clear_locals}{} Resets the \member{locals} member to an empty object. \end{methoddesc} \begin{methoddesc}[NamespaceMixin]{set_globals}{dict} Sets the global namespace for evaluating expressions to the \var{dict} argument. The \class{SimpleContext} class constructor automatically sets this to the globals of the function which invoked the \class{SimpleContext} constructor. The \method{run_template()} and \method{run_template_once()} methods of the \class{AppContext} calls this method to set global namespace to the globals of the calling function. \end{methoddesc} \begin{methoddesc}[NamespaceMixin]{eval_expr}{expr} Called by the template file interpreter to evaluate the embedded Python expression in the \var{expr} argument. \end{methoddesc} \begin{methoddesc}[NamespaceMixin]{set_value}{name, value} Sets the local namespace attribute named in the \var{name} argument to the value in the \var{value} argument. If the \var{name} argument begins with an underscore the method will raise a \exception{SecurityError} exception. This is used by the application \method{merge_request()} method to merge individual browser request fields into the local namespace. There is a special ``backdoor'' identifier format which which directs browser request fields to the \method{set_backdoor()} method of \class{ListIterator} and \class{TreeIterator} objects. The backdoor identifiers are generated by the \texttt{} and \texttt{} tags to implement sequence and tree browsing requests. The method implements a parser which can handle names of the form: \begin{productionlist} \production{name} {identifier | list-backdoor | tree-backdoor} \production{identifier} {identifier (("." identifier) | ("[" number "]"))*} \production{list-backdoor} {operation "," iter} \production{tree-backdoor} {operation "," iter "," alias} \end{productionlist} \end{methoddesc} \begin{methoddesc}[NamespaceMixin]{merge_vars}{...} This method merges request fields matching a prefix given in the argument list to the local namespace (via the \method{set_value()} method described above). Normally, merging of request fields is automatic: either all request fields are copied when \class{StubRecorderMixin} is used, or fields listed in \texttt{__albform__} are copied when \class{NameRecorderMixin} is used. However in cases where \class{NameRecorderMixin} is used and no \texttt{__albform__} field is present, request merging does not occur, and this method is needed to allow the application to explicitly request fields be merged. \end{methoddesc} \begin{methoddesc}[NamespaceMixin]{make_alias}{name} Called to generate an alternate name for an object referenced in the \texttt{alias} attribute of an Albatross tag. The method resolves the \var{name} argument up to the last ``.'' and then calls the \method{albatross_alias()} method of the resolved object. The resolved object is then entered into the local namespace and the session using the name returned by \method{albatross_alias()}. The return value is a new name by combining the name returned by \method{albatross_alias()} with the part of the original name following and including the last ``.''. Refer to the \texttt{} documentation on page \pageref{tag-input} in section \ref{tag-input} for an explanation of why this method exists. \end{methoddesc} \begin{methoddesc}[NamespaceMixin]{get_value}{name} Retrieves the value identified by the \var{name} argument from the local namespace. If the named value does not exist then \code{None} is returned. \end{methoddesc} \begin{methoddesc}[NamespaceMixin]{has_value}{name} Returns whether or not the value named in the \var{name} attribute exists in the local namespace. \end{methoddesc} \begin{methoddesc}[NamespaceMixin]{has_values}{\ldots} Returns TRUE only if values named in the argument list exist in the local namespace. \end{methoddesc} \section{SessionContextMixin Classes\label{mixin-ctxsession}} This mixin is used to manage the encoding and decoding of session data. Albatross supplies a number of classes for use in the execution context; \class{StubSessionMixin}, \class{SessionBase}, \class{HiddenFieldSessionMixin}, \class{SessionServerContextMixin}, and \class{SessionFileContextMixin}. The \class{SessionBase} class provides base functionality for non-stub session classes. Loading and storing of session data is usually performed by an application mixin. The \class{SessionServerAppMixin} is designed to be used in the application object. \subsection{StubSessionMixin\label{mixin-ses-stub}} The \class{StubSessionMixin} class ignores all session operations. \begin{methoddesc}[StubSessionMixin]{add_session_vars}{\ldots} Does nothing. \end{methoddesc} \begin{methoddesc}[StubSessionMixin]{del_session_vars}{\ldots} Does nothing. \end{methoddesc} \begin{methoddesc}[StubSessionMixin]{encode_session}{} Does nothing. \end{methoddesc} \begin{methoddesc}[StubSessionMixin]{load_session}{} Does nothing. \end{methoddesc} \begin{methoddesc}[StubSessionMixin]{save_session}{} Does nothing. \end{methoddesc} \begin{methoddesc}[StubSessionMixin]{remove_session}{} Does nothing. \end{methoddesc} \begin{methoddesc}[StubSessionMixin]{set_save_session}{flag} Does nothing. \end{methoddesc} \begin{methoddesc}[StubSessionMixin]{should_save_session}{} Returns \code{0}. \end{methoddesc} \subsection{SessionBase\label{mixin-ses-base}} The \class{SessionBase} class provides base session handling functionality which is used by all standard Albatross execution context session mixin classes. \begin{methoddesc}[SessionBase]{__init__}{} When you inherit from the \class{SessionBase} class you must call this constructor. The class maintains a dictionary of all names from the execution context local namespace which belong in the session. This dictionary is restored along with the session when the session is decoded. \end{methoddesc} \begin{methoddesc}[SessionBase]{add_session_vars}{\ldots} Adds all listed names to the session dictionary. The named variables must exist in the \member{locals} member or a \exception{SessionError} will be raised. The names can optionally be supplied as a list or tuple of names. \end{methoddesc} \begin{methoddesc}[SessionBase]{default_session_var}{name, value} Adds a name to the session directory. Sets a value in the local namespace if the name is not already in the local namespace. \end{methoddesc} \begin{methoddesc}[SessionBase]{del_session_vars}{\ldots} Deletes all listed names from the session dictionary. The names can optionally be supplied as a list or tuple of names. \end{methoddesc} \begin{methoddesc}[SessionBase]{session_vars}{} Returns a list of the names that are currently in the session. \end{methoddesc} \begin{methoddesc}[SessionBase]{remove_session}{} Deletes all names from the session dictionary and clears all values from the local namespace via the \method{clear_locals()} method. \end{methoddesc} \begin{methoddesc}[SessionBase]{decode_session}{text} Performs \code{cPickle.loads()} to retrieve a dictionary of session values. The dictionary is merged into the session local namespace. Adds the keys of the dictionary to the session dictionary. Note that an import hook is used around the \code{cPickle.loads()} to redirect requests to load page modules to the \method{app.load_page_module()} method. This allows the pickler to find classes which are defined in application page modules. Whether a module is a page module is determined by calling the \method{app.is_page_module()} method, passing the module name. \end{methoddesc} \begin{methoddesc}[SessionBase]{encode_session}{} Builds a dictionary by extracting all local namespace values which are listed in the session dictionary. A test pickle is performed on each value and any unpickleable value is discarded and an error message is written to \code{sys.stderr}. The dictionary is then passed to \code{cPickle.dumps()} and the result is returned. \end{methoddesc} \begin{methoddesc}[SessionBase]{set_save_session}{flag} Sets the flag which controls whether the session will be saved at the end of request processing. By default the internal flag is \code{TRUE} which means the session will be saved. \end{methoddesc} \begin{methoddesc}[SessionBase]{should_save_session}{} Returns the flag which controls whether the session will be saved at the end of request processing. \end{methoddesc} \subsection{HiddenFieldSessionMixin\label{mixin-ses-field}} Saves session state to a hidden field named \texttt{__albstate__} at the end of every form produced by \texttt{} tags. Inherits from the \class{SessionBase} class so you must call the constructor if you subclass this class. \begin{methoddesc}[HiddenFieldSessionMixin]{encode_session}{} Extends the base class \method{encode_session()} method to \code{zlib.compress()} and \code{base64.encodestring()} the result. This makes the session data suitable for placing in a hidden field in the HTML. \end{methoddesc} \begin{methoddesc}[HiddenFieldSessionMixin]{load_session}{} This is called from the \class{Application} class \method{load_session()} method. The session state is retrieved from the browser request, decoded and decompressed then passed to the \method{decode_session()} method. \end{methoddesc} \begin{methoddesc}[HiddenFieldSessionMixin]{save_session}{} This is called from the \class{Application} class \method{save_session()} method at the end of the request processing sequence. The method does nothing because the session state is saved in hidden fields in the HTML. \end{methoddesc} \begin{methoddesc}[HiddenFieldSessionMixin]{form_close}{} Called just before the \texttt{} tag is closed. If the session is flagged to be saved a hidden field named \texttt{__albstate__} is written to the output. Note that this method is also present in the \class{RecorderMixin}, so if you inherit from the \class{HiddenFieldSessionMixin} class you must define a \method{form_close()} method in the derived class which calls this method in both of the super classes. \end{methoddesc} \subsection{SessionServerContextMixin\label{mixin-ctx-sess}} This class works in concert with the \class{SessionServerAppMixin} application mixin class to store session data in the Albatross session server. All management of session data storage is performed by the application class. Inherits from the \class{SessionBase} class so you must call the constructor if you subclass this class. \begin{methoddesc}[SessionServerContextMixin]{__init__}{} When you inherit from the \class{SessionServerContextMixin} class you must call this constructor. \end{methoddesc} \begin{methoddesc}[SessionServerContextMixin]{sesid}{} Returns the session id. \end{methoddesc} \begin{methoddesc}[SessionServerContextMixin]{load_session}{} This is usually called from the \class{Application} class \method{load_session()} method. Retrieves the session id and then either retrieves an existing session or creates a new session via the application object. If an existing session is retrieved it is passed to \code{base64.decodestring()} and \code{zlib.decompress()} then passed to the \method{decode_session()} method (inherited from the superclass). If an exception is raised during \method{decode_session()} then the session will be deleted from the server and a new session will be created via the application object \method{new_session()} method. \end{methoddesc} \begin{methoddesc}[SessionServerContextMixin]{save_session}{} This is called from the \class{Application} class \method{save_session()} method at the end of the request processing sequence. If the session save flag has been cleared via the \method{set_save_session()} method then the session is not saved. Before saving a session the method calls the superclass \method{encode_session()} then calls \code{zlib.compress()} and \code{base64.encodestring()} to convert the session to plain text which is passed to the \method{put_session()} application method to save the session. \end{methoddesc} \begin{methoddesc}[SessionServerContextMixin]{remove_session}{} This is called from the \class{Application} class \method{remove_session()} method. The method calls the superclass \method{remove_session()} then calls the \method{del_session()} application method to remove the session at the server. \end{methoddesc} \subsection{SessionFileContextMixin\label{mixin-ctx-file-sess}} This class works in concert with the \class{SessionFileAppMixin} application mixin class to store session data in the local file-system. All management of session data storage is performed by the application class. Inherits from the \class{SessionBase} class so you must call the constructor if you subclass this class. \begin{methoddesc}[SessionFileContextMixin]{__init__}{} When you inherit from the \class{SessionFileContextMixin} class you must call this constructor. \end{methoddesc} \begin{methoddesc}[SessionFileContextMixin]{sesid}{} Returns the session id. \end{methoddesc} \begin{methoddesc}[SessionFileContextMixin]{load_session}{} This is usually called from the \class{Application} class \method{load_session()} method. Retrieves the session id and then either retrieves an existing session or creates a new session via the application object. If an existing session is retrieved it is passed to the \method{decode_session()} method (inherited from the superclass). If an exception is raised during \method{decode_session()} then the session will be deleted from the server and a new session will be created via the application object \method{new_session()} method. \end{methoddesc} \begin{methoddesc}[SessionFileContextMixin]{save_session}{} This is called from the \class{Application} class \method{save_session()} method at the end of the request processing sequence. If the session save flag has been cleared via the \method{set_save_session()} method then the session is not saved. Before saving a session the method calls the superclass \method{encode_session()} then calls the \method{put_session()} application method to save the session. \end{methoddesc} \begin{methoddesc}[SessionFileContextMixin]{remove_session}{} This is called from the \class{Application} class \method{remove_session()} method. The method calls the superclass \method{remove_session()} then calls the \method{del_session()} application method to remove the session at the server. \end{methoddesc} \subsection{BranchingSessionMixin\label{mixin-ctx-branching-sess}} A persistent problem with server-side sessions is the browser state getting out of synchronisation with the application state. This occurs when the browser "back" button is used, or when a form is reloaded (this is logically equivilent to a "back" then a resubmission of the old form state). One solution to this problem is to maintain a server-side session for each interaction with the browser, rather than a single session per client that is recycled for each interaction. A unique session identifier is stored in a hidden form field, which allows us to retrieve the appropriate version of the session on form submission (the hidden field value is rolled back with the browser state when the "back" button is used, unlike a cookie). This class provides a drop-in replacement for the \class{SessionServerContextMixin} and implements this session-per-interaction behaviour. \begin{methoddesc}[BranchingSessionMixin]{__init__}{} When you inherit from the \class{BranchingSessionMixin} class you must call this constructor. \end{methoddesc} \begin{methoddesc}[BranchingSessionMixin]{sesid}{} Returns the session id. \end{methoddesc} \begin{methoddesc}[BranchingSessionMixin]{load_session}{} This is usually called from the \class{Application} class \method{load_session()} method. Retrieves the session id and then either retrieves an existing session or creates a new session via the application object. If an existing session is retrieved it is passed to the \method{decode_session()} method (inherited from the superclass). If an exception is raised during \method{decode_session()} then the session will be deleted from the server and a new session will be created via the application object \method{new_session()} method. \end{methoddesc} \begin{methoddesc}[BranchingSessionMixin]{save_session}{} This is called from the \class{Application} class \method{save_session()} method at the end of the request processing sequence. If the session save flag has been cleared via the \method{set_save_session()} method then the session is not saved. Before saving a session the method calls the superclass \method{encode_session()} then calls the \method{put_session()} application method to save the session. \end{methoddesc} \begin{methoddesc}[BranchingSessionMixin]{remove_session}{} This is called from the \class{Application} class \method{remove_session()} method. The method calls the superclass \method{remove_session()} then calls the \method{del_session()} application method to remove the session at the server. \end{methoddesc} \begin{methoddesc}[BranchingSessionMixin]{form_close}{} Called just before the \texttt{} tag is closed. If the session is flagged to be saved a hidden field named \texttt{__albsessid__} containing the session identifier is written to the output. Note that this method is also present in the \class{RecorderMixin}, so if you inherit from the \class{BranchingSessionMixin} class you must define a \method{form_close()} method in the derived class which calls this method in both of the super classes. \end{methoddesc} \section{SessionAppMixin Classes\label{mixin-appsession}} \subsection{SessionServerAppMixin\label{mixin-app-sess}} The application mixin works in concert with the \class{SessionServerContextMixin} execution context method to store sessions in the Albatross session server. Whenever there are problems communicating with the session server the class raises a \exception{SessionServerError} exception, which is a subclass of \exception{SessionError}. Unless you have a reason to do otherwise, catch \exception{SessionError} rather than \exception{SessionServerError}, as this allows other Session classes to be substituted with minimal change. \begin{methoddesc}[SessionServerAppMixin]{__init__}{appid \optional{, server \code{= 'localhost'}} \optional{, port \code{= 34343}} \optional{, age \code{= 1800}}} When you inherit from the \class{SessionServerAppMixin} class you must call this constructor. The \var{appid} argument specifies the name of the cookie attribute which is used to store the session id. This uniquely identifies the application at the web server. Multiple applications can share sessions by defining the same value in this argument. The \var{server} and \var{port} arguments specify the location of the Albatross session server. By using a session server you can have a number of web serving machines which transparently share session data. The \var{age} argument specifies how long (in seconds) an idle session will be stored at the server before it is discarded. A connection to the session server is established. The connection will be kept open for the lifetime of the application object. \end{methoddesc} \begin{methoddesc}[SessionServerAppMixin]{ses_appid}{} Returns the \var{appid} argument which was passed to the constructor. \end{methoddesc} \begin{methoddesc}[SessionServerAppMixin]{get_session}{sesid} Returns the session identified by \var{sesid} argument and the \var{appid} passed to the constructor. If no such session exists \code{None} is returned. \end{methoddesc} \begin{methoddesc}[SessionServerAppMixin]{new_session}{} Returns a new session id for the \var{appid} passed to the constructor. \end{methoddesc} \begin{methoddesc}[SessionServerAppMixin]{put_session}{sesid, text} Saves the \var{text} argument as session data for the session identified by \var{sesid} argument and the \var{appid} passed to the constructor. \end{methoddesc} \begin{methoddesc}[SessionServerAppMixin]{del_session}{sesid} Removes the session identified by \var{sesid} argument and the \var{appid} passed to the constructor. \end{methoddesc} \subsection{SessionFileAppMixin\label{mixin-app-file-sess}} The application mixin works in concert with the \class{SessionFileContextMixin} execution context method to store sessions in the local file-system. Whenever there are problems reading or writing sessions from or to disk, the class raises a \exception{SessionFileError} exception, which is a subclass of \exception{SessionError}. Unless you have a reason to do otherwise, catch \exception{SessionError} rather than \exception{SessionFileError}, as this allows other Session classes to be substituted with minimal change. \begin{methoddesc}[SessionFileAppMixin]{__init__}{appid , session_dir} When you inherit from the \class{SessionFileAppMixin} class you must call this constructor. The \var{appid} argument specifies the name of the cookie attribute which is used to store the session id. This uniquely identifies the application at the web server. Multiple applications can share sessions by defining the same value in this argument. The \var{session_dir} argument specifies the location on the local file-system in which this application's sessions will be stored. The directory should not be publicly readable, as the session file names are the session id's (knowing a session id allows an attacker to steal that session). Sessions recorded via this class are not automatically aged. An external process will be required to clean orphaned sessions from the session directory (for example, by removing any file that has not been accessed in the last two hours). \end{methoddesc} \begin{methoddesc}[SessionFileAppMixin]{ses_appid}{} Returns the \var{appid} argument which was passed to the constructor. \end{methoddesc} \begin{methoddesc}[SessionFileAppMixin]{get_session}{sesid} Returns the session identified by \var{sesid} argument and the \var{appid} passed to the constructor. If no such session exists \code{None} is returned. \end{methoddesc} \begin{methoddesc}[SessionFileAppMixin]{new_session}{} Returns a new session id for the \var{appid} passed to the constructor. Note that if the \var{session_dir} passed to the constructor does not already exist, this method will attempt to create it. \end{methoddesc} \begin{methoddesc}[SessionFileAppMixin]{put_session}{sesid, text} Saves the \var{text} argument as session data for the session identified by \var{sesid} argument and the \var{appid} passed to the constructor. \end{methoddesc} \begin{methoddesc}[SessionFileAppMixin]{del_session}{sesid} Removes the session identified by \var{sesid} argument and the \var{appid} passed to the constructor. \end{methoddesc} \section{PickleSignMixin Classes\label{mixin-sign}} This is mixed with the application class to sign or modify pickles before sending them to the browser and to undo and check that modification on the return trip. When processing modified pickles returned from the browser the class discards pickles which do not pass the security check. There is only one mixin supplied for this function; the \class{PickleSignMixin} class. Pickle strings are combined with the secret string which was passed to the application constructor as the \var{secret} argument using the HMAC-SHA1 algorithm. The resulting signature is then prepended to the pickle. On the return trip the HMAC-SHA1 sign is compared with the result of the signing process on the pickle returned from the browser. If the two signs are not the same, the pickle is discarded. The process does not prevent users from seeing the contents of a pickle, rather it provides an assurance of its authenticity. The mixin has the following interface. \begin{methoddesc}[PickleSignMixin]{__init__}{secret} When you inherit from the \class{PickleSignMixin} class you must call this constructor. The \var{secret} argument is the secret key which is combined with the pickle to produces the HMAC-SHA1 signature. \end{methoddesc} \begin{methoddesc}[PickleSignMixin]{pickle_sign}{text} Generates an HMAC-SHA1 signed copy of the \var{text} argument, using the \var{secret} constructor argument as key. \end{methoddesc} \begin{methoddesc}[PickleSignMixin]{pickle_unsign}{text} Compares the HMAC-SHA1 signature on the given \var{text}, and if valid, returns the unsigned text. If the signature does not match, a \exception{SecurityError} exception is raised. \end{methoddesc} \section{PageMixin Classes\label{mixin-page}} The choice of mixin for this functionality determines how Albatross will locate the code to process the browser request and display the response for each page. Albatross supplies three classes; \class{PageModuleMixin}, \class{RandomPageModuleMixin}, and \class{PageObjectMixin}. \subsection{PageModuleMixin\label{mixin-page-module}} This class uses a separate Python module for each page in the application. This scales very well at runtime because at most two modules will be loaded for each request; one to process the browser request, and possibly another to display the response. Page modules are cached for further efficiency. The class is designed to be used where the application controls the sequence of pages seen in a browser session so the start page is also specified in the constructor. Application page modules are loaded from a base directory which is specified by the constructor \var{base_dir} argument. The current application page is identified by the path to the page module relative to the module base directory. Page identifiers consist of an optional path component followed by a module name without extension. For example \texttt{"login"}, \texttt{"user/list"}, \texttt{"home-page/default"}. Page modules are loaded into a dummy \texttt{__alpage__} namespace to avoid conflicts with python modules, so loading \texttt{"user/list"} actually imports the module as \module{__alpage__.user.list}. To support pickling of instances defined in a page module, a dummy hierarchy of modules needs to be created. In the \module{__alpage__.user.list} case mentioned above, a temporary dummy module called \module{__alpage__.user} is registered. This will be replaced by the real \texttt{user} module later if it is loaded. Note also that the \class{SessionBase} mixin uses an import hook while decoding the session to redirects attempts to load page modules (those that being with \texttt{__alpage__}) to the \method{load_page_module()} method. Page modules handled by this mixin have the following interface: \begin{funcdescni}{page_enter}{ctx \optional{, \ldots}} If this function is present in the new page module it will be called whenever your application code calls the execution context \method{set_page()} method. For application types which define a start page this method is called in the start page when a new session is created. The \var{ctx} argument contains the execution context. Any extra arguments which are passed to the \function{set_page()} method are passed as optional extra arguments to this function. \end{funcdescni} \begin{funcdescni}{page_leave}{ctx} If this function is present in the current page module it will be called whenever your application code changes to another page by calling the execution context \method{set_page()} method. The \var{ctx} argument contains the execution context. \end{funcdescni} \begin{funcdescni}{page_process}{ctx} If this function is present in the page module it will be called when the application object executes the \method{process_request()} method. This occurs if the browser request was successfully validated. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{funcdescni} \begin{funcdescni}{page_display}{ctx} This is the only mandatory page module function. The application object calls this function when it executes the \method{display_response()} method as the final step before saving the browser session. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{funcdescni} The \class{PageModuleMixin} class has the following interface. \begin{methoddesc}[PageModuleMixin]{__init__}{base_dir, start_page} When you inherit from the \class{PageModuleMixin} class you must call this constructor. The \var{base_dir} argument specifies the path of the root directory where page modules are loaded from. When deploying your application as a CGI program you can specify a relative path from the location of the application mainline. Apache sets the current directory to root so when using \texttt{mod_python} deployment you will need to specify a path relative to the root. The \var{start_page} argument is a page identifier which specifies the first page that new browser session will see. \end{methoddesc} \begin{methoddesc}[PageModuleMixin]{module_path}{} Returns the \var{base_dir} which was passed to the constructor. \end{methoddesc} \begin{methoddesc}[PageModuleMixin]{start_page}{} Returns the \var{start_page} which was passed to the constructor. \end{methoddesc} \begin{methoddesc}[PageModuleMixin]{load_page}{ctx} This method implements part of the standard application processing sequence. It is called immediately after restoring the browser session. The \var{ctx} argument is the execution context for the current browser request. If no current page is defined in \var{ctx} then the method will invoke \method{ctx.set_page()} passing the page specified as the \var{start_page} argument to the application constructor. The actual page module load is performed via the \method{load_page_module()} method. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{methoddesc} \begin{methoddesc}[PageModuleMixin]{load_page_module}{ctx, name} Loads the page module identified by the \var{name} argument and saves a reference to the module in the \member{page} member. \end{methoddesc} \begin{methoddesc}[PageModuleMixin]{page_enter}{ctx, args} Called when your application code calls the execution context \method{set_page()} method. The \var{ctx} argument is the execution context for the current browser request. The \var{args} argument is a tuple which contains all optional extra arguments which were passed to the \method{set_page()} method. The page module \function{page_enter()} function is called by this method. \end{methoddesc} \begin{methoddesc}[PageModuleMixin]{page_leave}{ctx} Called before changing pages when your application code calls the execution context \method{set_page()} method. The \var{ctx} argument is the execution context for the current browser request. The page module \function{page_leave()} function is called by this method. \end{methoddesc} \begin{methoddesc}[PageModuleMixin]{process_request}{ctx} This method implements part of the standard application processing sequence. It is called if the browser request is successfully validated. The \var{ctx} argument is the execution context for the current browser request. The page module \function{page_process()} function is called by this method. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{methoddesc} \begin{methoddesc}[PageModuleMixin]{display_response}{ctx} This method implements part of the standard application processing sequence. It is called as the final stage just before the session is saved. The \var{ctx} argument is the execution context for the current browser request. The page module \function{page_display()} function is called by this method. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{methoddesc} \subsection{RandomPageModuleMixin\label{mixin-randpage-module}} This class inherits from the \class{PageModuleMixin} class. It redefines the way in which page modules are selected. Instead of the application calling the \method{set_page()} execution context method, the URL in the browser request controls which page module is loaded and processed for each request. Page module management is inherited from \class{PageModuleMixin}. The \var{base_dir} argument to the constructor determines the root directory where modules are loaded from. Page modules handled by this mixin have the following interface: \begin{funcdescni}{page_enter}{ctx} If this function is present in the page module it will be called every time the page module is used for a browser request. The \var{ctx} argument contains the execution context. \end{funcdescni} \begin{funcdescni}{page_process}{ctx} If this function is present in the page module it will be called when the application object executes the \method{process_request()} method. This occurs if the browser request was successfully validated. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{funcdescni} \begin{funcdescni}{page_display}{ctx} This is the only mandatory page module function. The application object calls this function when it executes the \method{display_response()} method as the final step before saving the browser session. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{funcdescni} The \class{RandomPageModuleMixin} class has the following interface. \begin{methoddesc}[RandomPageModuleMixin]{load_page}{ctx} This method implements part of the standard application processing sequence. It is called immediately after restoring the browser session. The \var{ctx} argument is the execution context for the current browser request. The \method{get_page_from_uri()} method is called to determine the identifier of the page module that will be loaded. The identifier is then passed to the \method{load_page_module()} method (which is inherited from \class{PageModuleMixin}). Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{methoddesc} \begin{methoddesc}[RandomPageModuleMixin]{get_page_from_uri}{ctx, uri} The method uses the \function{urlparse()} function from the standard Python \function{urlparse} module to extract the path component from both the \var{uri} parameter and the value returned by the \method{base_url()} method (which returns the \var{base_url} argument to the application constructor). The \var{path} component of the \var{base_url} is then used to split the \var{path} component of the \var{uri}. Element one (first split to the right of \var{base_url}) of the resulting string list is returned as the page identifier. Override this method in your application if you wish to implement a your own scheme for mapping the request onto a page identifier. \end{methoddesc} \begin{methoddesc}[RandomPageModuleMixin]{load_badurl_template}{ctx} Called when your page template identified by the request URL does not exist. The \var{ctx} argument is the execution context for the current browser request. Override this method if you want to supply a different error page template. \end{methoddesc} \begin{methoddesc}[RandomPageModuleMixin]{page_enter}{ctx} Called as soon as the page module has been loaded. The \var{ctx} argument is the execution context for the current browser request. The page module \function{page_enter()} function is called by this method if a page module was located by the \method{load_page()} method. \end{methoddesc} \begin{methoddesc}[RandomPageModuleMixin]{process_request}{ctx} This method implements part of the standard application processing sequence. It is called if the browser request is successfully validated. The \var{ctx} argument is the execution context for the current browser request. The page module \function{page_process()} function is called by this method if a page module was located by the \method{load_page()} method. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{methoddesc} \begin{methoddesc}[RandomPageModuleMixin]{display_response}{ctx} This method implements part of the standard application processing sequence. It is called as the final stage just before the session is saved. The \var{ctx} argument is the execution context for the current browser request. The page module \function{page_display()} function is called by this method if a page module was located by the \method{load_page()} method. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{methoddesc} \subsection{PageObjectMixin\label{mixin-page-object}} This class is intended for applications which do not require a separate Python module for each page in the application. Page processing is performed by a set of objects which the application registers with this class. The class is designed to be used where the application controls the sequence of pages seen in a browser session so the start page is specified in the constructor. Application page objects must be registered before they can be used. Typically you will register the page objects immediately after constructing your application object. Since the current application page is identified by an internal value, any hashable pickleable value can be used as an identifier. Page objects handled by this mixin have the following interface: \begin{methoddescni}[PageObject]{page_enter}{ctx \optional{, \ldots}} If this method is present in the new page object it will be called whenever your application code changes current the page by calling the execution context \method{set_page()} method. For application types which define a start page this method is called in the start page when a new session is created. The \var{ctx} argument contains the execution context. Any extra arguments which are passed to the \function{set_page()} method are passed as optional extra arguments to this method. \end{methoddescni} \begin{methoddescni}[PageObject]{page_leave}{ctx \optional{, \ldots}} If this method is present in the old page object it will be called whenever your application code changes current the page by calling the execution context \method{set_page()} method. The \var{ctx} argument contains the execution context. \end{methoddescni} \begin{methoddescni}[PageObject]{page_process}{ctx} If this method is present in the page object it will be called when the application object executes the \method{process_request()} method. This occurs if the browser request was successfully validated. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{methoddescni} \begin{methoddescni}[PageObject]{page_display}{ctx} This is the only mandatory page object function. The application object calls this method when it executes the \method{display_response()} method as the final step before saving the browser session. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{methoddescni} The \class{PageObjectMixin} class has the following interface. \begin{methoddesc}[PageObjectMixin]{__init__}{start_page} When you inherit from the \class{PageObjectMixin} class you must call this constructor. The \var{start_page} argument is a page identifier which specifies the first page that new browser session will see. \end{methoddesc} \begin{methoddesc}[PageObjectMixin]{module_path}{} Returns \code{None}. \end{methoddesc} \begin{methoddesc}[PageObjectMixin]{start_page}{} Returns the \var{start_page} argument which was passed to the constructor. \end{methoddesc} \begin{methoddesc}[PageObjectMixin]{register_page}{name, obj} You must call this method to register every page object in your application. The \var{name} argument defines the page identifier which is used to select the page object specified in the \var{obj} argument. All pages must be registered before they can be used. \end{methoddesc} \begin{methoddesc}[PageObjectMixin]{load_page}{ctx} This method implements part of the standard application processing sequence. It is called immediately after restoring the browser session. The \var{ctx} argument is the execution context for the current browser request. If no current page is defined in \var{ctx} then the method will invoke \method{ctx.set_page()} passing the page specified as the \var{start_page} argument to the application constructor. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{methoddesc} \begin{methoddesc}[PageObjectMixin]{page_enter}{ctx, args} Called when your application code calls the execution context \method{set_page()} method. The \var{ctx} argument is the execution context for the current browser request. The \var{args} argument is a tuple which contains all optional extra arguments which were passed to the \method{set_page()} method. The page object \function{page_enter()} method is called by this method. \end{methoddesc} \begin{methoddesc}[PageObjectMixin]{page_leave}{ctx} Called before changing pages when your application code calls the execution context \method{set_page()} method. The \var{ctx} argument is the execution context for the current browser request. The page object \function{page_leave()} method is called by this method. \end{methoddesc} \begin{methoddesc}[PageObjectMixin]{process_request}{ctx} This method implements part of the standard application processing sequence. It is called if the browser request is successfully validated. The \var{ctx} argument is the execution context for the current browser request. The page object \function{page_process()} method is called by this method. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{methoddesc} \begin{methoddesc}[PageObjectMixin]{display_response}{ctx} This method implements part of the standard application processing sequence. It is called as the final stage just before the session is saved. The \var{ctx} argument is the execution context for the current browser request. The page object \function{page_display()} method is called by this method. Refer to page \pageref{app-proc-seq} in section \ref{app-proc-seq} for an overview of the application processing sequence. \end{methoddesc} \section{Request Classes\label{mixin-request}} The choice of \class{Request} class determines how you wish to deploy your application. Albatross supplies a number of pre-built Request implementations suited to various deployment methods. These include: \begin{longtableii}{l|l}{textrm}{Deployment Method}{Request Module} \lineii{CGI}{\module{albatross.cgiapp}} \lineii{mod_python}{\module{albatross.apacheapp}} \lineii{FastCGI_python}{\module{albatross.fcgiapp}} \lineii{Stand-alone Python HTTP server}{\module{albatross.httpdapp}} \end{longtableii} You can also develop your own \class{Request} class to deploy an Albatross application in other ways. All \class{Request} classes implement the same interface. Much of this interface can be supplied by the \class{RequestBase} mixin. \begin{methoddesc}[Request]{has_field}{name} Returns \code{TRUE} if the field identified by the \var{name} argument is present in the request. \end{methoddesc} \begin{methoddesc}[Request]{field_value}{name} Return the value of the field identified by the \var{name} argument. \end{methoddesc} \begin{methoddesc}[Request]{field_file}{name} Returns an object that contains the value of a file input field. \end{methoddesc} \begin{methoddesc}[Request]{field_names}{} Return a list of all all fields names in the request. \end{methoddesc} \begin{methoddesc}[Request]{get_uri}{} Return the URL which the browser used to perform the request. \end{methoddesc} \begin{methoddesc}[Request]{get_servername}{} Return the name of the server (Apache ServerName setting). \end{methoddesc} \begin{methoddesc}[Request]{get_header}{name} Return the value of the HTTP header identified in the \var{name} argument. \end{methoddesc} \begin{methoddesc}[Request]{write_header}{name, value} Add a header named \var{name} with the value \var{value} to the response. This method should not be called once you have started sending content to the browser. \end{methoddesc} \begin{methoddesc}[Request]{end_headers}{} Signal to the Request object that header generation has finished and that you are ready to start sending content. \end{methoddesc} \begin{methoddesc}[Request]{redirect}{loc} Send a \texttt{"301 Moved Permanently"} response back to the browser. \end{methoddesc} \begin{methoddesc}[Request]{write_content}{data} Send \var{data} as part of the request response. \end{methoddesc} \begin{methoddesc}[Request]{set_status}{status} Sets the HTTP status code of the response. Defaults to 200. For deployment methods based on the cgiapp module, this value is used to derive the \texttt{Status:} header. The apacheapp module uses it to set the \member{status} member of the mod_python \class{request} object. \end{methoddesc} \begin{methoddesc}[Request]{status}{num} Return the saved value for the HTTP status code. \end{methoddesc} \begin{methoddesc}[Request]{return_code}{} Returns a value which should be returned from the \class{Application} class \method{run()} method. For most deployment methods, this is \texttt{None}, however the \texttt{mod_python} requires that \texttt{mod_python.apache.OK} be returned if application emits any content. Your \texttt{mod_python} application should include code such as this: \begin{verbatim} from albatross.apacheapp import Request : : def handler(req): return app.run(Request(req)) \end{verbatim} \end{methoddesc} albatross-1.36/doc/albmvc.dia0000644000175000017500000000330507660434616016016 0ustar andrewmandrewms6+b$&;Mg:=lN/{Fi1P!ɥ{ŏ!(3 Hr|}>&ws/>; 3䷻bq:4%F4$/Eh1)ʥQˏ"J9R{Gc3n!Q7yL͢w"LnQ{̄D?Mn3`wyR~DETuF~Ps _b#^/AKx: %ȣUM%G<\'"uPtͰ ]}xZT?׳Ln1T(q^ijn*ؕJlaPn);M&b S&!KtQ7ԀY&NZ)]z'*]pW CMfkKu)-hzz@'Ẅ́)U[0ꇇ@CIzѤbx>CRKyvFyV ~x<(hI&)? ^/U()AZɧ73~p4]ސgq ۢʃewrJoh8"A D}iʿKՂW60Bm؇іҌo>Fq~{#.FINv jxnpǐJY}tu2|e$=J1(ԫn'AY  (Eq%sQ2G%sh9jɝE(8mQ2G%sQS9, *]k_54.Ij%'t~ceS9ù Wccʹf^ON$] tΏ7$E~CSɌ8c} IQH@ p WO.=Zd*f-S3uI68j Y]qqv ~"6*@؍1" Je9=}RZ$$ ɥf=*=$w=&^U6*h?]v+= ^Ul[:Nqo Dٔg:IVnrCQĪ8b#Q.,,-}& 7Yv%a7`F|BCD}+ r+EW MVnr9*7P*QJ}Gͽ1r9i]4DøfYݯ⚈mFn6GD!9-[ޓDaD1P}R0% &<#%)u1XIH~bxLAl, Y_(c?`n^ {,~OB',gnO4ialbatross-1.36/doc/installation.tex0000644000175000017500000000631610576163416017320 0ustar andrewmandrewm%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Copyright 2001 by Object Craft P/L, Melbourne, Australia. % LICENCE - see LICENCE file distributed with this software for details. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{Installation\label{inst-guide}} \section{Prerequisites\label{inst-pre}} \begin{itemize} \item Python 2.1 or later. \item \LaTeX Required if you want to build the PDF or PostScript documentation. \item latex2html Required if you want to build the HTML documentation. \item Python 2.3 source Required if you want to build the documentation. The \texttt{mkhowto} program and \LaTeX{} styles included in the distribution are used to build the documentation. \item \texttt{dia} The diagrams in the documentation are edited and converted to EPS using \texttt{dia} version 0.88 (later versions do not render fonts correctly). \end{itemize} \section{Installing\label{inst-install}} Unpack the package archive to extract the package source. This will create an \texttt{albatross-1.36} subdirectory. \begin{verbatim} tar xzf albatross-1.36.tar.gz \end{verbatim} Inside the \texttt{albatross-1.36} directory are a number of directories. \begin{description} \item{\texttt{albatross}} Contains the Python code for the package. \item{\texttt{doc}} Contains the documentation source. \item{\texttt{samples}} Contains all of the sample programs discussed in this document. \item{\texttt{session-server}} Contains a simple session server which works with the Albatross server-side session mixin classes. \item{\texttt{test}} Contains the unit tests. \end{description} The \module{Albatross} package uses the \module{distutils} package so all you need to do is type the following command as root from the top level directory: \begin{verbatim} python setup.py install \end{verbatim} If you have problems with this step, make sure that you contact the package author so that the installation process can be made more robust for other people. If you already have a copy of Albatross installed and wish to test the new release before installing it globally then you can install an application private copy. If your application is installed in \texttt{/path/to/proj} then the following command will install a copy of Albatross that is only visible to that application: \begin{verbatim} python setup.py install --install-lib /path/to/proj --install-scripts /path/to/proj \end{verbatim} If you wish to build the documentation you will almost certainly need to change first line of the \texttt{Makefile} in the documentation directory. This points to where you have unpacked the Python distribution so the \texttt{Makefile} can find the \texttt{mkhowto} program. \begin{verbatim} cd doc make pdf make html \end{verbatim} \section{Testing\label{inst-test}} There are a number of unit tests included in the distribution. These have been developed using the \module{unittest} package which is standard in Python 2.1 and later. Run the following commands to perform the unit tests: \begin{verbatim} cd test make \end{verbatim} If you want to do more than run the unit tests, you can start to work your way through the samples which are presented in this document. albatross-1.36/doc/sessionappcontext.dia0000644000175000017500000000764510135141327020337 0ustar andrewmandrewm]s~߿a_6Llݹδa PWXl'oMH1d>sG*C&]1?|wg.ɲ䯇"4 I$Ysr?}v$%Q$4h'KП\Yu;Wp!{??WnV?ȜN%%_k2u@n@L5VAIA?ɿoڬ[?lH7^7B!ђ9rԽٍ054Mu3=ñrOʋv+w+Nv+N d$ "BpJLj$cZ]NTдtm/ØĴ'f,đORf]sɼKyEA/ŐXC W-B6t5wN篗ԆBljQ ?RaD%ɮ-B%'Su-qsj[T|.J(;Dꑷ&ꅠNԶUEЉS/hoݹ'IP*t.zܫ_Y+J1ΏK間`spg&כ%㫇R" K0Z%v""j_l!B VE#p*BF1G1|Gb745ߐDQHl}5lF]G*s[jlkSzco{ {wo/q~ԭc:Y82iuq J8Y'r޺ʋoKڛҞ-ʉ򲘿=Kgzz\u1'ԴHZ`aGM>B2v;JbṡB!eH!@ "H@:A2$mnG-e9Ղu:7q:Ja Fĺ7mVOԿmԚ):fM^CwLm-n- X 1 xdNeZ_Uz(qR~eW$ +<|9yվ֟2~;]ͺN%gMPB$ɡf ƚ^*dp Va6>JFsj켮&#'`_WzL$Qݝ97ѳ W'swނ4QVmZcZ#3kP^+YVgRE]ˆɅBװqP&5Jk=ӪC׉t:@וBBjfh\%!.i}a˸S6#t+`325Ko Gu1] ̰^K  l^ ֤N*6B`~yT?Sh@<.!@  )O,O:ԩZ&mY#Uy)iK,ޑήҙ;ٿ.SGRecag϶$¸vvf͸ݯq"c`ݏajfs*j4yǧI愜b$|:&03Ϯfॖw]Jdl^fU t2&ꬶS KOR ú7 mEj^)kZm5s+x=Gr=!g9{Cr=G>=HPs.:%+}xi曮R.8d#X*V9 | 3/6>sN#gI\)@w;\+;I,a(H.6E0A-g{B|a`{X.pĠ˳w?Dd L1b0`]1qAm}@y /{aq)#"hJitqHh|Aغߚ\IE2))ַܓ$A(Cfwߺ Jjs'\YUMŒq~\,}U9B. wf"`{Y2z//RXhiJYE,":VeWK!s+ACZi\RATHVV|C GM]װNNv1UĽ֦Thq{l7]9V]ǻю.=9atB,}࢙s1ŪUSVul$q"INp7LB!ziA N'B|߁A߁Κ^w;w]ZyàP˳6Ö+ug~va`]3w߁w;; w;|{?|&7 ]{BnBӾ5;9&V*ĺ:DXkJgQBzj2(C2(C2(C2(C2(C2od?*ޝOݒR8D΁!`b|Ø+>pJz@z(]ֻjH!=yH_pDRpF#0)}`0q3؂_|yC0Ⱥ.~71Xc뽗T.0"R< DJ]D"O{'a|c}o@z =Xε"JnwYxu7#"#Wa( "gOE`0d#cy~I4lFqƃooWj><NHS?dDiIzH6MV̓j|TEO?g4eu'Z*$ 5 U9ڄ a=˲p~۵]/npZqiũӊ R/UDq(E4<\NPoMjv2;[~pyA[Vt+t%>~G*nF-fw Xaf4CP; ʎ68/;>9nV Þt?`&P[☒î]q^GK]Tx WW!G|y}CK(ҴBB4 7jLZ\$~AVh[N-xU_  WE4 \c]-5u5L-вsLʱ*Z% [X!{ ׯ d0ʅu wAT-q7MI(AL#1ͤqVoϱ}Wl޽.$ILSibMEImlE-RC+M_P!FĊ6}֥v57ίkjP4>Pm%,> '!Ykbtz?k܊qNo8DHiN4 M%@?)CΠ;)$q8* ('+Rog_ÿd Ϥw3}Iz.a+j>;tbwVQ7s]^ږc6?"d>^}j\a,^n-1nwׇ;aM׉d<^ \Rߒ_WWb,VjbyV-j<nZ[+.l֎aO1EYݺ[Vi~ B9IHo%V[Io%V[Io%7yAd+Wl[a`{zG_z?_F3yyWŁOiUOUՓ1 gV0ʒu2V5:rby ]W٨iL~ kU̕(Q}Nb*ws[xww%;v^] qk*V;xERV5t^lMYvʲSe,;e)NYvʲSeOfI !.?F2ҙ Aٷ~L7 b{y@D  4C!g]*u+ٕg,CD "yd&$3W9oOb֬l0Üy:{6zuWUi1|*Bu{&}^|frL@v [p溶zY(Z3*iL>SGUcjmGt~X "叔?RH#叔?RH#叔?RnʟQyo80Cɥ {1`wZ||uF8qjf1ιBƵڴL%Uqz(x@AJ`;D ZU5۪3[61k3Qk)@mt t gQV ~V$&@b $&@b $&@b EEv _3.f\[( =@ζf c[-4maOx< xzzgk}bK7?dbT T *Aŀ`x= | ȷCh1JA>w!eJ2t*e0*A٥4[y`9-[s*Kڥ'I 8(@q3P g8(@qY1^Rq5h4 %;w<nsKG_&&nP{qV5A@ ~y<*ؔgDܾ4c9=d 2m4{y^Ѻ;i< E#^*$x ⽇"<0EܝizzzJoBHGS}!3s`ccxgx<0hQW3 j3 ejl6NrV=:V͝4_.}4\YѭFzʛhVе?4W<Ҡ4A/ ziK^Ҡ4襁+63W++VVbi<0ïxҀy`y`~TOf蟑>}?H֦albatross-1.36/doc/application.dia0000644000175000017500000000335110135141327017037 0ustar andrewmandrewm]r6S0]ݙL{F5zg 1Ca&?;,s,OTLDCc;VF(Z姾㘑'$l3">ZSӓh!m[s6UpppLyZ#!8%U˄|,:G3p7aZMW4iyx=@U$7ׁ' e?2F=tc̈́_oN?q4, ƸO{W_0feJ"Mb^ϗ;crdNep qг^}{֯4p?Dbm܈"l| jJry,W%XvANҀ уA z=^wQ_0,1,_KI { P=TՃՇ øĸ| =佅Q(V>C k\}239ȝq§cWvvS3'fOHrPn"%gp{n9ovuHA>?C Ll}Lm>A8_Ag@zg@zg@zg@zg@zg@z,bˬ?RzD`04;~ g^;&銋̧q7TՃ_.Dh湆y e} wtnV:/c~*X[G==.ST)>@ x<>@~hSBxP<(h̓AyмylnIj8BiTՃA~\EDrj#Ԥccc~9fb6F x<3 ŃjK"h4̓Au}9's*ﯖOO`f\f*albatross-1.36/doc/simplesessfileapp.dia0000644000175000017500000000602610135141327020266 0ustar andrewmandrewm]]s6_Y vӤݙλG˕$ 16&>pP%$ yJ!BVeCh]TVD'=H4g zM]jwtVRwLGl //ϫRuzX$\QӸP2R.n?~~5iƲM -&TxT& E\˲`8n\V<8JR"BF3J$bۺe_Yg^;ѣ;&ti;t-$n_+%JjyC5 `i0}P<აmj=Ѫo'5gìRa1J%;jb]^n߁F߹NW[/xA/FA fwﯯ>4IK%D |xfzotM  rzW"`I H~B̈́s>qr9դM@Ȩ+oC[G]ˮ!khʅzV^j;!ΒlC97SLLdLlmzNX4(ƽ QRAҸ(umܘ٩Um=T_|56r'r1NO?>ciרVVzl(x>LbO>DBoBN,{uG'<޴'"ILn%jZUE3YMܪu҄XӉZqF ՋY ETn{PGIة5V_̽I^Yx:v5:r6֥پ/*ek: ;>\W_r}? 8r5$Ed^.-iK({.Gc&/ΖhOʁOCS ,$mPBHѧCPy}7/G2S]eϜM*_F uva48hI٢dՌ53Tޘu7v2IAӍɄ8u!%F6:ͦAMWM4n&W#Xh`Xh`Xh`V U':: |$Lo@@1'D ^/ț#(ʃ\AJ3J3c+'[1+1+VnB(bPĠA"{eʻ)HttUJ # T,L\wMʶQ񮊐j]8c%lc[+~g)Fna,דYhAZ+6T )Vdz{ڜ;ozN H0"OZc NJ7ZOoڽ|9X},X\kP vv=d &K fq -;3=}窝ii+{%ݣ^o[_h0ֿ9g2+x+x+x+x+x+xLH0!] i@ Vt x$ 8w F >eݺ7{eMz x֦y+ VN65KӥC6?}jT8N5pS jT8g8c{يJzÏVq"ޒT{=BKa7 @ (وg+ ` 'l58IIyP A  t;O˘$|߁ww@(Ĭ 1K7(bPŠA*{ c`>0̷4d w!B3xuC3x9s vL=V+f!raaaaaaǫ8^%eir9`ie`9OE)`q7Xփ\>g!#g0|7ä<…Ș hpe 1(c]=y^(OXwьx`<0{1&L1C!И1Cc~s̃ 0fO ;kK#p8Ff_Y:XO4&0m\4Vô3 s}we[ĨWCH5ڭRLd+k~o\Pf%DS%H["o(ӈ"5FdIk)%:7^a>lx֜?k>[/duKcJee,;dm8P O\@suRU mo9Wđ`'v3%, "Xy<V]qsO=kܳ=?`[ h 8;S&o?FZ[G)albatross-1.36/doc/thanks.tex0000644000175000017500000000101407701474512016073 0ustar andrewmandrewm\chapter*{Thank you\label{thanks}} Object Craft wish to thank the following people. \begin{description} \item[Tim Churches] For all of the time he has spent reviewing the documentation and pointing out errors. \item[Lewis Bergman] For a constant stream of documentation fixes and suggestions \item[Gregory Bond] For maintaining the FreeBSD port. \item[Matt Goodall] For the FastCGI and (standard Python) BaseHTTPServer deployment code. \item[Fabian Fagerholm] For building the Debian package. \end{description} albatross-1.36/doc/appuser.tex0000644000175000017500000016653210577416325016306 0ustar andrewmandrewm%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Copyright 2001 by Object Craft P/L, Melbourne, Australia. % LICENCE - see LICENCE file distributed with this software for details. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{Guide to Building Applications\label{app-guide}} Roughly speaking, every page or document sent from a web server to a browser is the result of the same processing sequence. For our purposes a document is one page in an application. \begin{enumerate} \item The browser connects to the server and requests a page. \item The server decodes the browser request and processes it. This can cause all manner of subsidiary application processing to occur. \item The server sends the result of processing back to the browser as the next page in the application. \end{enumerate} The essentially stateless nature of the web presents problems for the application developer who wishes to retain state between different pages in their application. From the point of view of the user, the state of the application at the server is represented by the page that they see in their browser window(s). When they enter values and press submit buttons on their page they (quite reasonably) expect the application on the web server to process their request and return the next page in the application. The point of view of the web application developer is very different. At the server side the application must be able to receive a request from one browser, process it and send the next application page back to that browser. Before seeing the next request from the same browser, another browser may request a different page from the application. The web application even has to deal with multiple browsers simultaneously accessing different pages in the application. All of this while maintaining the illusion for the end user that they are running their own copy of the application. Even when you only have a single machine running Apache, there are multiple Apache processes on that machine that are receiving and processing browser requests. This means that there is no guarantee that the same process that received the last request from a particular browser will receive the next request from that browser. If your application requires some state to be retained between pages, there has to be a way for that state to migrate between different Apache processes. If you are using more than one machine to process requests, then you need some way for the application state to move between machines. There are essentially three approaches to solving the state propagation problem. \begin{enumerate} \item Deploy a stateless application. Only the most trivial applications do not require state to be retained across browser requests. \item Get the browser to store the application state. This can be done by embedding information in URL's and hidden fields in forms. When the browser requests the next page the application restores state by extracting it from the hidden field values sent back by the browser. Browser cookies can also be used to store some types of application state. \item Get the browser to store a session identifier in either URL's, hidden fields, or a cookie. When the browser requests the next page the application uses the browser supplied session identifier to locate the state in a server-side session database of some description. \end{enumerate} Assuming that your application is non-trivial, the second approach (state held by the browser) is the easiest to implement. If you are building a financial application, you will probably feel more comfortable with the third approach. This has the advantage of hiding implementation details of your application from prying eyes. All three approaches are supported (in one way or another) by Albatross. In all Albatross applications there are two important objects that determine how browser requests are processed; the application object, and the execution context object. The application is a potentially long lived object that provides generic services for multiple browser requests. A new execution context is created to process each browser request. One of the things that the application and execution context do is cooperate to provide session functionality. The execution context is the object where session data is created and manipulated, but the application is usually responsible to loading and saving the session. This allows the application to maintain a long lived connection with a server if necessary. \section{Albatross Application Model\label{app-model}} In the Albatross world view explained in the previous section, all web applications follow the same processing sequence to handle browser requests. When processing a browser request to generate a response the processing (in most cases) flows according to figure \ref{fig-dataflow}. \begin{figure}[h] \begin{center} \includegraphics{dataflow} \caption{Request Processing Dataflow\label{fig-dataflow}} \end{center} \end{figure} The processing steps are: \begin{enumerate} \item Capture the browser request in a \class{Request} object. \item Pass the \class{Request} object to the \method{run()} method of the application object. \item Application locates the Python code for processing the browser request. \item Page processing code runs one or more Albatross templates. \item Templates contain either standard Albatross tags or application defined extension tags. \item As tags are converted to HTML a stream of content fragments is sent to the execution context. \item When the execution context content is flushed all of the fragments are joined together. \item Joined flushed content is sent to the \class{Request} object \method{write_content()} method. \item Application response is returned to the browser. \end{enumerate} In the Albatross code the processing is driven by the \method{run()} method of the \class{Application} class in the \module{app} module. It is instructive to look at the \emph{exact} code from Albatross that implements the processing sequence. \label{app-proc-seq} \begin{verbatim} ctx = self.create_context() ctx.set_request(req) self.load_session(ctx) self.load_page(ctx) if self.validate_request(ctx): self.merge_request(ctx) self.process_request(ctx) self.display_response(ctx) self.save_session(ctx) ctx.flush_content() \end{verbatim} The code is contained within a \code{try}/\code{except} block to allow the application to trap and handle exceptions. The \class{Application} class assumes very little about the implementation of each of these steps. The detail of the processing stages is defined by a collection of mixin classes. A combination of the \class{Application} class and a selection of the mixin classes is used to construct your application class and execution context classes. There are a number of prefabricated applications and execution contexts, see chapter \ref{pack-overview}. This mix and match approach to building the application and execution context classes provides a great deal of flexibility. Albatross is an application toolkit, not a deployment platform. You are encouraged to look at the code and develop your own mixin classes to suit your deployment requirements. One of the primary goals of Albatross is to keep the toolkit line count low. This reduces the amount of time you need to spend before you can make your own custom extensions to the toolkit. In the previous chapter we talked about the importance of separating the presentation layer of the application from the implementation as shown in figure \ref{fig-presimp}. Albatross HTML templates provide a fairly powerful tool for achieving that separation. \begin{figure}[h] \begin{center} \includegraphics{twolayer} \caption{Separation of Presentation/Implementation\label{fig-presimp}} \end{center} \end{figure} The presentation layer consists of a collection of template files that contain the logic required to display data from the objects contained in the implementation layer. In Albatross, the line between the two layers is the execution context. To make objects available to the presentation layer the application places references to those objects into the local or global namespace of the execution context. The local namespace is populated in application code like this: \begin{verbatim} ctx.locals.mbox = Mbox(ctx.locals.username, ctx.locals.passwd) ctx.locals.msg = ctx.locals.mbox[int(ctx.locals.msgnum) - 1] \end{verbatim} To execute Python expressions inside the template files, the execution context uses the following Python code from the \class{NamespaceMixin} class: \begin{verbatim} def eval_expr(self, expr): self.locals.__ctx__ = self try: return eval(expr, self.__globals, self.locals.__dict__) finally: del self.locals.__ctx__ \end{verbatim} Whenever application code calls the \method{run_template()} or \method{run_template_once()} methods of the \class{NamespaceMixin} class Albatross sets the global namespace (via \method{set_globals()}) for expression evaluation (in \member{self.__globals}) to the globals of the function that called \method{run_template()} or \method{run_template_once()}. Not many applications are output only, most accept browser input. The Albatross application object merges the browser request into the execution context in the \method{merge_request()} method. Referring back to the application processing sequence also note that the application object displays the result of processing the browser request via the execution context. With this in mind figure \ref{fig-presimp} becomes figure \ref{fig-presimpexec}. \begin{figure}[h] \begin{center} \includegraphics{twolayerctx} \caption{Presentation/Implementation and Execution Context\label{fig-presimpexec}} \end{center} \end{figure} The only thing missing is the application glue that processes the browser requests, places application objects into the execution context, and directs the execution of template files. The application model built into Albatross is intended to facilitate the use of a model-view-controller like approach (see figure \ref{fig-mvc}) to constructing your application. There are many excellent descriptions of the model-view-controller design pattern which can be found by searching for ``model view controller'' on \url{http://www.google.com/}. \begin{figure}[h] \begin{center} \includegraphics{mvc} \caption{Albatross model-view-controller\label{fig-mvc}} \end{center} \end{figure} The \emph{user} invokes application functions via the \emph{controller} through the \emph{view}. The \emph{controller} contains logic to direct the application functionality contained within the \emph{model}. All of the real application functionality is in the \emph{model}, not the \emph{controller}. Changes to the application \emph{model} are then propagated to the \emph{view} via the \emph{controller}. In Albatross terms, the implementation layer is the \emph{model} and the presentation layer is the \emph{view}. The application glue plays the role of the \emph{controller}. By divorcing all application logic from the \emph{view} and \emph{controller} you are able to construct unit test suites for your application functionality using the Python \module{unittest} module. Albatross uses an approach inspired by the traditional model-view-controller design pattern. So now we can draw the final version of the diagram which shows how Albatross applications process browser requests in figure \ref{fig-appmodel}. \begin{figure}[h] \begin{center} \includegraphics{albmvc} \caption{Albatross Application Model\label{fig-appmodel}} \end{center} \end{figure} As you can see the execution context is central to all of the Albatross processing. It is worth revisiting the application processing sequence set out on page \pageref{app-proc-seq} at the start of this section to see how the application draws all of the elements together. During step four (page processing) the Albatross application object will call on your application code to process the browser request. There are a number of different ways in that your application code can be ``attached'' to the Albatross application object. The \class{PageObjectMixin} application mixin class requires that you implement application functionality in ``page objects'' and define methods that can be called by the toolkit. As an example, here is the page object for the \texttt{'login'} page of the \texttt{popview} sample application. \begin{verbatim} class LoginPage: name = 'login' def page_process(self, ctx): if ctx.req_equals('login'): if ctx.locals.username and ctx.locals.passwd: try: ctx.open_mbox() ctx.add_session_vars('username', 'passwd') except poplib.error_proto: return ctx.set_page('list') def page_display(self, ctx): ctx.run_template('login.html') \end{verbatim} When the toolkit needs to process the browser request it calls the \method{page_process()} method of the current page object. As you can see, the code determines which request was made by the browser, instantiates the application objects required to service the browser request, then directs the context to move to a new page via the \method{set_page()} method. When Albatross is ready to display the browser response it calls the \method{page_display()} method of the current page object. In the code above, \method{set_page()} is only called if the mailbox is opened successfully. This means that a failed login will result in the login page being displayed again. Note that when you change pages the object that generates the HTML will be a different object to that that processed the browser request. To let you get a foothold on the toolkit application functionality we will work through another variant of the \texttt{form} application. \section{Using Albatross Input Tags (Again)\label{tug-form4}} In the previous chapter we demonstrated the use of Albatross input tags to transfer values from the execution context into HTML \texttt{} tags, and from the browser request back into the execution context. In this section we present the same process using an Albatross application object. The sample program from this section is supplied in the \texttt{samples/form4} directory and can be installed in your web server \texttt{cgi-bin} directory by running the following commands. \begin{verbatim} cd samples/form4 python install.py \end{verbatim} The \texttt{form.html} template file used by the application follows. \verbatiminput{../samples/form4/form.html} The most important new features in the template file are the use of the \texttt{} tag, and the \texttt{list} attribute in the \texttt{} tag. Most execution contexts created by application objects inherit from the \class{NameRecorderMixin}. The \class{NameRecorderMixin} records the name, type and multiple value disposition of each input tag in a form in a cryptographically signed hidden field named \texttt{__albform__}. This mechanism prevents clients from being able to merge arbitrary data into the local namespace, as well as providing additional information to make the merging process more reliable. The recording process requires that all \texttt{} tags be enclosed by an \texttt{} tag. When the resulting form is submitted, the contents of the \texttt{__albform__} field controls merging of form fields into \code{ctx.locals}. Only fields tracked by \texttt{__albform__} will be merged. Consequently, if submission occurs via a GET request without an \texttt{__albform__} field (for example, as a result of the user following an \texttt{}), the application must explicitly request relevent fields be merged via the \texttt{merge_vars(...)} method. \footnote{Prior to Albatross version 1.36, in the absence of an \texttt{__albform__} field, all request fields would be merged.} Any input field with the \texttt{list} attribute will always receive a list value from a POST browser request regardless of how many values (including none) were sent by the browser. An exception will be raised if you specify multiple input tags with the same name in a form and do not include the \texttt{list} attribute. The input tag types \texttt{radio}, \texttt{image}, and \texttt{submit} can only have a single value, even if multiple inputs of the same name appear in a form, and the \texttt{list} attribute should not be specified on these. The \texttt{form.py} program is show below. \verbatiminput{../samples/form4/form.py} You can run the program by pointing your browser at \mbox{\url{\cgibindir/alsamp/form4/form.py}}. Notice that the browser request is automatically merged into the local namespace and then extracted by the template when generating the HTML response. The program uses the \class{SimpleApp} application class. \class{SimpleApp} uses an object to define each page served by the application. Each of the page objects must be registered with the application via the \method{register_page()} method. When the application enters a new page \class{SimpleApp} calls the \method{page_enter()} method of the page object to allow the application to initialise execution context values. In the above program the \method{page_enter()} method initialises all values used by the HTML form to \code{None}, initialises the variable \var{num} to \code{0} and places it into the session. As shown in the application processing sequence on page \pageref{app-proc-seq}, the first step in handling a browser request is to create an execution context. The \class{SimpleApp} class uses instances of the \class{SimpleAppContext} class which inherits from \class{HiddenFieldSessionMixin}. The \class{HiddenFieldSessionMixin} class stores session data in a hidden field named \texttt{__albstate__} at the end of each form. When an Albatross application needs to display the result of processing a request it calls the \method{page_display()} method of the current page. In the above program this method increments \var{num} and then runs the \texttt{form.html} template. It is important to note that any changes to session values after executing a template will be lost as the session state is saved in the HTML produced by the template. It is worth explaining again that the program does not perform any request merging --- this is all done automatically by the Albatross application and execution context objects. \section{The Popview Application\label{app-popview1}} In this section we will develop a simple application that allows a user to log onto a POP server to view the contents of their mailbox. Python provides the \module{poplib} module which provides a nice interface to the POP3 protocol. The complete sample program is contained in the \texttt{samples/popview1} directory. Use the \texttt{install.py} script to install the sample. \begin{verbatim} cd samples/popview1 python install.py \end{verbatim} First of all let's create the \emph{model} components of the application. This consists of some classes to simplify access to a user mailbox. Create a module called \texttt{popviewlib.py} and start with the \class{Mbox} class. \begin{verbatim} import string import poplib pophost = 'pop' class Mbox: def __init__(self, name, passwd): self.mbox = poplib.POP3(pophost) self.mbox.user(name) self.mbox.pass_(passwd) def __getitem__(self, i): try: return Msg(self.mbox, i + 1) except poplib.error_proto: raise IndexError \end{verbatim} The important feature of our \class{Mbox} class is that it implements the Python sequence protocol to retrieve messages. This allows us to iterate over the mailbox using the \texttt{} tag in templates. When there is an attempt to retrieve a message number which does not exist in the mailbox, a \module{poplib.error_proto} exception will be raised. We transform this exception into an \exception{IndexError} exception to signal the end of the sequence. The sequence protocol is just one of the truly excellent Python features which allows your application objects to become first class citizens. Next we need to implement a \class{Msg} class to access the header and body of each message. \begin{verbatim} class Msg: def __init__(self, mbox, msgnum): self.mbox = mbox self.msgnum = msgnum self.read_headers() def read_headers(self): res = self.mbox.top(self.msgnum, 0) hdrs = Headers() hdr = None for line in res[1]: if line and line[0] in string.whitespace: hdr = hdr + '\n' + line else: hdrs.append(hdr) hdr = line hdrs.append(hdr) self.hdrs = hdrs return hdrs def read_body(self): res = self.mbox.retr(self.msgnum) lines = res[1] for i in range(len(lines)): if not lines[i]: break self.body = string.join(lines[i:], '\n') return self.body \end{verbatim} Note that we retrieve the message headers in the constructor to ensure that the creation of the \class{Msg} object will fail if there is no such message in the mailbox. The \class{Mbox} class uses the exception raised by the \module{poplib} module to detect when a non-existent message is referenced. The \module{poplib} module returns the message headers as a flat list of text lines. It is up to the \module{poplib} user to process those lines and impose a higher level structure upon them. The \method{read_headers()} method attaches header continuation lines to the corresponding header line before passing each complete header to a \class{Headers} object. The \method{read_body()} method retrieves the message body lines from the POP server and combines them into a single string. We are going to need to display message headers by name so our \class{Headers} class implements the dictionary protocol. \begin{verbatim} class Headers: def __init__(self): self.hdrs = {} def append(self, header): if not header: return parts = string.split(header, ': ', 1) name = string.capitalize(parts[0]) if len(parts) > 1: value = parts[1] else: value = '' curr = self.hdrs.get(name) if not curr: self.hdrs[name] = value return if type(curr) is type(''): curr = self.hdrs[name] = [curr] curr.append(value) def __getitem__(self, name): return self.hdrs.get(string.capitalize(name), '') \end{verbatim} Instead of raising a \exception{KeyError} for undefined headers, the \method{__getitem__()} method returns the empty string. This allows us to test the presence of headers in template files without being exposed to exception handling. Lets take all of these classes for a spin in the Python interpreter. \begin{verbatim} >>> import popviewlib >>> mbox = popviewlib.Mbox('djc', '***') >>> msg = mbox[0] >>> msg.hdrs['From'] 'Owen Taylor ' >>> print msg.read_body() Daniel Egger writes: > Am 05 Aug 2001 12:00:15 -0400 schrieb Alex Larsson: > [snip] \end{verbatim} Next we will create the application components (the \emph{controller}) in \texttt{popview.py}. Albatross has a prepackaged application object which you can use for small applications; the \class{SimpleApp} class. In anything but the most trivial applications it is probably a good idea to draw a site map like figure \ref{fig-popview-sitemap} to help visualise the application. Our application will contain three pages; login, message list, and message detail. \begin{figure}[h] \begin{center} \includegraphics{pagemap} \caption{Popview Site Map\label{fig-popview-sitemap}} \end{center} \end{figure} The \class{SimpleApp} class inherits from the \class{PageObjectMixin} class which requires that the application define an object for each page in the application. Albatross stores the current application page identifier in the local namespace of the execution context as \member{__page__}. The value is automatically created and placed into the session. When the current page changes, the Albatross application object calls the \method{page_enter()} function/method of the new page object. The \method{page_process()} method is called to process a browser request, and finally \method{page_display()} is called to generate the application response. The \class{SimpleApp} class creates \class{SimpleAppContext} execution context objects. By subclassing \class{SimpleApp} and overriding \method{create_context()} we can define our own execution context class. The prologue of the application imports the required application and execution context classes from Albatross and the \class{Request} class from the deployment module. \begin{verbatim} #!/usr/bin/python from albatross import SimpleApp, SimpleAppContext from albatross.cgiapp import Request import poplib import popviewlib \end{verbatim} Next in the file is the class for the login page. Note that we only implement glue (or \emph{controller}) logic in the page objects. Each time a new page is served we will need to open the mail box and retrieve the relevant data. That means that the username and password will need to be stored in some type of session data storage. \begin{verbatim} class LoginPage: name = 'login' def page_process(self, ctx): if ctx.req_equals('login'): if ctx.locals.username and ctx.locals.passwd: try: ctx.open_mbox() ctx.add_session_vars('username', 'passwd') except poplib.error_proto: return ctx.set_page('list') def page_display(self, ctx): ctx.run_template('login.html') \end{verbatim} The \method{req_equals()} method of the execution context looks inside the browser request for a field with the specified name. It returns a TRUE value if such a field exists and it has a value not equal to \code{None}. The test above will detect when a user presses the submit button named \texttt{'login'} on the \texttt{login.html} page. Next, here is the page object for displaying the list of messages in the mailbox. \begin{verbatim} class ListPage: name = 'list' def page_process(self, ctx): if ctx.req_equals('detail'): ctx.set_page('detail') def page_display(self, ctx): ctx.open_mbox() ctx.run_template('list.html') \end{verbatim} The ``detail'' page displays the message detail. \begin{verbatim} class DetailPage: name = 'detail' def page_process(self, ctx): if ctx.req_equals('list'): ctx.set_page('list') def page_display(self, ctx): ctx.open_mbox() ctx.read_msg() ctx.run_template('detail.html') \end{verbatim} And finally we define the application class and instantiate the application object. Note that we have subclassed \class{SimpleApp} to create our own application class. This allows us to implement our own application level functionality as required. \begin{verbatim} class AppContext(SimpleAppContext): def open_mbox(self): if hasattr(self.locals, 'mbox'): return self.locals.mbox = popviewlib.Mbox(self.locals.username, self.locals.passwd) def read_msg(self): if hasattr(self.locals, 'msg'): return self.locals.msg = self.locals.mbox[int(self.locals.msgnum) - 1] self.locals.msg.read_body() class App(SimpleApp): def __init__(self): SimpleApp.__init__(self, base_url='popview.py', template_path='.', start_page='login', secret='-=-secret-=-') for page_class in (LoginPage, ListPage, DetailPage): self.register_page(page_class.name, page_class()) def create_context(self): return AppContext(self) app = App() if __name__ == '__main__': app.run(Request()) \end{verbatim} The \var{base_url} argument to the application object constructor will be placed into the \texttt{action} attribute of all forms produced by the \texttt{} tag. It will also form the left hand side of all hrefs produced by the \texttt{} tag. The \var{template_path} argument is a relative path to the directory that contains the application template files. The \var{start_page} argument is the name of the application start page. When a browser starts a new session with the application it will be served the application start page. We have also created our own execution context to provide some application functionality as execution context methods. With the \emph{model} and \emph{controller} components in place we can now move onto the template files that comprise the \emph{view} components of the application. First let's look at the \texttt{login.html} page. \verbatiminput{../samples/popview1/login.html} When you look at the HTML produced by the application you will notice two extra \texttt{} tags have been generated at the bottom of the form. They are displayed below (reformatted to fit on the page). \label{alb-hidden} \begin{verbatim} \end{verbatim} If we fire up the Python interpreter we can have a look at what these fields contain. \begin{verbatim} >>> import base64,zlib,cPickle >>> s = "eJzTDJeu3P90rZC6dde04xUhHL\n" + \ ... "WFjBqhHKXFqUV5ibmphUzeDKFsBYnFxeUphcxANmtOfnpmXiGLN0OpHgB7UBOp\n" >>> cPickle.loads(zlib.decompress(base64.decodestring(s))[16:]) {'username': 0, 'passwd': 0, 'login': 0} >>> s = "eJzT2sr5Jezh942TUrMty6q1j\n" + \ ... "WsLGUM54uMLEtNT4+MLmUJZc/LTM/MKmYv1AH8XEAY=\n" >>> cPickle.loads(zlib.decompress(base64.decodestring(s))[16:]) {'__page__': 'login'} \end{verbatim} The first string contains a dictionary that defines the name and type of the input fields that were present in the form. This is placed into the form by the \class{NameRecorderMixin} class which is subclassed by \class{SimpleAppContext}. If you look at the definition of \class{SimpleAppContext} you will notice the following definition at the start of the class. \begin{verbatim} NORMAL = 0 MULTI = 1 MULTISINGLE = 2 FILE = 3 \end{verbatim} The value \code{0} for each field in the dictionary above corresponds to field type \code{NORMAL}. When merging the browser request into the execution context the dictionary of field names is used to assign the value \code{None} to any \code{NORMAL} or \code{FILE} fields, or \code{[]} to any \code{LIST} fields that were left empty by the user. This is useful because it lets us write application code which can ignore the fact that fields left empty by the user will not be sent by the browser when the form is submitted. The second string contains all of the session values for the application. In the application start page the only session variable that exists is the \member{__page__} variable. The \class{HiddenFieldSessionMixin} places this field in the form when the template is executed and pulls the field value back out of the browser request into the execution context when the session is loaded. The first 20 bytes of the decompressed string is an HMAC-SHA1 cryptographic signature. This was generated by combining the application secret (passed as the \var{secret} argument to the application object) with the pickle string with a cryptographic hash. When the field is sent back to the application the signing process is repeated. The pickle is only loaded if the sign sent by the browser and the regenerated signature are the same. Since the \texttt{popview} application has been provided for example purposes you will probably forgive the usage of the \class{HiddenFieldSessionMixin} class to propagate session state. In a real application that placed usernames and passwords in the session you would probably do something to protect these values from prying eyes. Now let's look at the \texttt{list.html} template file. Note that the use of the \texttt{} tag causes the HTML output to be streamed to the browser. Use of this tag can give your application a much more responsive feel when generating pages that involve lengthy processing. A slightly obscure feature of the page is the use of a separate form surrounding the \texttt{} field used to select each message. An unfortunate limitation of the HTML \texttt{} tag is that you cannot associate a value with the field because the browser returns the coordinates where the user pressed the mouse inside the image. In order to associate the message number with the image button we place the message number in a separate hidden \texttt{} field and group the two fields using a form. You have to be careful creating a large number of forms on the page because each of these forms will also contain the \texttt{__albform__} and \texttt{__albstate__} hidden fields. If you have a lot of data in your session the size of the \texttt{__albstate__} field will cause the size of the generated HTML to explode. \verbatiminput{../samples/popview1/list.html} Finally, here is the message detail page \texttt{detail.html}. \verbatiminput{../samples/popview1/detail.html} \section{Adding Pagination Support to Popview\label{app-popview2}} If the previous section we constructed a simple application for viewing the contents of a mailbox via the \module{poplib} module. One problem with the application is that there is no limit to the number of messages that will be displayed. In this section we will build pagination support into the message list page. We will modify the application to display 15 messages on each page and will add buttons to navigate to the next and previous pages. The \texttt{pagesize} attribute of the \texttt{} tag provides automatic pagination support for displaying sequences. The only extra code that we need to add is a \texttt{__len__} method to the \class{Mbox} class which returns the number of messages in the mailbox. This \texttt{__len__} method is needed to allow the template file to test whether or not to display a next page control. The complete sample program is contained in the \texttt{samples/popview2} directory. Use the \texttt{install.py} script to install the sample. \begin{verbatim} cd samples/popview2 python install.py \end{verbatim} Add a \texttt{__len__} method to the \class{Mbox} class in \texttt{popviewlib.py}. \begin{verbatim} class Mbox: def __init__(self, name, passwd): self.mbox = poplib.POP3(pophost) self.mbox.user(name) self.mbox.pass_(passwd) def __getitem__(self, i): try: return Msg(self.mbox, i + 1) except poplib.error_proto: raise IndexError def __len__(self): len, size = self.mbox.stat() return len \end{verbatim} Now we modify the template file \texttt{list.html}. \verbatiminput{../samples/popview2/list.html} The \texttt{} tag just below the \texttt{} tag contains two new attributes; \texttt{pagesize} and \texttt{prepare}. The \texttt{pagesize} turns on pagination for the \texttt{} \class{ListIterator} object and defines the size of each page. In order to remember the current top of page between pages, the tag places the iterator into the session. When saving the iterator, only the top of page and pagesize are retained. The \texttt{prepare} attribute instructs the \texttt{} tag to perform all tasks except actually display the content of the sequence. This allows us to place pagination controls before the actual display of the list. \section{Adding Server-Side Session Support to Popview\label{app-popview3}} So far we have been saving all application state at the browser inside hidden fields. Sometimes it is preferable to retain state at the server side. Albatross includes support for server-side sessions in the \class{SessionServerContextMixin} and \class{SessionServerAppMixin} classes. In this section we will modify the \texttt{popview.py} program to use server-side sessions. The \class{SessionServerAppMixin} class uses a socket to communicate with the \texttt{session-server/al-session-daemon} session server. You will need to start this program before using the new \code{popview} application. In previous versions of the program we were careful to place all user response into forms. This allowed Albatross to transparently attach the session state to hidden fields inside the form. When using server-side sessions Albatross does not need to save any application state at the browser so we are free to use URL style user inputs. To illustrate the point, we will replace all of the form inputs with URL user inputs. The complete sample program is contained in the \texttt{samples/popview3} directory. Use the \texttt{install.py} script to install the sample. \begin{verbatim} cd samples/popview3 python install.py \end{verbatim} The new \texttt{list.html} template file follows. \verbatiminput{../samples/popview3/list.html} Next the new \texttt{detail.html} template file. \verbatiminput{../samples/popview3/detail.html} One of the more difficult tasks for developing stateful web applications is dealing with browser requests submitted from old pages in the browser history. When all application state is stored in hidden fields in the HTML, requests from old pages do not usually cause problems. This is because the old application state is provided in the same request. When application state is maintained at the server, requests from old pages can cause all sorts of problems. The current application state at the server represents the result of a sequence of browser requests. If the user submits a request from an old page in the browser history then the fields and values in the request will probably not be relevant to the current application state Making sure all application requests are uniquely named provides some protection against the application processing a request from another page which just happened the share the same request name. It is not a complete defense as you may receive a request from an old version of the same page. A request from an old version of a page is likely to make reference to values which no longer exist in the server session. Some online banking applications attempt to avoid this problem by opening browser windows that do not have history navigation controls. A user who uses keyboard accelerators for history navigation will not be hindered by the lack of navigation buttons. The \texttt{popview} application does not modify any of the data it uses so there is little scope for submissions from old pages to cause errors. By changing the base class for the application object we can gain support for server side sessions. Albatross includes a simple session server and supporting mixin classes. The new application prologue looks like this: \begin{verbatim} #!/usr/bin/python from albatross import SimpleSessionApp, SessionAppContext from albatross.cgiapp import Request import popviewlib \end{verbatim} The execution context now inherits from \class{SessionAppContext}: \begin{verbatim} class AppContext(SessionAppContext): def open_mbox(self): if hasattr(self.locals, 'mbox'): return self.locals.mbox = popviewlib.Mbox(self.locals.username, self.locals.passwd) def read_msg(self): if hasattr(self.locals, 'msg'): return self.locals.msg = self.locals.mbox[int(self.locals.msgnum) - 1] self.locals.msg.read_body() \end{verbatim} And the new application class looks like this: \begin{verbatim} class App(SimpleSessionApp): def __init__(self): SimpleSessionApp.__init__(self, base_url='popview.py', template_path='.', start_page='login', secret='-=-secret-=-', session_appid='popview3') for page_class in (LoginPage, ListPage, DetailPage): self.register_page(page_class.name, page_class()) def create_context(self): return AppContext(self) \end{verbatim} The \var{session_appid} argument to the constructor is used to uniquely identify the application at the server so that multiple applications can be accessed from the same browser without the session from one application modifying the session from another. Apart from changes to load the new template files, we also need to change the \class{ListPage} class because we changed the method of selecting messages from the message list. \begin{verbatim} class ListPage: name = 'list' def page_process(self, ctx): if ctx.req_equals('msgnum'): ctx.set_page('detail') def page_display(self, ctx): ctx.open_mbox() ctx.run_template('list.html') \end{verbatim} \section{Building Applications with Page Modules\label{app-popview4}} Implementing an application as a monolithic program is fine for small applications. As the application grows the startup time becomes an issue, as does maintenance. Albatross provides a set of classes that allow you to implement each page in a separate Python module. In this section we will convert the \texttt{popview} application to this type of application. Converting a monolithic application to a page module application is usually fairly simple. First we must turn each page object into a page module. When we used page objects, the class that implemented each page was identified by the \member{name} class member. With page modules the name of the module identifies the page that it processes. The complete sample program is contained in the \texttt{samples/popview4} directory. Use the \texttt{install.py} script to install the sample. \begin{verbatim} cd samples/popview4 python install.py \end{verbatim} The \class{LoginPage} class becomes \texttt{login.py}. \verbatiminput{../samples/popview4/login.py} The \class{ListPage} class becomes \texttt{list.py}. \verbatiminput{../samples/popview4/list.py} And the \class{DetailPage} class becomes \texttt{detail.py}. \verbatiminput{../samples/popview4/detail.py} When using page modules we do not need to register each page module. When Albatross needs to locate the code for a page it simply imports the module. So the entire \texttt{popview.py} program now looks like this: \verbatiminput{../samples/popview4/popview.py} \section{Random Access Applications\label{app-random}} In the popview application the server is in complete control of the sequence of pages that are served to the browser. In some applications you want the user to be able to bookmark individual pages for later retrieval in any desired sequence. Albatross provides application classes built with the \class{RandomPageModuleMixin} class for this very purpose. The \texttt{random} sample is provided to demonstrate the use of the \class{RandomModularSessionApp} class. Use the \texttt{install.py} script to install the sample. \begin{verbatim} cd samples/random python install.py \end{verbatim} The complete mainline of the \texttt{randompage.py} sample is shown below. \verbatiminput{../samples/random/randompage.py} When processing the browser request the application determines which page to serve to the browser by inspecting the URL in the browser request. The page identifier is taken from the part of the URL which follows the \var{base_url} argument to the constructor. If the page identifier is empty then the application serves the page identified by the \var{start_page} argument to the constructor. If you point your browser at \mbox{\url{\cgibindir/alsamp/random/randompage.py}} you will notice that the server has redirected your browser to the start page. The sample program defines two pages which demonstrate two different ways to direct user navigation through the application. The \texttt{tree.html} page template uses a form to capture user input. \verbatiminput{../samples/random/pages/tree.html} During conversion to HTML the \texttt{} tag automatically places the name of the current page into the \texttt{action} attribute. This makes the browser send the response back to the same page module (\texttt{tree.py}). \verbatiminput{../samples/random/pages/tree.py} When the application receives the \texttt{paginate} request it uses the \method{redirect()} method to direct the browser to a new page. The \texttt{paginate.html} page template uses a URLs to capture user input. \verbatiminput{../samples/random/pages/paginate.html} During conversion to HTML the \texttt{} tag automatically translates the \texttt{href="tree"} attribute into a URL which requests the \texttt{tree} page from the application. Since the browser is doing all of the work, the \texttt{paginate.py} module which handles the \texttt{paginate} page is very simple. \verbatiminput{../samples/random/pages/paginate.py} \section{The Albatross Session Server\label{app-ses-server}} The Albatross Session server works in concert with the execution context and application mixin classes in the \module{albatross.session} module to provide server-side session recording. The application and the server communicate via TCP sockets. By default, session servers listen on port 34343. More than one Albatross application can share a single session server process, and applications can be deployed over multiple web server hosts. \subsection{Sample Simple Session Server\label{app-ses-simple-server}} The \texttt{albatross.simpleserver} module is a sample session server. It can either be used stand-alone, or imported into other Python scripts. The session server uses TCP sockets to communicate with the application mixin, by default listening on port 34343. The server port can be changed by using the \texttt{-p} or \texttt{--port=} command line argument. Internally the server uses a select loop to allow connections from multiple applications simultaneously. Note that the daemon does not need to run as \texttt{root}, provided it listens on a port above 1024. If possible, you should run it under a user ID not shared by any other processes (and not \texttt{nobody}). You should also ensure that only authorised clients can connect to your session server, as the protocol provides no authentication or authorisation mechanisms. Application constructor arguments which are relevant to the session server are: \begin{itemize} \item{\var{session_appid}} This is used to identify the application with the session server. It is also used as the session id in the cookie sent to the browser. \item{\var{session_server}\code{ = 'localhost'}} If you decide to run the session server on a different machine to the application you must pass the host name of the session server in this argument. \item{\var{server_port}\code{ = 34343}} If you decide to run the session server on a different port you must pass the port number in this argument. \item{\var{session_age}\code{ = 1800}} This argument defines the amount of time in seconds for which idle sessions will kept in the server. \end{itemize} \subsection{Unix Session Server Daemon\label{app-ses-unix-daemon}} In the \texttt{session-server} directory of the source distribution is a Unix daemon version of the \texttt{simpleserver.py} session server, called \texttt{al-session-daemon}. Long running server processes under Unix need to be backgrounded, and disassociated from the terminal device on which they were started. \texttt{al-session-daemon} provides these functions, as well as recording the process ID of the daemon so as to allow it to be easily shut-down. Note that the daemon does not need to run as \texttt{root}, provided it listens on a port above 1024, and can write to it's pid and log directories. If possible, you should run it under a user ID not shared by any other processes (and not \texttt{nobody}). You should also ensure that only authorised clients can connect to your session server, as the protocol provides no authentication or authorisation mechanisms. \begin{verbatim} Usage: al-session-daemon [options]... Where [options] are: -D, --debug Write debugging to log -h, --help Display this help and exit -k , Record server pid in , default is --pidfile= /var/run/al-session-daemon.pid -p , --port= Listen on , default is 34343 -l , Write log to , default is --log= /var/log/al-session-daemon.log is one of: start start a new daemon stop kill the current daemon status request the daemon's status \end{verbatim} \subsection{Server Protocol\label{app-ses-protocol}} You can see the session server in action by using telnet. \begin{verbatim} djc@rat:~$ telnet localhost 34343 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. new myapp 3600 OK 38b80b3f546c8cfa put myapp 38b80b3f546c8cfa OK - send data now, terminate with blank line here is my session data it is on multiple lines OK get myapp 38b80b3f546c8cfa OK - session follows here is my session data it is on multiple lines del myapp 38b80b3f546c8cfa OK get myapp 38b80b3f546c8cfa ERROR no such session quit Connection closed by foreign host. djc@rat:~$ \end{verbatim} All dialogue is line based using a CRLF end of line sequence. Session ids are generated by the server and each application has its own set of session ids. The application mixin class in the \module{albatross.session} module uses the \var{session_appid} argument to the constructor as the application id with the session server. Note that this application id is also used in the cookie sent to the browser. If a command was successful the server response line will start with the text \texttt{'OK'} otherwise it will start with \texttt{'ERROR'}. \subsubsection{Create New Session\label{app-proto-new}} To create a new session for the \var{appid} application which will be deleted if left idle for more than \var{age} seconds the application sends a line of the form: \begin{verbatim} "new " appid " " age CRLF \end{verbatim} Successful response will be a line of the form: \begin{verbatim} "OK " sesid CRLF \end{verbatim} \subsubsection{Save Session\label{app-proto-put}} To save data into an existing session the application sends a line of the form: \begin{verbatim} "put " appid " " sesid CRLF \end{verbatim} If the session exists in the server it will respond with the following line: \begin{verbatim} "OK - send data now, terminate with blank line" CRLF \end{verbatim} The program then sends a sequence of text lines terminated by a single blank line. The server then responds with: \begin{verbatim} "OK" CRLF \end{verbatim} \subsubsection{Retrieve Session\label{app-proto-get}} To retrieve data for an existing session the application sends a line of the form: \begin{verbatim} "get " appid " " sesid CRLF \end{verbatim} If the session exists in the server it will respond with the following line: \begin{verbatim} "OK - session follows" CRLF \end{verbatim} The session data saved previously will then be sent terminated by a single blank line. \subsubsection{Delete Session\label{app-proto-del}} To delete an existing session the application sends a line of the form: \begin{verbatim} "del " appid " " sesid CRLF \end{verbatim} If the session exists it will be deleted and the server will respond with the following line: \begin{verbatim} "OK" CRLF \end{verbatim} \subsubsection{Quit\label{app-proto-quit}} To disconnect from the server the application sends a line of the form: \begin{verbatim} "quit" CRLF \end{verbatim} The server will then close the connection. \section{Application Deployment Options\label{app-deployoptions}} In all of the previous sections you will note that all of the programs used the \class{Request} class from the \module{albatross.cgiapp} module to deploy the application as a CGI script. The choice of \class{Request} class determines how you wish to deploy your application. Albatross supplies a number of pre-built Request implementations suited to various deployment methods. You should import the Request method from the appropriate module: \begin{longtableii}{l|l}{textrm}{Deployment Method}{Request Module} \lineii{CGI}{\module{albatross.cgiapp}} \lineii{mod_python}{\module{albatross.apacheapp}} \lineii{FastCGI_python}{\module{albatross.fcgiapp}} \lineii{Stand-alone Python HTTP server}{\module{albatross.httpdapp}} \end{longtableii} By placing all deployment dependencies in a \class{Request} class you are able to change deployment method with only minimal changes to your application mainline code. You could, for instance, carry out your initial development as a stand-alone Python HTTP server, where debugging is easier, and final deployment as FastCGI. You could also develop your own \class{Request} class to deploy an Albatross application in other ways, such as using the Medusa web server (\url{http://www.amk.ca/python/code/medusa.html}), or to provide a \class{Request} class which for performing unit tests on your application. The chapter on mod_python contains an example where the popview application is changed from CGI to \texttt{mod_python}. \subsection{\texttt{CGI} Deployment\label{app-deploycgi}} The \module{albatross.cgiapp} module contains a \class{Request} class to allow you to deploy your application using \texttt{CGI}. CGI is the simplest and most common application deployment scheme. The application is started afresh by your web server to service each client request, and is passed client input via the command line and stdin, and returns it's output via stdout. An example of a CGI application: \begin{verbatim} #!/usr/bin/python from albatross.cgiapp import Request class Application(...): ... app = Application() if __name__ == '__main__': app.run(Request()) \end{verbatim} \subsection{\texttt{mod_python} Deployment\label{app-deploymodpython}} The \module{albatross.apacheapp} module contains a \class{Request} class to allow you to deploy your application using \texttt{mod_python} \footnote{For more information on \texttt{mod_python}, including installation instructions, see \url{http://www.modpython.org/}.}. In the following example, we change the popview application from CGI to \texttt{mod_python}. The complete sample program is contained in the \texttt{samples/popview5} directory. Use the \texttt{install.py} script to install the sample. \begin{verbatim} cd samples/popview5 python install.py \end{verbatim} The new \texttt{popview.py} mainline follows. \verbatiminput{../samples/popview5/popview.py} The \function{handler()} function is called by \texttt{mod_python} when a browser request is received that must be handled by the program. You also need to create a \texttt{.htaccess} file to tell Apache to run the application using \texttt{mod_python}. \begin{verbatim} DirectoryIndex popview.py SetHandler python-program PythonHandler popview \end{verbatim} Assuming you install the popview sample below the \texttt{/var/www} directory you will need configure Apache settings for the \texttt{/var/www/alsamp} directory: \begin{verbatim} AllowOverride FileInfo Indexes Order allow,deny Allow from all \end{verbatim} \subsection{\texttt{FastCGI} Deployment\label{app-deployfastcgi}} The \module{albatross.fcgiapp} module contains a \class{Request} class to allow you to deploy your application using \texttt{FastCGI} \footnote{For more information on \texttt{FastCGI}, including installation instructions, see \url{http://www.fastcgi.com/}.}. Applications deployed via CGI often perform poorly under load, because the application is started afresh to service each client request, and the start-up time can account for a significant proportion of request service time. \texttt{FastCGI} attempts to address this by turning the application into a persistent server that can handle many client requests. Unlike \texttt{mod_python}, where applications run within the web server, \texttt{FastCGI} applications communicate with the web server via a platform-independent socket protocol. This improves security and the resilience of the web service. To deploy your application via \texttt{FastCGI} and Apache, you need to configure Apache to load \texttt{mod_fastcgi.so}, configure it to start your script as a \texttt{FastCGI} server, and use the \texttt{albatross.fcgiapp} \class{Request} class in your application. As an example of Apache configuration: \begin{verbatim} LoadModule fastcgi_module /usr/lib/apache/1.3/mod_fastcgi.so AddHandler fastcgi-script py Options +ExecCGI \end{verbatim} And the application main-line: \begin{verbatim} #!/usr/bin/python from albatross.fcgiapp import Request, running class Application(...): ... app = Application() if __name__ == '__main__': while running(): app.run(Request()) \end{verbatim} \subsection{Stand-alone Python HTTP Server Deployment\label{app-deploypyhttpd}} The standard Python libraries provide a pure-Python HTTP server in the \texttt{BaseHTTPServer} module. Code contributed by Matt Goodall allows you to deploy your Albatross applications as stand-alone scripts using this module to service HTTP requests. Unlike the other \texttt{Request} classes, applications deployed via the stand-alone Python HTTP server do not require you to instantiate the \texttt{Request} class directly. Instead, the \texttt{al-httpd} script imports your application (specifically, the script that instantiates the application object), and starts the HTTP server. Currently, applications deployed via the stand-alone http server are single threaded, meaning that other requests are blocked while the current request is being serviced. In many cases this is not a problem as the requests are handled quickly, but if your application takes a significant amount of time to generate it's results, you may want to consider other deployment options for production use. The stand-alone http server makes it particularly easy to deploy Albatross applications, and is a great way to debug applications without the complications that \texttt{mod_python} and \texttt{FastCGI} necessarily entail. Most of the Albatross samples can be run under the stand-alone server: \begin{verbatim} $ cd samples/tree2 $ al-httpd tree.app 8080 /alsamp/images ../images/ \end{verbatim} \section{Albatross Exceptions\label{app-exceptions}} \begin{excdesc}{AlbatrossError} An abstract base class all Albatross exceptions inherit from. \end{excdesc} \begin{excdesc}{UserError} Raised on abnormal input from the user. All current use of this exception is through the \exception{SecurityError} subclass. \end{excdesc} \begin{excdesc}{ApplicationError} Raised on invalid Albatross use by the application, such as attempting to set a response header after the headers have been sent to the client. Template errors are also instances of this exception. \end{excdesc} \begin{excdesc}{InternalError} Raised if Albatross detects an internal error (bug). \end{excdesc} \begin{excdesc}{ServerError} Raised on difficulties communicating with the session server or errors reading server-side session files. \end{excdesc} \begin{excdesc}{SecurityError} A subclass of \exception{UserError}, this exception is raised when Albatross detects potentially hostile client activity. \end{excdesc} \begin{excdesc}{TemplateLoadError} A subclass of \exception{ApplicationError}, this exception is raised if a template cannot be loaded. \end{excdesc} \begin{excdesc}{SessionExpired} A subclass of \exception{UserError}, this exception is raised when a client attempts to submit form results against a session that has expired. \end{excdesc} albatross-1.36/doc/test_examples.py0000644000175000017500000001217210360365320017306 0ustar andrewmandrewm#!/usr/bin/python # # Test fragments of example code to ensure they behave as # advertised. # # Functionally, this is similar to doctest, however our input # is more regular than that seen by doctest, and therefore our # parsing is more relaxed. # import sys import os import traceback import StringIO # Can't use sys.ps1, sys.ps2 as these are only defined for interactive sessions PS1 = '>>>' PS2 = '...' class ExampleFailure(Exception): pass class ExampleCmd: def __init__(self, linenum, line): self.linenum = linenum self.cmds = [line] self.expect = [] def add_continuation(self, line): self.cmds.append(line) def has_output(self): return bool(self.expect) def add_output(self, line): self.expect.append(line) def run(self, namespace): got = None expect = ''.join(self.expect) src = ''.join(self.cmds) try: saved_stdout = sys.stdout sys.stdout = StringIO.StringIO() try: code = compile(src, '', 'single') exec code in namespace got = sys.stdout.getvalue() finally: sys.stdout = saved_stdout except: # Is it an expected exception? if ('Traceback (innermost last):\n' in expect or 'Traceback (most recent call last):\n' in expect): # Only compare exception type and value - the rest of # the traceback isn't necessary. exc_type, exc_val = sys.exc_info()[:2] got = traceback.format_exception_only(exc_type, exc_val)[-1] expect = self.expect[-1] else: # Unexpected exception - print something useful exc_type, exc_val, exc_tb = sys.exc_info() exc_tb = exc_tb.tb_next psrc = PS1 + " " + src.rstrip().replace("\n", PS2 + " ") pexp = traceback.format_exception(exc_type, exc_val, exc_tb) pexp = ''.join(pexp).rstrip() raise ExampleFailure('Line %d:\n%s\n%s' % (self.linenum, psrc, pexp)) if got != expect: raise ExampleFailure('Line %d: output does not match example\n' 'Expected:\n%s\nGot:\n%s' % (self.linenum, expect, got)) class ExampleTest: def __init__(self, filename): self.filename = filename self.result = "" self.example = [] def report(self): if self.result: print "=" * 70 print "Testing \"%s\" failed," % self.filename, print self.result print def test(self): try: if not self.example: self.load_and_parse() self.result = "" self.execute_verify_output() except ExampleFailure, msg: self.result = msg return self.result == "" def load_and_parse(self): self.example = [] try: f = open(self.filename) except IOError, (eno, estr): raise ExampleFailure("could not load: %s" % estr) try: for linenum, line in enumerate(f): if line.startswith(PS1): self.example.append(ExampleCmd(linenum + 1, line[len(PS1)+1:])) elif line.startswith(PS2): if not self.example or self.example[-1].has_output(): raise ExampleFailure("Line %d: %r line with no preceeding %r line" % (linenum+1, PS2, PS1)) self.example[-1].add_continuation(line[len(PS2)+1:]) else: if not self.example: raise ExampleFailure("Line %d: output with no preceeding %r line" % (linenum+1, PS1)) self.example[-1].add_output(line) finally: f.close() def execute_verify_output(self): namespace = {} for cmd in self.example: cmd.run(namespace) def files_from_directory(dir): filenames = [] for fn in os.listdir(dir): filename = os.path.join(dir, fn) if fn[0] != '.' and os.path.isfile(filename) \ and not filename.endswith('.py'): filenames.append(filename) filenames.sort() return filenames def main(): if len(sys.argv) > 1: filenames = sys.argv[1:] else: filenames = files_from_directory("doctest") fail_cnt = total_cnt = 0 failures = [] for filename in filenames: if os.path.isfile(filename): total_cnt += 1 e = ExampleTest(filename) if not e.test(): failures.append(e) fail_cnt += 1 sys.stdout.write("!") else: sys.stdout.write(".") sys.stdout.flush() sys.stdout.write("\n") for e in failures: e.report() print "%d of %d tests failed" % (fail_cnt, total_cnt) sys.exit(fail_cnt > 0) if __name__ == '__main__': main() albatross-1.36/doc/.cvsignore0000644000175000017500000000011010135142506016043 0ustar andrewmandrewm*.l2h *.pdf *.tex2 *.eps *.ps *.gz *.lof *-methods.tex .*.swp albatross albatross-1.36/doc/packaged.tex0000644000175000017500000011361110577416325016354 0ustar andrewmandrewm%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Copyright 2001 by Object Craft P/L, Melbourne, Australia. % LICENCE - see LICENCE file distributed with this software for details. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{Prepackaged Application and Execution Context Classes\label{pack-overview}} \begin{figure}[h] \begin{center} \includegraphics{AlbatrossObjects} \caption{Albatross Classes} \end{center} \end{figure} \section{The \texttt{SimpleContext} Execution Context\label{pack-simplecontext}} The \texttt{SimpleContext} class is provided for applications which only make use of the Albatross template functionality. If you look at the implementation of the class you will note that it is constructed from a number of mixin classes. Each of these classes implements some of the functionality required for interpreting Albatross templates. Diagrammatically the \texttt{SimpleContext} class looks like this: \begin{figure}[h] \index{\class{SimpleContext}} \begin{center} \includegraphics{simplecontext} \caption{The \class{SimpleContext} class\label{fig-simplecontext}} \end{center} \end{figure} By implementing the execution context semantics in a collection of mixin classes Albatross allows you to change semantics by substituting mixins which implement the same interface. This is very useful when using the Albatross application objects. \begin{description} \item{\class{NamespaceMixin}} This mixin provides a local namespace for evaluating the expressions embedded in the \texttt{expr} tag attributes. Application code places values into the \member{locals} member to make them available for display by the template files. You will probably always use this mixin in your execution context. \item{\class{ExecuteMixin}} This mixin provides a sort of virtual machine which is required by the template file interpreter. It maintains a macro argument stack for expanding macros, and it is used to accumulate HTML produced by template execution. You will probably always use this mixin in your execution context. \item{\class{ResourceMixin}} This mixin provides a registry of template resources which only need to be defined once. Specifically the class provides a dictionary of Python classes which implement template file tags, a dictionary of template macros, and a dictionary of template lookup tables. If you are using Albatross application functionality, you will almost certainly use this mixin in your application class, not the execution context. \item{\class{TemplateLoaderMixin}} This mixin is a very simple template file loader. You will almost certainly use the \class{CachingTemplateLoaderMixin} in your application object instead of this mixin when you use the Albatross application objects. \item{\class{StubRecorderMixin}} Albatross provides special versions of the standard HTML \texttt{}, \texttt{ albatross-1.36/doc/doctest/tags-comment0000644000175000017500000000036407701715021020047 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... Before, ... This will not be executed ... after. ... ''').to_html(ctx) >>> ctx.flush_content() Before, after. albatross-1.36/doc/doctest/tags-form-file0000644000175000017500000000106710577416325020300 0ustar andrewmandrewm>>> import albatross >>> from fakeapp import ctx >>> albatross.Template(ctx, '', ''' ... ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content()
albatross-1.36/doc/doctest/tags-input-select20000644000175000017500000000106407623065706021114 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.opt = 3 >>> ctx.locals.sel = ['spam', 'eggs'] >>> albatross.Template(ctx, '', ''' ... ... Spam times ... ... eggs ... bacon ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-for30000644000175000017500000000152207415056537017267 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext, albatross.HiddenFieldSessionMixin): ... def __init__(self): ... albatross.SimpleContext.__init__(self, '.') ... albatross.HiddenFieldSessionMixin.__init__(self) ... >>> ctx = Ctx() >>> albatross.Template(ctx, '', ''' ... A: ... ... ... B: ... ... ... C: ... ... ... ''').to_html(ctx) >>> ctx.flush_content() A: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 B: 0 1 2 3 4 5 6 7 8 9 C: 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 albatross-1.36/doc/doctest/tags-lookup0000644000175000017500000000113607415056537017730 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> MILD, MEDIUM, HOT = 0, 1, 2 >>> albatross.Template(ctx, '', ... ''' ... Mild ... Medium ... Hot ... ''').to_html(ctx) >>> ctx.locals.spicy = 2 >>> ctx.locals.curry = 'Vindaloo' >>> albatross.Template(ctx, '', ''' ... ... ''').to_html(ctx) >>> ctx.flush_content() Hot Vindaloo albatross-1.36/doc/doctest/tags-exec10000644000175000017500000000123407671023266017420 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() 2 is a prime number 3 is a prime number 4 equals 2 * 2 5 is a prime number 6 equals 2 * 3 7 is a prime number 8 equals 2 * 4 9 equals 3 * 3 albatross-1.36/doc/doctest/tags-textarea10000644000175000017500000000075107701474341020312 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> text = ''' ... ... Type in some text ... ... ''' >>> albatross.Template(ctx, '', text).to_html(ctx) >>> ctx.flush_content() >>> ctx.locals.msg = 'This came from the program' >>> albatross.Template(ctx, '', text).to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-img0000644000175000017500000000042307733225677017177 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.src = 'http://icons.r.us/spam.png' >>> albatross.Template(ctx, '', ''' ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-input-radio30000644000175000017500000000121310001143460020702 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext): ... def input_add(self, *args): ... print args ... >>> ctx = Ctx('.') >>> ctx.locals.swallows = ['African', 'European'] >>> ctx.locals.num = 0 >>> albatross.Template(ctx, '', ''' ... ... ... ... ''').to_html(ctx) ('radio', 'swallow', 'African', False) ('radio', 'swallow', 'European', False) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-for90000644000175000017500000000060510575716014017270 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.items = [(1.23, 'Red'), (4.71, 'Green'), (0.33, 'Blue')] >>> albatross.Template(ctx, '', ''' ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() $1.23 Red $4.71 Green $0.33 Blue albatross-1.36/doc/doctest/tags-exec20000644000175000017500000000034607671023266017424 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', r''' ... ... a = ... ''').to_html(ctx) >>> ctx.flush_content() a = " albatross-1.36/doc/doctest/tags-macro90000644000175000017500000000060310447653355017607 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... <al-usearg name="title"> ... ... ''').to_html(ctx) >>> albatross.Template(ctx, '', ''' ... ''').to_html(ctx) >>> ctx.flush_content() Lumberjack albatross-1.36/doc/doctest/tags-macro40000644000175000017500000000124207420011175017563 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... more please ... more please ... ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() >>> albatross.Template(ctx, '', ''' ... spam ... ''').to_html(ctx) >>> ctx.flush_content() more spam please more spam please albatross-1.36/doc/doctest/tags-a20000644000175000017500000000047207442367447016730 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext): ... def current_url(self): ... return 'magic' ... >>> ctx = Ctx('.') >>> albatross.Template(ctx, '', ''' ... Login ... ''').to_html(ctx) >>> ctx.flush_content()
Login albatross-1.36/doc/doctest/templ-white40000644000175000017500000000061207417771267020012 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.title = 'Mr.' >>> ctx.locals.fname = 'Harry' >>> ctx.locals.lname = 'Tuttle' >>> templ = albatross.Template(ctx, '', ''' ... ... ... ''') >>> ctx.push_content_trap() >>> templ.to_html(ctx) >>> ctx.pop_content_trap() 'Mr. \nHarry \nTuttle' albatross-1.36/doc/doctest/tags-input-select40000644000175000017500000000065307623062746021122 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.menu = [(1, 'Spam'), (2, 'Eggs'), (3, 'Bacon')] >>> ctx.locals.sel = 1 >>> albatross.Template(ctx, '', ''' ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-exception0000644000175000017500000000021210360365561020400 0ustar andrewmandrewm>>> 1/0 Traceback (most recent call last): # This is simply to test the test runner ZeroDivisionError: integer division or modulo by zero albatross-1.36/doc/doctest/tags-include20000644000175000017500000000055407671015524020122 0ustar andrewmandrewm>>> open('other.html', 'w').write('name = ""') >>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.name = 'other.html' >>> albatross.Template(ctx, '', ''' ... Inserting other.html: here. ... ''').to_html(ctx) >>> ctx.flush_content() Inserting other.html: name = "other.html" here. albatross-1.36/doc/doctest/tags-for10000644000175000017500000000045607415056537017272 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 albatross-1.36/doc/doctest/tags-input-select30000644000175000017500000000115407623062746021116 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.sel1 = 3 >>> ctx.locals.sel2 = (2,3) >>> albatross.Template(ctx, '', ''' ... ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-input-treeselect-node0000644000175000017500000000101007733225677022633 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> class Node: ... def __init__(self, ino): ... self.ino = ino ... def albatross_alias(self): ... return 'ino%d' % self.ino ... >>> ctx.locals.node = Node(81489) >>> albatross.Template(ctx, '', ''' ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-value10000644000175000017500000000043310575645167017617 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.items = ['pencil', 'eraser', 'lunchbox'] >>> albatross.Template(ctx, '', ''' ... There are items ... ''').to_html(ctx) >>> ctx.flush_content() There are 3 items albatross-1.36/doc/doctest/tags-for50000644000175000017500000000056107673051321017263 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 albatross-1.36/doc/doctest/tags-input-nameexpr0000644000175000017500000000066707733225677021411 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.names = ['John', 'Terry', 'Eric'] >>> albatross.Template(ctx, '', ''' ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-macro30000644000175000017500000000060707420011175017566 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... 1. ... 2. ... ... ''').to_html(ctx) >>> ctx.flush_content() >>> albatross.Template(ctx, '', ''' ... ... spam ... ''').to_html(ctx) >>> ctx.flush_content() 1. spam 2. spam albatross-1.36/doc/doctest/tags-input-select50000644000175000017500000000124110575416454021114 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.spam = 'manufactured meat' >>> ctx.locals.potato = ('mash', 'Mashed Potato') >>> ctx.locals.sel = ['manufactured meat', 'eggs'] >>> albatross.Template(ctx, '', ''' ... ... ... ... eggs ... bacon ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-value30000644000175000017500000000047207671021731017610 0ustar andrewmandrewm>>> import albatross >>> import time >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... The time is ... ''').to_html(ctx) >>> ctx.flush_content() The time is 01:23:45 albatross-1.36/doc/doctest/tags-a30000644000175000017500000000114007443111712016701 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext): ... def current_url(self): ... return 'magic' ... def redirect_url(self, loc): ... return 'here/%s' % loc ... >>> ctx = Ctx('.') >>> ctx.locals.name = 'eric' >>> albatross.Template(ctx, '', ''' ... Login ... Login ... Login ... ''').to_html(ctx) >>> ctx.flush_content() Login Login Login albatross-1.36/doc/doctest/tags-input-radio10000644000175000017500000000113510001143460020703 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext): ... def input_add(self, *args): ... print args ... >>> ctx = Ctx('.') >>> ctx.locals.swallow = 'African' >>> albatross.Template(ctx, '', ''' ... ... ... ''').to_html(ctx) ('radio', 'swallow', 'African', False) ('radio', 'swallow', 'European', False) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-input-radio20000644000175000017500000000125510001143460020707 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext): ... def input_add(self, *args): ... print args ... >>> ctx = Ctx('.') >>> ctx.locals.swallows = ['African', 'European'] >>> ctx.locals.num = 0 >>> albatross.Template(ctx, '', ''' ... ... ... ''').to_html(ctx) ('radio', 'swallow', 'African', False) ('radio', 'swallow', 'European', False) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-macro100000644000175000017500000000121010447653355017652 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.title = 'Lumberjack' >>> albatross.Template(ctx, '', ''' ... ... <al-usearg name="title"> ... ... ''').to_html(ctx) >>> albatross.Template(ctx, '', ''' ... ''').to_html(ctx) >>> ctx.flush_content() Lumberjack >>> albatross.Template(ctx, '', ''' ... ... ... ''').to_html(ctx) >>> ctx.flush_content() Lumberjack albatross-1.36/doc/doctest/tags-textarea20000644000175000017500000000045407701474341020313 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.msg = 'Should escape < & >...' >>> albatross.Template(ctx, '', ''' ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-input-select60000644000175000017500000000076410575416454021126 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.spam = 'manufactured meat' >>> ctx.locals.sel = ['spam', 'eggs'] >>> albatross.Template(ctx, '', ''' ... ... spam ... eggs ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-item0000644000175000017500000000116407673572635017366 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.key = 2 >>> albatross.Template(ctx, '', ... ''' ... item expr="1" key is ... item expr="key" key is ... ''').to_html(ctx) >>> ctx.locals.key = 1 >>> t = albatross.Template(ctx, '', ''' ... ... ''') >>> t.to_html(ctx) >>> ctx.flush_content() item expr="1" key is 1 >>> ctx.locals.key = 2 >>> t.to_html(ctx) >>> ctx.flush_content() item expr="key" key is 2 albatross-1.36/doc/doctest/tags-input-noescape0000644000175000017500000000043707733225677021362 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.oops = '&<>"\'' >>> albatross.Template(ctx, '', ''' ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-anytag-tr0000644000175000017500000000130710360365561020316 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.names = ['Alex', 'Fred', 'Guido', 'Martin', 'Raymond', 'Tim'] >>> albatross.Template(ctx, '', ''' ...
... ... ... ... ... ...
... ''').to_html(ctx) >>> ctx.flush_content()
Alex
Fred
Guido
Martin
Raymond
Tim
albatross-1.36/doc/doctest/tags-macro60000644000175000017500000000101607653112432017572 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... ... ... ... ... ''').to_html(ctx) >>> ctx.locals.food = 'spam' >>> albatross.Template(ctx, '', ''' ... ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() spam! spam!! spam!!! albatross-1.36/doc/doctest/tags-macro110000644000175000017500000000120510447672641017656 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... Parrot ... <al-usearg name="title"> ... ... ''').to_html(ctx) >>> albatross.Template(ctx, '', ''' ... ... Lumberjack ... ''').to_html(ctx) >>> ctx.flush_content() Lumberjack >>> albatross.Template(ctx, '', ''' ... ... ''').to_html(ctx) >>> ctx.flush_content() Parrot albatross-1.36/doc/doctest/tags-for20000644000175000017500000000104407521166635017264 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext, albatross.HiddenFieldSessionMixin): ... def __init__(self): ... albatross.SimpleContext.__init__(self, '.') ... ... >>> ctx = Ctx() >>> ctx.locals.__dict__.keys() [] >>> albatross.Template(ctx, '', ''' ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 >>> ctx.locals.__dict__.keys() ['i'] albatross-1.36/doc/doctest/tags-if30000644000175000017500000000112707671020616017071 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.a = 30 >>> albatross.Template(ctx, '', ''' ... ... a () is less than 15. ... ... a () is greater than or equal to 15 and less than 25. ... ... a () is greater than or equal to 25 and less than 35. ... ... a () is greater than or equal to 25. ... ... ''').to_html(ctx) >>> ctx.flush_content() a (30) is greater than or equal to 25 and less than 35. albatross-1.36/doc/doctest/tags-macro80000644000175000017500000000107107677462451017614 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... <al-usearg> ... ... ''').to_html(ctx) >>> albatross.Template(ctx, '', ''' ... ... Lumberjack ... ''').to_html(ctx) >>> ctx.flush_content() Lumberjack >>> albatross.Template(ctx, '', ''' ... ... Lumberjack ... ''').to_html(ctx) >>> ctx.flush_content() Lumberjack albatross-1.36/doc/doctest/tags-flush0000644000175000017500000000035207415056537017537 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... Processing results... ... ... Finished ... ''').to_html(ctx) Processing results... >>> ctx.flush_content() Finished albatross-1.36/doc/doctest/tags-macro50000644000175000017500000000122407420011175017564 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() >>> albatross.Template(ctx, '', ''' ... more spam please ... more spam please ... ''').to_html(ctx) >>> ctx.flush_content() more spam please more spam please albatross-1.36/doc/doctest/tags-for80000644000175000017500000000044610575716014017272 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 albatross-1.36/doc/doctest/tags-input-checkbox0000644000175000017500000000242510001143460021315 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext): ... def input_add(self, *args): ... print args ... >>> ctx = Ctx('.') >>> ctx.locals.menu = ['spam', 'eggs'] >>> ctx.locals.eric = 'half' >>> ctx.locals.parrot = 'on' >>> ctx.locals.halibut = 'off' >>> albatross.Template(ctx, '', ''' ... ... ... ... ... ... ... ''').to_html(ctx) ('checkbox', 'menu', 'spam', False) ('checkbox', 'menu', 'eggs', False) ('checkbox', 'menu', 'bacon', False) ('checkbox', 'eric', 'half', False) ('checkbox', 'parrot', 'on', False) ('checkbox', 'halibut', 'on', False) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-macro20000644000175000017500000000103607420011175017562 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() >>> templ = albatross.Template(ctx, '', ''' ... ... ''') >>> try: ... templ.to_html(ctx) ... ctx.flush_content() ... except NameError, e: ... print e ... name 'oops' is not defined >>> ctx.locals.oops = 'there is now' >>> templ.to_html(ctx) >>> ctx.flush_content() there is nowalbatross-1.36/doc/doctest/tags-if20000644000175000017500000000056207671020616017072 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.a = 20 >>> albatross.Template(ctx, '', ''' ... ... a () is less than 15. ... ... a () is greater than or equal to 15. ... ... ''').to_html(ctx) >>> ctx.flush_content() a (20) is greater than or equal to 15. albatross-1.36/doc/doctest/templ-white10000644000175000017500000000155507416573750020012 0ustar andrewmandrewm>>> import albatross >>> text = ''' ... ... ... Simple Content Management - <al-usearg name="title"> ... ... ...

Simple Content Management -

...
... ... ... ...
... ''' >>> ctx = albatross.SimpleContext('.') >>> templ = albatross.Template(ctx, '', text) >>> templ.to_html(ctx) >>> text = ''' ... hello ... ... ''' >>> expand = albatross.Template(ctx, '', text) >>> ctx.push_content_trap() >>> expand.to_html(ctx) >>> result = ctx.pop_content_trap() >>> print result Simple Content Management - hello

Simple Content Management - hello


albatross-1.36/doc/doctest/tags-macro10000644000175000017500000000062107420011175017560 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... Will be executed when macro is expanded. ... ... ''').to_html(ctx) >>> ctx.flush_content() >>> albatross.Template(ctx, '', ''' ... ... ''').to_html(ctx) >>> ctx.flush_content() Will be executed when macro is expanded. albatross-1.36/doc/doctest/tags-if10000644000175000017500000000054307671020616017070 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.a = 10 >>> albatross.Template(ctx, '', ''' ... ... a () is less than 15. ... ... a () is greater than or equal to 15. ... ... ''').to_html(ctx) >>> ctx.flush_content() a (10) is less than 15. albatross-1.36/doc/doctest/tags-for70000644000175000017500000000326310001143460017250 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.seq = range(9) >>> t = albatross.Template(ctx, '', ''' ... pagesize has_prevpage has_nextpage index start count value ... ---------------------------------------------------------- ... ... ... ... ... ... ... ... ... ''') >>> t.to_html(ctx) >>> ctx.locals.i.set_backdoor('nextpage', 'nextpage,i') >>> t.to_html(ctx) >>> ctx.locals.i.set_backdoor('nextpage', 'nextpage,i') >>> t.to_html(ctx) >>> ctx.flush_content() pagesize has_prevpage has_nextpage index start count value ---------------------------------------------------------- 3 False True 0 0 0 0 3 False True 1 0 1 1 3 False True 2 0 2 2 pagesize has_prevpage has_nextpage index start count value ---------------------------------------------------------- 3 True True 3 3 0 3 3 True True 4 3 1 4 3 True True 5 3 2 5 pagesize has_prevpage has_nextpage index start count value ---------------------------------------------------------- 3 True False 6 6 0 6 3 True False 7 6 1 7 3 True False 8 6 2 8 albatross-1.36/doc/doctest/tags-input-treeselect0000644000175000017500000000105607733225677021722 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> class Node: ... def __init__(self, ino): ... self.ino = ino ... def albatross_alias(self): ... return 'ino%d' % self.ino ... >>> ctx.locals.tree = Node(81489) >>> albatross.Template(ctx, '', ''' ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-input-image0000644000175000017500000000063110001143460020606 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext): ... def input_add(self, *args): ... print args ... >>> ctx = Ctx('.') >>> albatross.Template(ctx, '', ''' ... ... ''').to_html(ctx) ('image', 'nextpage,m', None, False) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-input-nextpage0000644000175000017500000000046607733225677021402 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/templ-white50000644000175000017500000000065607416573750020017 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.title = 'Mr.' >>> ctx.locals.fname = 'Harry' >>> ctx.locals.lname = 'Tuttle' >>> templ = albatross.Template(ctx, '', ''' ... ... ... ''') >>> ctx.push_content_trap() >>> templ.to_html(ctx) >>> ctx.pop_content_trap() 'Mr. Harry Tuttle' albatross-1.36/doc/doctest/tags-input-generic0000644000175000017500000000261010001143460021137 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext): ... def input_add(self, *args): ... print args ... >>> ctx = Ctx('.') >>> ctx.locals.zero = 0 >>> ctx.locals.zerostr = '0' >>> ctx.locals.width = 5 >>> ctx.locals.height = 7 >>> ctx.locals.secret = 42 >>> ctx.locals.other_secret = '<"&' >>> albatross.Template(ctx, '', ''' ... ... ... ... ... ... ... ... ... ''').to_html(ctx) ('text', 'zero', 0, False) ('text', 'zerostr', '0', False) ('text', 'width', 5, False) ('text', 'area', 35, False) ('password', 'passwd', None, False) ('submit', 'login', 'Login', False) ('hidden', 'secret', 42, False) ('hidden', 'other_secret', '<"&', False) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-value20000644000175000017500000000064207415056537017616 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.field = '' >>> albatross.Template(ctx, '', ''' ... Safe: ... Oops: ... ''').to_html(ctx) >>> ctx.flush_content() Safe: <img src="http://rude.pictures.r.us/"> Oops: albatross-1.36/doc/doctest/tags-form-action0000644000175000017500000000141310577416325020631 0ustar andrewmandrewm>>> import albatross >>> from fakeapp import ctx >>> albatross.Template(ctx, '', ''' ... ... ... ... ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content()
albatross-1.36/doc/doctest/tags-for40000644000175000017500000000125207673051321017260 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext, albatross.HiddenFieldSessionMixin): ... def __init__(self): ... albatross.SimpleContext.__init__(self, '.') ... albatross.HiddenFieldSessionMixin.__init__(self) ... >>> ctx = Ctx() >>> albatross.Template(ctx, '', ''' ... ... prev ... next ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() next 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 albatross-1.36/doc/doctest/tags-img-noescape0000644000175000017500000000043407733225677020774 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.url = 'http://www.com/img?a=1&b=2' >>> albatross.Template(ctx, '', ''' ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/tags-macro70000644000175000017500000000116307420011175017570 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... The unnamed arg () still works, ... but we can also include named arguments () ... ... ''').to_html(ctx) >>> albatross.Template(ctx, '', ''' ... ... unnamed arg ... named arg: arg1 ... ... ''').to_html(ctx) >>> ctx.flush_content() The unnamed arg (unnamed arg) still works, but we can also include named arguments (named arg: arg1) albatross-1.36/doc/doctest/tags-for60000644000175000017500000000057707673051321017273 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> albatross.Template(ctx, '', ''' ... ... ... ... ... ... ''').to_html(ctx) >>> ctx.flush_content() 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 albatross-1.36/doc/doctest/tags-input-select10000644000175000017500000000064707623065706021121 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.opt = 'spam' >>> ctx.locals.sel = 'spam' >>> albatross.Template(ctx, '', ''' ... ... ... eggs ... ... ''').to_html(ctx) >>> ctx.flush_content() albatross-1.36/doc/doctest/templ-white20000644000175000017500000000060407415056537020003 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.title = 'Mr.' >>> ctx.locals.fname = 'Harry' >>> ctx.locals.lname = 'Tuttle' >>> templ = albatross.Template(ctx, '', ''' ... ... ... ''') >>> ctx.push_content_trap() >>> templ.to_html(ctx) >>> ctx.pop_content_trap() 'Mr.HarryTuttle' albatross-1.36/doc/doctest/templ-white30000644000175000017500000000056707415056537020014 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.title = 'Mr.' >>> ctx.locals.fname = 'Harry' >>> ctx.locals.lname = 'Tuttle' >>> templ = albatross.Template(ctx, '', ''' ''') >>> ctx.push_content_trap() >>> templ.to_html(ctx) >>> ctx.pop_content_trap() 'Mr. Harry Tuttle' albatross-1.36/doc/doctest/tags-include10000644000175000017500000000056207671015524020120 0ustar andrewmandrewm>>> open('other.html', 'w').write('name = ""') >>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.name = 'other.html' >>> albatross.Template(ctx, '', ''' ... Inserting : here. ... ''').to_html(ctx) >>> ctx.flush_content() Inserting other.html: name = "other.html" here. albatross-1.36/doc/doctest/tags-value40000644000175000017500000000065307415056537017622 0ustar andrewmandrewm>>> import albatross >>> ctx = albatross.SimpleContext('.') >>> ctx.locals.simple = 0 >>> ctx.locals.works = 1 >>> albatross.Template(ctx, '', ''' ... FALSETRUE ... Simple: ... Works: ... ''').to_html(ctx) >>> ctx.flush_content() Simple: FALSE Works: TRUE albatross-1.36/doc/doctest/tags-a10000644000175000017500000000050507442367447016724 0ustar andrewmandrewm>>> import albatross >>> class Ctx(albatross.SimpleContext): ... def current_url(self): ... return 'magic' ... >>> ctx = Ctx('.') >>> albatross.Template(ctx, '', ''' ... Next Page ... ''').to_html(ctx) >>> ctx.flush_content() Next Page albatross-1.36/doc/modularsessapp.dia0000644000175000017500000000567510135141327017611 0ustar andrewmandrewm]]s8}_Aѯia dޮ݇I̳K41G0W6;&Nks~ytT\7] }1tӟDtgq===`h,$ xBFNmջ4%'1tnc?LHIwjGt?gnoMo}Gtƒч r2]GLnw;Ivͪҍ篃KZ׾ 5ϩp׎7FXı,K u\c4/tsf͚͚ʋ%ɱj,Vގi]а 7iZ;,oDZ82{"CZ~N}O?-rzyx=`zg~q _?^a6O'i'Lq69=Vzv7f}g&`r?^W.̏WΗη*}!ZM?I͉ڃ,ok{hxݻOiV>NU@A js)3^@!m}*ؚ1>v7^rƶDӆd$;S{} k*fԅlV69 cY8Ķ'ݢ$vSBk6S/UpPCnhoݹ'I ϧQt`_ ^9SH{-lO1BO^X^yE(TD}xM.Dr&2W8gHь,*%(ʵo<QJ3] N5&멅Z 5gvӻ5u{뱵 UOr{{ƚ̃u`<rHb򺌹 e:<^3dz N7;-oH{(Ig>כhU3W䢉Qe@)_йETYl/;Gu#c:!.e/%i~<7œ>t~s4p8Ӣ4(hPWp\9^/j K]1+/ 5!ԸxZVQcʮxϼl'j%N|ϚDn<;5$R$Gu7/\z`=Ui}')ܑtf4"И0v^{ÞϚ5w.XfUŲEltj]^Ab>d]'W^+-uG(us5!XL֯aM5jcd6v,~ 7V0`X1m) B"nsJa>5a{ƕkZ`vUsS7l%|5%vGj?z` +[%dAԺˆl0-b6DԸb@gI;~LjWA'Lgba`a`a`a`a`a '9Im>1Ưp،%K@λF#~ }MlҰɰtm#5qW1FȬƨ*5rNj-Hl,9Щ6T,KYm/1kH7ch75 d68j2oLi l@d"ـD6  l@dbqCXjBe#1e7X\gUI6)C@z =HwNk+snX{xH"00000G>z`=X_/}!ӌO @y!{ 3 .L^iziRJB2FZ?&=)`-᭹8C!ngXNn_kT j36j36j36j36j36j36b"zId9@]/,`qi i%سϢ7Xփ^e~`0|xgg 3:8ekgtsKS2fc?@AAA3@{nŔB!ƻhSx`<0jh8y $]ΓLcz`=Xֻ\SżD`;N9S(f`0(baf1jKBp8pFMgiF4+˪(q3=JôP<(*rƈ؛i k }3HA g>UVEXªY "z4lbWLeEtv.uϳ vxEEE򢧞v d/f?P3˞q[νz`=XkH^ lFT$TTTTT}*ʩW+[4Q<"dOw7HfH] mg@ɟn>>ZsIjEვQ/Elv\}Zix{6Mͼ(U['Qξo,YV9S?i!BQ$>iG~=*SDx{xK u5Fn4A`c=뛸o"o"o"V$S?OO(㢥<}H s]-ҿ/a'zŏ.](^~ϵZܦ+%Zjy 7ntaGAӻ|5M]eZjڕŖ:^/׵K53 -jf3.eot3s45|nnSLϑ/xc[SxE居rCC `Z'i|jd莪^K/hԽ .ocvk%Xes[ E|m(y\4oHw#{\119S=2W]l>ՉGCZ'f rI8*m^`Mm" ^#UP4J [BT%5mjxrjz Y6S#M(߱6]A_@+/1VЀ%> 6 xO5 D.0x Gx aO-*7}InU=j+-V \64svJ@+Z~3ۉq$sŀ1`s4!V镣#H(|bQIYd#x$#A zɐEڻ-  RHq@\s4AЛ! RvRw:iޘ(UeR~"Jݡc0~?'ocTqoH@?ɖxn  Ŧ,ruѶ\1KeWAhNז3+>o07+?Mvc͚WQo)wn D14xhCӦft05!mh2RD{2{`5׻ז5OJTGԬÀ~0#E#7ȵspcbfN2G3BYߺcX?EǘS1/:)uu\] 7: 7ұ=>.pt9E˥Qam&O:(wdi٭רe f Cr=Cj,u8jƗs;si裺4}ȻR5F^+@ea2[l^U-ZjKb LAׄ&f:0y^ 4ny9â2vUT1DO1r6-tb Dh*InN;|5L7{^0 âzÊ<(SjچئᱽD8[y]}"/^~l+vc.OGz@eT]~#S9E *}ҿ"\.uu=NceX+罁ުP}<1CcjC[s.gW: :v8׃>W>0]b,fA:Q;15~{׼%/E FXV}Nؾ] yC ˤ߁p1_u ؿ!wi6Kl]Ee[DeH]B )sB=!ÈG0^L `)2ULǠ߁IӵNlm:-w>2Xx& G?֟+۳STLYC+O^QyHma珉z2]QYbu:=>0Հ~b,9OS9HSXsuAGueH?q3Cޤg(#яIz6 ^nFzL,gkvː~w {'2ڻJ~.yR(pN 'NyP/fxw˥̽뮧"bڒn"i{ze8?qs<_l"c!ꆾ2RxDm6lKC!y\ m'}s?݇`(albatross-1.36/doc/doc_methods.py0000644000175000017500000000375007733221373016734 0ustar andrewmandrewmimport sys import os import getopt sys.path.insert(0, '..') _ARGS_ARGS_FLAG = 4 _KW_ARGS_FLAG = 8 def find_methods(klass): methods = [] for name, attr in klass.__dict__.iteritems(): if name.startswith('_'): continue if callable(attr): args = [] code = attr.func_code arg_names = list(code.co_varnames[1:code.co_argcount]) arg_idx = code.co_argcount if code.co_flags & _ARGS_ARGS_FLAG: arg_names.append('*' + code.co_varnames[arg_idx]) arg_idx += 1 if code.co_flags & _KW_ARGS_FLAG: arg_names.append('**' + code.co_varnames[arg_idx]) methods.append((name, arg_names, klass.__name__)) for base in klass.__bases__: methods.extend(find_methods(base)) return methods def combine_methods(methods): combined = [] names = {} for name, arg_names, klass in methods: if names.get(name): continue combined.append((name, arg_names, klass)) names[name] = 1 combined.sort() return combined def method_table(klass): n = find_methods(klass) print r'\begin{longtableii}{l|l}{textrm}{Method}{Mixin}' for name, arg_names, klass in combine_methods(n): args = ', '.join(arg_names) print r'\lineii{\method{%s(%s)}}{\class{%s}}' % (name, args, klass) print r'\end{longtableii}' def usage(): sys.exit('usage: %s [-o filename] module.class' % os.path.basename(sys.argv[0])) if __name__ == '__main__': try: opts, args = getopt.getopt(sys.argv[1:], 'o:', ['output=']) except getopt.GetoptError: usage() for opt, arg in opts: if opt in ('-o', '--output'): sys.stdout = open(arg, 'w') if len(args) != 1: usage() try: mod_name, klass_name = args[0].split('.') except: usage() mod = __import__(mod_name) klass = getattr(mod, klass_name) method_table(klass) albatross-1.36/doc/modularsessfileapp.dia0000644000175000017500000000561410135141327020442 0ustar andrewmandrewm]]s8}_re`dޮ݇I3%c#$އ+IUGEXstuuﯿ=u$Fâ hz_o7~p :wD2;S*||$BR H C׍ݛO&TիT)ǩbuwLh]Z 0^w?_Z?{N蔍]t0<%M"jRO6VR77ڟzᵯ]ZhDͩhێ7FXĵ,K s=cd{rqf͉fq'Prmr!Ҫ)+oG4Sl߰ 20/,ó\oXW*>0;c|;Y >((Ow+LezCyX7=i ܺ9I:$xѢxw/FӐ,s2#seik6qrX~h[!kD6G lנә*66 Κq ɴ&nIRlr}!$;5)b$.[ل~*,RO42oOEi}?MZ_S?L=בZW`e 9V;(hw$rkIݫ`ipi{R!(m^9y2vZdzAoq*8?[+pݳ\By,J]Y."ȤUKT?/_>)ЊD+qt%+|;g!}ל9]󬓹.soglmCz1ˡB\sC>)8y]\crC̱(nl/KҎ%ʑnmhkEM+ebĨreй%T&wY/ohBH<0Sg}YCI=&EF 렱C4v&t/ly rf =/׷4wl" y!n̪[78=kH7o oRr'j#8q\E}Mk٨X6X`n]BkEpy|{v%~2yKgFʍ nքfsfc6aecQ(slUB#kjB[|ٰX6,8=ӭ5feڊ\ n@WQ+1 cfK1̮ ,V9fڙ̴zrר [`G0Tq^Mh?)UAxUtcXYuȨgzcD75t] ѤCUt! S/8Oax&YB=h0?q=&np'1p'1p'1p'1p'1p'1TD$ƘJOq hq09N@λcF >(fG rvȰgf&s12FfAk= !@AB|||[_ Y se*#:*8 CQͤF!D< x@"D< =&T6S9 D ~?el/_p\`S1H@z$Fg~ôĴ|Z,=q|&aaaaa |z`=Xއ$yyNs>}310;|2$wv滐2EdyPK9QVhYŲMsLbo3/à Q:bI Hˀ Hˀ Hˀ Hˀ Hˀ Hˀ p½ %eir91spB/}vF$e峧%.nX\<I# 9POdVt]ѽ-Șip1111v8| ݒI ƒwь'x`<0#Ϙ;"ޛ=)0ՌF |3A ܪp­zn<݄ VZ=Zm6KQN&f;UOb5"""CzS+fA=b.!&ƊW/t/`=Xo$kYY҃~ ӷ 00000]TSYYb00000%󝈃t3J2@z =HIoe҅&=đRz?.=U5YRie* 2pl 4cy5f?&yk !!!!!M'VI'› o*R/S|A @QV̻ؑC#(ʃh.B4A ͭ>7-)g!(Ǔ/邉O_T?n7u*albatross-1.36/doc/AlbatrossObjects.dia0000644000175000017500000001165010576462006020012 0ustar andrewmandrewm]Ks8W-1dj5]35v#s]Ľl=H b|Il]:$[}M'j-'5ǫ./eM;Vk݇mQo߾:*?׫W]گ_u>ˋLϡAf0sm' 0949;7>Z-x3>5?g[?OO ő ʮ ʠ+zWk\#쪡Rj*\|HxttW*^iC|EMղ7o}/bt:b9JQYbe /iSM-h>MQa~#qwClU4LDwIaIN'0U_|>ebq)éy:M ]|{:t5:'[kMzU?qjs`ŁPdQ99:9'Ѽ99\NNN;ك#BH18FaGpZs3*+o<Ɣ:4!ToY14D4^Vʶ2t Hx1LvIp*8`vD:`q0"Z (h7ʀ~,Bq8ۤ6RTCa;jJCX\kjseQ#5|Љ:qK5(qO~^fYjW&i` J2l_{ߌ\zQC.D zzԳLzI=gygnKoHss;9_&A5wT_9207+`Kf??o" g$l40e $f@ɮqpͲ&=: QP ª 8!OQUa vT.A\9)C3Ԗ xͳ5$! VUB a;_(_hP‡2 |1J‹6<|Qhrv| unC=Q24 E__ 1Qa*嗃////C_(_x|i2AJw ߯D^fYs`_> EV"ܷuui&ۚ[Z: WQ#_(n|H v/CЎ &|Q0AaBf4#I*Lv4 LW I2D:#tޥâGx Kf$B33ea9 o̜o|iv3 qoK %%e31^ 3Zb 5JmжFQgU02r'$]bf4((s2:e{zi?(Y2 +FJx:K B00'! /PުJV%"21DKj>Qf[cB.m pp0>b.$<,˿TFpfx2ͥzbKD1v&:J^.(6!WnJvA'Q&N_ں8 !nq d ^M5HaFw) 15)PF4PU&\KkG lVuc]XCkk)M6 w*D>U RwFQ  יA1B]̜ffX&LķH3ިIa"Fox(e$O7pc#(@MP3t] F=haDLxM]5uАR/esX!Q_>MXB`u#Hr~X|#V˝H ۏhD@:41* 1Rpf} k5 $.YlJ2pvP̪ TЛ*PȭsґU-:] 5k+a瞏 ~pb!gJϟdUP`9/efԠFr]^uHXY#gu3q_8Ԟ F eߒ' eJyr#VV8ɶ&)\4bxqo&Ë4$^x;m^H3T3SJDGt&ӱJwIl.hq]3eY"0#Ja͞3r1 \'JFn0H/ 1؉"b4 @@sњ'%;oH}oVa(_vZqT{}4֡7JsS; }'齯׷fD?yZ*ɲ4V"Or-jٶO=Sgwf:'FZ!2Ǟv#>KV||h_3[<{zW} -8K*4۟ODwIaIN'Ѣ2zq5.yӧ,]"?Hm5}9q`!qS4,oRUPzWq(MWeߗ>'.LUq&`y'% A  Ŀʵ DܼoS>k &`Q{Qῴ"Mϔ$Q$1K&$$6VOJ$:Ht2PG([= ӑ N!ܯUBgPp 3&D(^(# rkAnCWJ % V а{k`+ rJ,`>˓eo+Ses}\I@?<{<7pJOMH n-T|z~xh#8/=2}&H|>@[^v0 -fy!Gg ּfvf0 `,dG6 f7uH?7`Q(|ʣu!`VgBPg8kPu]Eh F y^??Wl$R&Dtͬfl/C#/D4$kh鐈6@hq y Lv'pTf‘73 fϞ>jChhUfUGw K3?albatross-1.36/doc/Makefile0000644000175000017500000000566710445741751015544 0ustar andrewmandrewmPYTHON = python PYDIR=$(shell $(PYTHON) -c 'import os; print os.path.dirname(os.__file__)') DOC_ROOT = $(PYDIR)/doc MKHOWTO = $(DOC_ROOT)/tools/mkhowto ICONS = $(DOC_ROOT)/html/icons .PHONY: html PAPER = a4 DOCFILES = albatross.tex copyright.tex installation.tex introduction.tex \ tempuser.tex tags.tex customtags.tex mixins.tex packaged.tex METHODFILES = SimpleContext-methods.tex AppContext-methods.tex \ SimpleAppContext-methods.tex SessionAppContext-methods.tex \ SessionFileAppContext-methods.tex Application-methods.tex \ SimpleApp-methods.tex SimpleSessionApp-methods.tex \ SimpleSessionFileApp-methods.tex ModularApp-methods.tex \ ModularSessionApp-methods.tex ModularSessionFileApp-methods.tex \ RandomModularApp-methods.tex RandomModularSessionApp-methods.tex \ RandomModularSessionFileApp-methods.tex \ BranchingSessionContext-methods.tex FIGURES = simplecontext.pdf simpleappcontext.pdf simpleapp.pdf \ twolayer.pdf twolayerctx.pdf mvc.pdf albmvc.pdf pagemap.pdf \ toolkit.pdf appcontext.pdf application.pdf sessionappcontext.pdf \ sessionfileappcontext.pdf simplesessapp.pdf simplesessfileapp.pdf \ modularapp.pdf modularsessapp.pdf modularsessfileapp.pdf \ dataflow.pdf AlbatrossObjects.pdf randmodapp.pdf randmodsessapp.pdf \ randmodsessfileapp.pdf branchingsessioncontext.pdf ALLFILES = $(DOCFILES) $(METHODFILES) $(FIGURES) all: pdf test: PYTHONPATH=.. $(PYTHON) test_examples.py pdf: $(ALLFILES) version $(MKHOWTO) --pdf --$(PAPER) albatross.tex html: $(ALLFILES) version $(MKHOWTO) --image-type png --html albatross.tex mkdir -p albatross/icons cp $(ICONS)/* albatross/icons/ # the iconserver option of mkhowto is broken since it writes # it to the end if the init_file where they aren't useful anymore, # so we work around it: for f in `find albatross -type f`; do \ cat $$f | sed s/\.\.\\/icons/icons/g > $${f}2; \ mv $${f}2 $$f; \ done -rm albatross/albatross2 -rm albatross/icons/icons2 -rm -f albatross/images.* -rm -f albatross/*.old world: pdf html pdf-$(PAPER).tar.gz: pdf tar cf - *.pdf | gzip -9 >$@ html.tar.gz: html tar cf - albatross | gzip -9 >$@ # build method files $(METHODFILES): $(PYTHON) doc_methods.py -o $@ albatross.$(subst -methods.tex,,$@) # convert .obj images to .eps and then to .pdf # Some versions of dia require --export-to-format=eps-builtin, rather than # --filter=eps-builtin. %.pdf: %.dia dia --nosplash --filter=eps-builtin \ --export=$(subst .dia,.eps,$<) $< epstopdf $(subst .dia,.eps,$<) clean: rm -f *~ *.aux *.idx *.ilg *.ind *.log *.toc *.bkm *.syn \ *.pla *.eps *.pdf *.lof *.l2h *.tex2 *.html *.pyc $(METHODFILES) # Version substitution version: ../albatross/__init__.py DATE="`date +'%B %d, %Y'`"; \ VERSION="`awk '/__version__/ {print $$3}' ../albatross/__init__.py`"; \ VERSION="`echo $$VERSION | sed s/\\'//g`"; \ cat albatross.tex | sed s/\\release.*/\\release\{$$VERSION\}/ >albatross.tex2; \ cat albatross.tex2 | sed s/\\date.*/\\date\{"$$DATE"\}/ >albatross.tex albatross-1.36/doc/tempuser.tex0000644000175000017500000013207607701526240016460 0ustar andrewmandrewm%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Copyright 2001 by Object Craft P/L, Melbourne, Australia. % LICENCE - see LICENCE file distributed with this software for details. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{Templates User Guide\label{tug-guide}} There are many different ways which you can use Albatross to assist in the construction of a web application. The purpose of this guide is to slowly introduce the features of Albatross to allow you to learn which features can be useful in your application. All of the example programs in this chapter are distributed with the source in the \texttt{samples/templates} directory. \section{Introduction to CGI\label{tug-simple1}} This section presents a very simple program which will introduce you to CGI programming. This serves two purposes; it will verify that your web server is configured to run CGI programs, and it will demonstrate how simple CGI programming can be. The sample program from this section is supplied in the \texttt{samples/templates/simple1} directory and can be installed in your web server \texttt{cgi-bin} directory by running the following commands. \begin{verbatim} cd samples/templates/simple1 python install.py \end{verbatim} The \texttt{simple.py} program is show below. \verbatiminput{../samples/templates/simple1/simple.py} You can see the program output by pointing your browser at \mbox{\url{\cgibindir/alsamp/simple1/simple.py}}. If after installing the program locally you do not see a page displaying the text ``Hello from my simple CGI application!'' then you should look in the web server error log for clues. The location of the error log is specified by the \texttt{ErrorLog} directive in the Apache configuration. On a Debian Linux system the default location is \texttt{/var/log/apache/error.log}. While developing web applications you will become intimately familiar with this file. Knowing how Apache treats a request for a document in the \texttt{cgi-bin} directory is the key to understanding how CGI programs work. Instead of sending the document text back to the browser Apache executes the ``document'' and sends the ``document'' output back to the browser. This simple mechanism allows you to write programs which generate HTML dynamically. If you view the page source from the \texttt{simple1.py} application in your browser you will note that the first two lines of output produced by the program are not present. This is because they are not part of the document. The first line is an HTTP (Hypertext Transfer Protocol) header which tells the browser that the document content is HTML. The second line is blank which signals the end of HTTP headers and the beginning of the document. You can build quite complex programs by taking the simple approach of embedding HTML within your application code. The problem with doing this is that program development and maintenance becomes a nightmare. The essential implementation (or business level) logic is lost within a sea of presentation logic. The impact of embedding HTML in your application can be reduced somewhat by using a package called \texttt{HTMLgen}. \footnote{\texttt{HTMLgen} can be retrieved from \url{http://starship.python.net/crew/friedrich/HTMLgen/html/main.html}. On Debian Linux you can install the \texttt{htmlgen} package.} The other way to make complex web applications manageable is to separate the presentation layer from the application implementation via a templating system. This is also the Albatross way. \section{Your First Albatross Program\label{tug-simple2}} This section rewrites the sample CGI program from the previous section as an Albatross program. The program uses the Albatross templating system to generate the HTML document. The sample program from this section is supplied in the \texttt{samples/templates/simple2} directory and can be installed in your web server \texttt{cgi-bin} directory by running the following commands. \begin{verbatim} cd samples/templates/simple2 python install.py \end{verbatim} All of the HTML is moved into a template file called \texttt{simple.html}. \verbatiminput{../samples/templates/simple2/simple.html} The \texttt{simple.py} program is then rewritten as shown below. \verbatiminput{../samples/templates/simple2/simple.py} You can see the program output by pointing your browser at \mbox{\url{\cgibindir/alsamp/simple2/simple.py}}. This is probably the most simple application that you can write using Albatross. Let's analyse the program step-by-step. This first line imports the Albatross package and places the \class{SimpleContext} into the global namespace. \begin{verbatim} from albatross import SimpleContext \end{verbatim} Before we can use Albatross templates we must create an execution context which will be used to load and execute the template file. The Albatross \class{SimpleContext} object should be used in programs which directly load and execute template files. The \class{SimpleContext} constructor has a single argument which specifies a path to the directory from which template files will be loaded. Before Apache executes a CGI program it sets the current directory to the directory where that program is located. We have installed the template file in the same directory as the program, hence the path \texttt{'.'}. \begin{verbatim} ctx = SimpleContext('.') \end{verbatim} Once we have an execution context we can load template files. The return value of the execution context \method{load_template()} method is a parsed template. \begin{verbatim} templ = ctx.load_template('simple.html') \end{verbatim} Albatross templates are executed in two stages; the first stage parses the template and compiles the embedded Python expressions, the second actually executes the template. To execute a template we call it's \method{to_html()} method passing an execution context. Albatross tags access application data and logic via the execution context. Since the template for the example application does not refer to any application functionality, we do not need to place anything into the context before executing the template. \begin{verbatim} templ.to_html(ctx) \end{verbatim} Template file output is accumulated in the execution context. Unless you use one of the Albatross application objects you need to output your own HTTP headers. \begin{verbatim} print 'Content-Type: text/html' print \end{verbatim} Finally, you must explicitly flush the context to force the HTML to be written to output. Buffering the output inside the context allows applications to trap and handle any exception which occurs while executing the template without any partial output leaking to the browser. \begin{verbatim} ctx.flush_content() \end{verbatim} \section{Introducing Albatross Tags\label{tug-simple3}} In the previous section we presented a simple Albatross program which loaded and executed a template file to generate HTML dynamically. In this section we will place some application data into the Albatross execution context so that the template file can display it. To demonstrate how Albatross programs separate application and presentation logic we will look at a program which displays the CGI program environment. The sample program from this section is supplied in the \texttt{samples/templates/simple3} directory and can be installed in your web server \texttt{cgi-bin} directory by running the following commands. \begin{verbatim} cd samples/templates/simple3 python install.py \end{verbatim} The CGI program \texttt{simple.py} is shown below. \verbatiminput{../samples/templates/simple3/simple.py} The following lines construct a sorted list of all defined environment variables. It makes the display a little nicer if the values are sorted. \begin{verbatim} keys = os.environ.keys() keys.sort() \end{verbatim} The Albatross execution context is constructed with an empty object in the \member{locals} member which is used as a conduit between the application and the toolkit. It is used as the local namespace for expressions evaluated in template files. To make the environment available to the template file we simply assign to an attribute using a name of our choosing which can then be referenced by the template file. \begin{verbatim} ctx.locals.keys = keys ctx.locals.environ = os.environ \end{verbatim} The \class{SimpleContext} constructor save a reference ( in the \member{globals} member) to the global namespace of the execution context to the globals of the code which called the constructor. Now the template file \texttt{simple.html}. Two Albatross tags are used to display the application data; \texttt{} and \texttt{}. \verbatiminput{../samples/templates/simple3/simple.html} You can see the program output by pointing your browser at \mbox{\url{\cgibindir/alsamp/simple3/simple.py}}. The \texttt{} Albatross tag iterates over the list of environment variable names we placed in the \code{keys} value (\code{ctx.locals.keys}). All template file content enclosed by the \texttt{} tag is evaluated for each value in the sequence returned by evaluating the \texttt{expr} attribute. The \texttt{iter} attribute specifies the name of the iterator which is used to retrieve each successive value from the sequence. The toolkit places the iterator object in the \member{locals} member of the execution context. Be careful that you do not overwrite application values by using an iterator of the same name as an application value. The \texttt{} Albatross tag is used to retrieve values from the execution context. The \texttt{expr} attribute can contain any Python expression which can legally be passed to the Python \code{eval()} function when the \var{kind} argument is \texttt{"eval"}. Deciding where to divide your application between implementation and presentation can be difficult at times. In the example above, we implemented some presentation logic in the program; we sorted the list of environment variables. Let's make a modification which removes that presentation logic from the application. The \texttt{simple.py} application is shown below. \verbatiminput{../samples/templates/simple4/simple.py} Now look at the new \texttt{simple.html} template file. By using the Albatross \texttt{} tag we can prepare a sorted list of environment variable names for the \texttt{} tag. \verbatiminput{../samples/templates/simple4/simple.html} You can see the program output by pointing your browser at \mbox{\url{\cgibindir/alsamp/simple4/simple.py}}. The \texttt{} tag compiles the contents of the \texttt{expr} tag by passing \texttt{"exec"} as the \var{kind} argument. This means that you can include quite complex Python code in the attribute. Remember that we want to minimise the complexity of the entire application, not just the Python mainline. If you start placing application logic in the presentation layer, you will be back to having an unmaintainable mess. Just for your information, the \texttt{} tag could have been written like this: \begin{verbatim} \end{verbatim} \subsection{Eliminating the Application\label{tug-naughty}} Let's revisit our first Albatross application with the \texttt{simple.py} sample program in the \texttt{samples/templates/simple5} directory. \verbatiminput{../samples/templates/simple5/simple.py} Now consider the template file \texttt{simple.html}. \verbatiminput{../samples/templates/simple5/simple.html} You can see the program output by pointing your browser at \mbox{\url{\cgibindir/alsamp/simple5/simple.py}}. You will notice that we have completely removed any application logic from the Python program. This is a cute trick for small example programs, but it is definitely a bad idea for any real application. \section{Building a Useful Application\label{tug-content1}} In the previous section we saw how Albatross tags can be used to remove presentation logic from your application. In this section we will see how with a simple program we can serve up a tree of template files. If you look at the output of the \texttt{simple4/simple.py} program you will notice the following lines: \begin{verbatim} REQUEST_URI /cgi-bin/alsamp/simple4/simple.py SCRIPT_FILENAME /usr/lib/cgi-bin/alsamp/simple4/simple.py SCRIPT_NAME /cgi-bin/alsamp/simple4/simple.py \end{verbatim} Now watch what happens when you start appending extra path elements to the end of the URL. Try requesting the following with your browser: \mbox{\url{\cgibindir/alsamp/simple4/simple.py/main.html}}. You should see the following three lines: \begin{verbatim} REQUEST_URI /cgi-bin/alsamp/simple4/simple.py/main.html SCRIPT_FILENAME /usr/lib/cgi-bin/alsamp/simple4/simple.py SCRIPT_NAME /cgi-bin/alsamp/simple4/simple.py \end{verbatim} The interesting thing is that Apache is still using the \texttt{simple4/simple.py} program to process the browser request. We can use the value of the \texttt{REQUEST_URI} environment variable to locate a template file which will be displayed. The sample application in the \texttt{samples/templates/content1} directory demonstrates serving dynamic content based upon the requested URI. The program can be installed in your web server \texttt{cgi-bin} directory by running the following commands. \begin{verbatim} cd samples/templates/content1 python install.py \end{verbatim} The CGI program \texttt{content.py} is shown below. \verbatiminput{../samples/templates/content1/content.py} To demonstrate this application we have three template files; \texttt{main.html}, \texttt{oops.html}, and \texttt{other.html}. First \texttt{main.html}. \verbatiminput{../samples/templates/content1/main.html} Now \texttt{other.html}. \verbatiminput{../samples/templates/content1/other.html} And finally the page for displaying errors; \texttt{oops.html}. \verbatiminput{../samples/templates/content1/oops.html} Test the program by trying a few requests with your browser: \indent \mbox{\url{\cgibindir/alsamp/content1/content.py}} \\ \mbox{\url{\cgibindir/alsamp/content1/content.py/main.html}} \\ \mbox{\url{\cgibindir/alsamp/content1/content.py/other.html}} \\ \mbox{\url{\cgibindir/alsamp/content1/content.py/error.html}} \\ \mbox{\url{\cgibindir/alsamp/content1/content.py/oops.html}} Let's analyse the program step-by-step. The preamble imports the modules we are going to use. \begin{verbatim} #!/usr/bin/python import os from albatross import SimpleContext, TemplateLoadError \end{verbatim} The next part of the program removes the prefix in the \texttt{SCRIPT_NAME} variable from the value in the \texttt{REQUEST_URI} variable. When removing the script name we add one to the length to ensure that the \texttt{"/"} path separator between the script and page is also removed. This is important because the execution context \method{load_template()} method uses \code{os.path.join()} to construct a script filename by combining the \var{base_dir} specified in the constructor and the name passed to the \method{load_template()} method. If any of the path components being joined begin with a \texttt{"/"} then \code{os.path.join()} creates an absolute path beginning at the \texttt{"/"}. If no page was specified in the browser request then we use the default page \texttt{main.html}. \begin{verbatim} script_name = os.environ['SCRIPT_NAME'] request_uri = os.environ['REQUEST_URI'] page = request_uri[len(script_name) + 1:] if not page: page = 'main.html' \end{verbatim} The next section of code creates the Albatross execution context and places the requested filename into the \code{page} local attribute. It then attempts to load the requested file. If the template file does not exist the \method{load_template()} will raise a \code{TemplateLoadError} exception. We handle this by loading the error page \texttt{oops.html}. The error page displays a message which explains that the requested page (saved in the \code{page} variable) does not exist. \begin{verbatim} ctx = SimpleContext('templ') ctx.locals.page = page try: templ = ctx.load_template(page) except TemplateLoadError: templ = ctx.load_template('oops.html') \end{verbatim} Looking at the error page \texttt{oops.html}, you will see a new Albatross tag \texttt{}. \begin{verbatim} You actually requested the error page! Sorry, the page does not exist. \end{verbatim} The \texttt{} tag allows you to conditionally include or exclude template content by testing the result of an expression. Remember that we placed the name of the requested page into the \code{page} variable, so we are able to display different content when the browser actually requests \texttt{oops.html}. Finally, the remainder of the program displays the selected HTML page. \begin{verbatim} templ.to_html(ctx) print 'Content-Type: text/html' print ctx.flush_content() \end{verbatim} \section{Albatross Macros\label{tug-content2}} In the previous section we demonstrated a program which can be used to display pages from a collection of template files. You might recall that the HTML in the template files was very repetitive. In this section you will see how Albatross macros can be used to introduce a common look to all HTML pages. If we look at the \texttt{main.html} template file again you will notice that there really is very little content which is unique to this page. \verbatiminput{../samples/templates/content1/main.html} Using Albatross macros we can place all of the boilerplate into a macro. Once defined, the macro can be reused in all template files. The sample program from this section is supplied in the \texttt{samples/templates/content2} directory and can be installed in your web server \texttt{cgi-bin} directory by running the following commands. \begin{verbatim} cd samples/templates/content2 python install.py \end{verbatim} First consider the macro in the \texttt{macros.html} template file. \verbatiminput{../samples/templates/content2/macros.html} Now we can change \texttt{main.html} to use the macro. \verbatiminput{../samples/templates/content2/main.html} Likewise, the \texttt{other.html} file. \verbatiminput{../samples/templates/content2/other.html} And finally the error page \texttt{oops1.html}. \verbatiminput{../samples/templates/content2/oops.html} We also have to modify the application to load the macro definition before loading the requested pages. \verbatiminput{../samples/templates/content2/content.py} Test the program by trying a few requests with your browser: \indent \mbox{\url{\cgibindir/alsamp/content2/content.py}} \\ \mbox{\url{\cgibindir/alsamp/content2/content.py/main.html}} \\ \mbox{\url{\cgibindir/alsamp/content2/content.py/other.html}} \\ \mbox{\url{\cgibindir/alsamp/content2/content.py/error.html}} \\ \mbox{\url{\cgibindir/alsamp/content2/content.py/oops.html}} The only new line in this program is the following: \begin{verbatim} ctx.load_template('macros.html').to_html(ctx) \end{verbatim} This loads the file which contains the macro definition and then executes it. Executing the macro definition registers the macro with the execution context, it does not produce any output. This means that once defined the macro is available to all templates interpreted by the execution context. The template is not needed once the macro has been registered so we can discard the template file. When you use Albatross application objects the macro definition is registered in the application object so can be defined once and then used with all execution contexts. There is one small problem with the program. What happens if the browser requests \texttt{macros.html}? Suffice to say, you do not get much useful output. The way to handle this problem is to modify the program to treat requests for \texttt{macros.html} as an error. Let's revisit the macro definition in \texttt{macros.html} and see how it works. Albatross macros use four tags; \texttt{}, \texttt{}, \texttt{}, and \texttt{}. \verbatiminput{../samples/templates/content2/macros.html} The \texttt{} tag is used to define a named macro. The \texttt{name} attribute uniquely identifies the macro within the execution context. In our template file we have defined a macro called \texttt{"doc"}. All content enclosed in the \texttt{} tag will be substituted when the macro is expanded via the \texttt{} tag. In all but the most simple macros you will want to pass some arguments to the macro. The place where the arguments will be expanded is controlled via the \texttt{} tag in the macro definition. All macros accept an ``unnamed'' argument which captures all of the content within the \texttt{} tag not enclosed by \texttt{} tags. The unnamed argument is retrieved within the macro definition by using \texttt{} without specifying a \texttt{name} attribute. In our example we used a fairly complex macro. If you are still a bit confused the following sections should hopefully clear up that confusion. \subsection{Zero Argument Macros\label{tug-zeromacro}} The most simple macro is a macro which does not accept any arguments. You might define the location of the company logo within a zero argument macro. \begin{verbatim} \end{verbatim} Then whenever you need to display the logo all you need to do is expand the macro. \begin{verbatim} \end{verbatim} This allows you to define the location and name of your company logo in one place. \subsection{Single Argument Macros\label{tug-singlemacro}} The single argument macro is almost as simple as the zero argument macro. You should always use the unnamed argument to pass content to a single argument macro. If you look at news sites such as \url{http://slashdot.org/} you will note that they make heavy use of HTML tricks to improve the presentation of their pages. Single argument macros can be extremely useful in simplifying your template files by moving the complicated HTML tricks into a separate macro definition file. Let's look at the top story at \url{http://slashdot.org/} to illustrate the point. The title bar for the story is constructed with the following HTML (reformatted so it will fit on the page). \begin{verbatim}
Another Nasty Outlook Virus Strikes
\end{verbatim} As you can see, most of the HTML is dedicated to achieving a certain effect. If you were using Albatross to construct the same HTML you would probably create a macro called \texttt{story-title} like this: \begin{verbatim}
\end{verbatim} Then you could generate the story title HTML like this: \begin{verbatim} Another Nasty Outlook Virus Strikes \end{verbatim} Since stories are likely to be generated from some sort of database it is more likely that you would use something like this: \begin{verbatim} \end{verbatim} \subsection{Multiple Argument Macros\label{tug-multimacro}} Multiple argument macros are effectively the same as single argument macros that accept additional named arguments. The following example shows a macro that defines multiple arguments and some template to expand the macro. \begin{verbatim} arg1 is "" and arg2 is "" and the default argument is "". This is arg2 content the arg1 content default argument \end{verbatim} When the above template is executed the following output is produced. \begin{verbatim} arg1 is "arg1 content" and arg2 is "arg2 content" and the default argument is "This is the default argument". \end{verbatim} \subsection{Nesting Macros\label{tug-nestmacro}} Let's revisit the \url{http://slashdot.org/} HTML for a story and see how to use macros to assist in formatting the entire story summary. Consider the rest of the story summary minus the header (reformatted to fit on the page): \begin{verbatim} Microsoft Posted by timothy on Sunday July 22, @11:32PM
from the hide-the-children-get-the-gun dept.
Goldberg's Pants writes: "ZDNet and Wired are both reporting on a new virus that spreads via Outlook. Nothing particularly original there, except this virus is pretty unique both in how it operates, and what it does, such as emailing random documents from your harddrive to people in your address book, and hiding itself in the recycle bin which is rarely checked by virus scanners." I talked by phone with a user whose machine seemed determined to send me many megabytes of this virus 206k at a time; he was surprised to find that his machine was infected, as most people probably would be. The anti-virus makers have patches, if you are running an operating system which needs them. \end{verbatim} The first task is to simplify is the topic specific image. There are a finite number of topics, and the set of topics does not change much over time. We could make effective use of the Albatross \texttt{} tag to simplify this (\texttt{} is discussed in section \ref{tug-lookup}): \begin{verbatim} Microsoft : \end{verbatim} Then to display the HTML for the story topic all we would need to do is the following: \begin{verbatim} \end{verbatim} Next we will simplify the acknowledgement segment: \begin{verbatim} Posted by on
from the dept.
\end{verbatim} Finally we can bring all of these fragments together like this: \begin{verbatim} Posted by on
from the dept.
\end{verbatim} Having defined the macro for formatting a story summary we can format a list of story summaries like this: \begin{verbatim} \end{verbatim} Notice that all of the macros are referring directly to the \code{story} object which contains all of the data which pertains to one story. If you find that your macros are referring to application data by name then you should consider using a function instead. Once functions have been implemented you might be able to use them as well as consider them. \section{Lookup Tables\label{tug-lookup}} The example macro used in the previous section introduced a new Albatross tag called \texttt{}. In this section we will look at the tag in more detail. The \texttt{} tag provides a mechanism for translating internal program values into HTML for display. This is another way which Albatross allows you to avoid placing presentation logic in your application. In a hypothetical bug tracking system we have developed we need to display information about bugs recorded in the system. The severity of a bug is defined by a collection of symbols defined in the \module{btsvalues} module. \begin{verbatim} TRIVIAL = 0 MINOR = 1 NORMAL = 2 MAJOR = 3 CRITICAL = 4 \end{verbatim} While the integer severity levels are OK for use as internal program values they are not very useful as displayed values. The obvious way to display a bug severity would be via the \texttt{} tag. \begin{verbatim} Severity: \end{verbatim} Unfortunately, this would yield results like this: \begin{verbatim} Severity: 1 \end{verbatim} By using the \texttt{lookup} attribute of the \texttt{} tag we are able to use the internal value as an index into a lookup table. The corresponding entry from the lookup table is displayed instead of the index. The following is a table which translates the internal program value into HTML for display. \begin{verbatim} Trivial Minor Normal Major Critical \end{verbatim} The \module{btsvalues} module must be visible when the \texttt{} tag is executed. You can place the \module{btsvalues} module in the the global namespace of the execution context by importing the \module{btsvalues} module in the same module which creates the \class{SimpleContext} execution context. When using other Albatross execution contexts you would need to import \module{btsvalues} in the module which called \method{run_template()} or \method{run_template_once()} to execute the \texttt{} tag. We invoke the lookup table by using the \texttt{lookup} attribute of the \texttt{} tag. \begin{verbatim} Severity: \end{verbatim} Note that the \module{btsvalues} module does not need to be in the namespace at this point. The \texttt{expr} attributes in the \texttt{} tags are evaluated once when the \texttt{} tag is executed. The \texttt{} tag has the same runtime properties as the \texttt{} tag. You have execute the tag to register the lookup table with the execution context. Once the lookup table has been registered it is available to all template files executed in the same execution context. When using Albatross application objects the lookup table is registered in the application object so can be defined once and then used with all execution contexts. Each entry in the lookup table is enclosed in a \texttt{} tag. The \texttt{expr} attribute of the \texttt{} tag defines the expression which will be evaluated to determine the item's table index. As explained above, the expression is evaluated when the lookup table is executed, not when the table is loaded, or looked up (with the rare exception of a lookup being used earlier in the same template file that it is defined). It is important to note that the content enclosed by the \texttt{} tag is executed when the item is retrieved via an \texttt{} tag. This allows you to place Albatross tags inside the lookup table that are designed to be evaluated when the table is accessed. Finally, any content not enclosed by an \texttt{} tag will be returned as the result of a failed table lookup. \section{White Space Removal in Albatross\label{tug-white}} If you were paying close attention to the results of expanding the macros we created in section \ref{tug-content2} you would have noticed that nearly all evidence of the Albatross tags has disappeared. It is quite obvious that the Albatross tags are no longer present. A little less obvious is removal of whitespace following the Albatross tags. Let's have a look at the \texttt{"doc"} macro again. \verbatiminput{../samples/templates/content2/macros.html} We can get a capture the result of expanding the macro by firing up the Python interpreter to manually exercise the macro. \verbatiminput{doctest/templ-white1} Not only have the \texttt{} and \texttt{} tags been removed, the whitespace that follows those tags has also been removed. By default Albatross removes all whitespace following an Albatross tag that begins with a newline. This behaviour should be familiar to anyone who has used PHP. Looking further into the result you will note that the \texttt{} tag is aligned with the \texttt{} tag above it. This is the result of performing the \texttt{} substitution (which had no content) and removing all whitespace following the \texttt{} tag. This whitespace removal nearly always produces the desired result, though it can be a real problem at times. \verbatiminput{doctest/templ-white2} The whitespace removal has definitely produced an undesirable result. You can always get around the problem by joining all of the \texttt{} tags together on a single line. Remember that the whitespace removal only kicks in if the whitespace begins with a newline character. For our example this would be a reasonable solution. \verbatiminput{doctest/templ-white3} The other way to defeat the whitespace removal while keeping each \texttt{} tag on a separate line would be to place a single trailing space at the end of each line. This would be a very bad idea because the next person to modify the file might remove the space without realising how important it was. Note that there are trailing spaces at the end of each line in the \code{text} assignment. This should give you a clue about how bad this technique is. \verbatiminput{doctest/templ-white4} A much better way to solve the problem is to explicitly tell the Albatross parser that you want it to do something different with the whitespace that follows the first two \texttt{} tags. \verbatiminput{doctest/templ-white5} The above variation has told the Albatross interpreter to only strip the trailing newline, leaving intact the indent on the following line. The following table describes all of possible values for the \texttt{whitespace} attribute. \begin{longtableii}{l|l}{textrm}{Value}{Meaning} \lineii{\code{'all'}}{Keep all following whitespace.} \lineii{\code{'strip'}}{Remove all whitespace - this is the default.} \lineii{\code{'indent'}}{Keep indent on following line.} \lineii{\code{'newline'}}{Remove all whitespace and substitute a newline.} \end{longtableii} Note that when the trailing whitespace does not begin with a newline the \code{'strip'} and \code{'indent'} whitespace directives are treated exactly like \code{'all'}. \section{Using Forms to Receive User Input\label{tug-form1}} Nearly all web applications need to accept user input. User input is captured by using forms. We will begin by demonstrating the traditional approach to handling forms, then in later sections you will see how Albatross can be used to eliminate the tedious shuffling of application values in and out of form elements. Let's start with a program that presents a form to the user and displays to user response to the form. The sample program from this section is supplied in the \texttt{samples/templates/form1} directory and can be installed in your web server \texttt{cgi-bin} directory by running the following commands. \begin{verbatim} cd samples/templates/form1 python install.py \end{verbatim} The CGI program \texttt{form.py} is shown below. \verbatiminput{../samples/templates/form1/form.py} There are no surprises here, we are using the standard Python \module{cgi} module to capture the browser request. We want to display the contents of the request so it is placed into the execution context. The \texttt{form.html} template file is used to display present a form and display the browser request. \verbatiminput{../samples/templates/form1/form.html} We have placed the form display logic in a separate template file because we wish to reuse that particularly nasty piece of template. The form display template is contained in \texttt{form-display.html}. If you do not understand how the \class{FieldStorage} class from the \module{cgi} module works, do not try to understand the following template. Refer to section \ref{tug-form2} which contains a small explanation of the \class{FieldStorage} class and some Python code that performs the same task as the template. \verbatiminput{../samples/templates/form1/form-display.html} You can see the program output by pointing your browser at \mbox{\url{\cgibindir/alsamp/form1/form.py}}. You will notice that each time you submit the page it comes back with all of the fields cleared again. Typically web applications that generate HTML dynamically will hand construct \texttt{} tags and place application values into the \texttt{value} attributes of the input tags. Since we are using Albatross templates we do not have the ability to construct tags on the fly without doing some very nasty tricks. Fortunately Albatross supplies some tags that we can use in place of the standard HTML \texttt{} tags. \section{Using Albatross Input Tags\label{tug-form2}} In the previous section we saw how web applications can capture user input from browser requests. This section explains how Albatross \texttt{} tags can be used to take values from the execution context and format them as \texttt{value} attributes in the HTML \texttt{} tags sent to the browser. The sample program from this section is supplied in the \texttt{samples/templates/form2} directory and can be installed in your web server \texttt{cgi-bin} directory by running the following commands. \begin{verbatim} cd samples/templates/form2 python install.py \end{verbatim} The first change is in the \texttt{form.html} template file. \verbatiminput{../samples/templates/form2/form.html} We need to place some values into the execution context so that the Albatross \texttt{} tags can display them. The easiest thing to do is to place the browser submitted values into the execution context. The documentation for the Python \module{cgi} module is quite good so I will not try to explain the complete behaviour of the \class{FieldStorage} class. The only behaviour that we need to be aware of for our program is what it does when it receives more than one value for the same field name. The \class{FieldStorage} object that captures browser requests behaves like a dictionary that is indexed by field name. When the browser sends a single value for a field, the dictionary lookup yields an object containing the field value in the \member{value} member. When the browser sends more than one value for a field, the dictionary lookup returns a list of the objects used to represent a single field value. Using this knowledge, the \texttt{form.py} program can be modified to merge the browser request into the execution context. \verbatiminput{../samples/templates/form2/form.py} You can see the program output by pointing your browser at \mbox{\url{\cgibindir/alsamp/form2/form.py}}. You will notice that your input is sent back to you as the default value of each form element. When you use Albatross application objects the browser request is automatically merged into the execution context for you. \section{More on the \texttt{} Tag\label{tug-form3}} In the previous section we performed a direct translation of the standard HTML input tags to the equivalent Albatross tags. In addition to a direct translation from the HTML form, the \texttt{} tag supports a dynamic form. In all but the most simple web application you will occasionally need to define the options in a \texttt{ \end{verbatim} \subsubsection{Enforce only one definition of macros and lookups\label{rel-1.30-checkdefs}} Since macros and lookups are an application global resource, they can only be defined once per application, however this was not previously enforced. Redefinition of macros or lookups will now result in an ApplicationError exception. \subsubsection{In-line expansion of \texttt{}\label{rel-1.30-inlinelookup}} The \texttt{} tag can now be optionally expanded in place. If the tag has an expr= attribute, this will be evaluated and used as the value to look up, and the results of the lookup substituted for the tag. Functionality of named lookups remains unchanged. \subsubsection{New \texttt{} tag\label{rel-1.30-require}} A new tag has been added to allow templates to assert that specific Albatross features are available, or templating scheme version number is high enough. For instance, the addition of the "Any Tag" functionality has resulting in the templating version incrementing from 1 to 2. \subsubsection{Set Cache-Control header\label{rel-1.30-cachecontrol}} \texttt{Cache-Control: no-cache} is now set in addition to \texttt{Pragma: no-cache}. \texttt{Cache-Control} was introduced in HTTP/1.1, prior to this the same effect was achieved with \texttt{Pragma}. Some browsers change their behaviour depending on whether the page was delivered via HTTP/1.1 or HTTP/1.0. \subsubsection{Simplified Session Cookie handling\label{rel-1.30-simplecookie}} Session cookie handling has been simplified. \subsection{Bug Fixes\label{rel-1.30-bug}} \subsubsection{FastCGI finalisation\label{rel-1.30-fcgifinalise}} FastCGI apps were not being explicitly finalised, relying instead on their object destructor, with the result that writing application output (or errors) would be indefinitely deferred if object cycles existed. We now call \texttt{fcgi.Finish()} from the fcgiapp \texttt{Request.return_code()} method. \subsubsection{Delete traceback objects\label{rel-1.30-tbcycle}} When handling exceptions, the traceback is now explicitly deleted from the local namespace to prevent cycles (otherwise the garbage collection of other objects in the local namespace will be delayed). \subsubsection{\texttt{} fixes\label{rel-1.30-alselect}} Two fixes to the \texttt{} tag: the albatross-specific "list" attribute was leaking into resulting markup, and the use of the "expr" attribute would result in invalid markup being emitted. \subsubsection{Illegal placement of \texttt{} tag\label{rel-1.30-hiddeninput}} Thanks to Robert Fendt for picking this up: the Albatross-generated hidden field input element must not appear naked inside a form element for strict HTML conformance. The solution is to wrap the input elements in div. \subsubsection{Allow BranchingSessions to be deleted\label{rel-1.30-branchsessdel}} BranchingSession sessions could not be "deleted" because each interaction is a separate session. The solution implemented is to add a dummy "session" shared by all branches, which is deleted when one branch "logs out". \section{Release 1.20\label{rel-1.20}} This section describes the changes in release 1.20 of Albatross that were made since release 1.11. \subsection{Functional Changes\label{rel-1.20-func}} \subsubsection{New \texttt{BranchingSessionContext}\label{rel-1.20-branchingsession}} A persistent problem with server-side sessions is the browser state getting out of synchronisation with the application state. This occurs when the user reloads the page or uses the "back" button. A new \texttt{BranchingSessionContext} application context class has been added that attempts to work around this problem by creating a new server-side session for every interaction with the browser. The unique session identifier is stored in a hidden form field, rather than a cookie. The new Context class is intended to be used with the server-side Application classes, and provides a similar experience to storing the context in a hidden form field, without the overhead and security issues of sending the context on a round-trip through the user's browser. No effort is made at this time to control the resources used by these server-side sessions, other than expiring them after \texttt{session_age} seconds. \subsubsection{Improved Request classes\label{rel-1.20-refactorrequest}} The Request classes provide the interface between specific application deployment models (CGI, FastCGI, mod_python, etc), and the Albatross application. These classes have been refactored to extract common functionality into a new RequestBase class. The Request classes also now have methods for passing status back to browser. \subsubsection{Page Module loading\label{rel-1.20-pagemodule}} The page module loader in PageModuleMixin has been reimplemented so that it does not pollute \code{sys.modules}. Page modules are now loaded into a synthetic module's namespace, rather than the global module namespace. This will break code that defined classes in page modules and placed instances of those classes into the session. \subsubsection{Multi-instance response headers now supported\label{rel-1.20-responsemultiheader}} Some HTTP headers can appear multiple times (for example Set-Cookie) - the response handling has been modified to allow multiple instances of a header. \texttt{ResponseMixin.get_header()} now returns a list of strings, rather than just a string. The httpdapp module has also been updated to allow multiple instances of a header, keeping headers in a list rather than a dictionary. \subsubsection{simpler \texttt{req_equals()} matching with image maps\label{rel-1.20-reqequalsimgmap}} \texttt{ctx.req_equals(name)} now checks for \texttt{name.x} if \texttt{name} is not found. This makes using image maps as buttons easier (from Michael C. Neel). \subsection{Bug Fixes\label{rel-1.20-bug}} \subsubsection{\texttt{redirect_url()} fixes\label{rel-1.20-fixredirecturl}} Under some circumstances, \texttt{redirect_url()} would redirect to incorrect or invalid URLs (for example, an https app would redirect to http) - the URI parsing has been refactored, and this bug has been fixed. Tests were also added for the refactored URI parsing. \subsubsection{Improved request status handling\label{rel-1.20-requeststatus}} \begin{enumerate} \item Symbolic names are now defined for the RFC1945 status header values, such as HTTP_OK, HTTP_MOVED_PERMANENTLY, HTTP_MOVED_TEMPORARILY and HTTP_INTERNAL_SERVER_ERROR \item The Request classes (deployment model adaptors) and Application \texttt{run()} method have been updated to correctly pass the returned status back to the client. \end{enumerate} \subsubsection{Response header matching now case-insensitive\label{rel-1.20-responseheadercase}} Response header names were being matched in a case-sensitive way - this was incorrect and has been fixed. \subsubsection{Cookie handling fixes\label{rel-1.20-cookiefixes}} \begin{enumerate} \item A Cookie path bug was noticed when Albatross applications were used with the Safari browser. \texttt{absolute_base_url()} was generating a trailing slash on the returned application URL (so /path/app.cgi/ instead of /path/app.cgi). This was causing problems for requests like /path/app.cgi?blah in that Safari did not send the cookie (probably correctly). \item When an application was accessed via https, the \texttt{secure} attribute on any resulting cookies was not being set. This attribute marks the cookie to be only returned via an https connection. The \texttt{secure} attribute is now set. \item Cookie max-age was being allowed to default - this is now explicitly set to match the configured session age (from the Application \texttt{session_age} parameter). \end{enumerate} \section{Release 1.11\label{rel-1.11}} This section describes the changes in release 1.11 of Albatross that were made since release 1.10. \subsection{Functional Changes\label{rel-1.11-func}} \subsubsection{\texttt{/} consistency\label{rel-1.11-alselectconsistency}} \texttt{} handling of \texttt{name}, \texttt{expr}, \texttt{valueexpr} and \texttt{value} attributes has been made consistent with that of \texttt{}. \subsubsection{\texttt{absolute_base_url} method\label{rel-1.11-absbaseurl}} New method \method{absolute_base_url()} has been added to the \class{AppContext}. \subsubsection{\texttt{al-httpd} enhancements\label{rel-1.11-httpd}} Matt Goodall has continued to improve the capabilities of the \texttt{al-httpd} program and \module{httpdapp.py} so that it is now possible to run all of the CGI based sample applications. You can now initialise the \code{static_resources} from the command line. For example, the tree samples can be executed to serve up their images like this: \begin{verbatim} $ cd samples/tree2 $ al-httpd tree.app 8080 /alsamp/images ../images/ \end{verbatim} \subsubsection{XHTML fixes\label{rel-1.11-xhtml}} The \texttt{} and \texttt{} tags now output XHTML compliant end tags. \subsection{Bug Fixes\label{rel-1.11-bug}} \subsubsection{\texttt{mod_python} support\label{rel-1.11-modpython}} Greg Bond fixed a \module{cgiapp} field handling incompatibility with \texttt{mod_python} 3. \subsubsection{\texttt{get_servername()} support\label{rel-1.11-servername}} The \method{get_servername()} method of the \module{cgiapp} and \module{fcgiapp} \class{Request} classes now use the \texttt{HTTP_HOST} environment variable rather than \texttt{SERVER_NAME}. \subsubsection{Multiple cookies\label{rel-1.11-cookie}} All session cookies now include a path attribute. This prevents multiple redundant cookies being set for all URI paths in an application. \section{Release 1.10\label{rel-1.10}} This section describes the changes in release 1.10 of Albatross that were made since release 1.01. \subsection{Functional Changes\label{rel-1.10-func}} \subsubsection{FastCGI support\label{rel-1.10-fastcgi}} Matt Goodall developed support for deployment of applications via FastCGI. FastCGI applications import their \class{Request} class from \module{albatross.fcgiapp}. \subsubsection{Standalone BaseHTTPServer support\label{rel-1.10-standalone}} Matt Goodall developed support for standalone deployment of applications via the standard Python \module{BaseHTTPServer} module. The \texttt{al-httpd} program can be used to deploy a CGI application as a standalone \module{BaseHTTPServer} server. \subsubsection{Exception Classes\label{rel-1.10-exc}} All Albatross exceptions have been redefined to indicate the source of the error; user (\exception{UserError}), programmer (\exception{ApplicationError}), or Albatross itself (\exception{InternalError}). The \exception{ServerError} exception reports problems related to the session server, \exception{SecurityError} reports either a programmer error, or a user attempt to access restricted values in the execution context, and \exception{TemplateLoadError} reports failures to load templates. All of the exceptions inherit from \exception{AlbatrossError}. The \module{albatross.common} module defines the exceptions. \subsubsection{Response Header Management\label{rel-1.10-headers}} All response header management has been moved to the \class{ResponseMixin} class. The \class{AppContext} class now inherits from \class{ResponseMixin}. The \class{Request} class no longer tracks whether or not headers have been sent. \class{ResponseMixin} provides the ability to set, get, delete, and send headers. Headers are automatically sent when the application sends any content to the browser. The \method{write_headers()} method has been deleted from the following classes; \class{SimpleAppContext}, \class{SessionAppContext}, \class{SessionServerContextMixin}, \class{SessionFileContextMixin}, \class{SessionFileAppContext}. For \class{SessionServerContextMixin} and \class{SessionFileContextMixin} the \texttt{Set-Cookie} header is set when session is created or loaded. \subsubsection{HTTP Response Codes\label{rel-1.10-response}} The \method{Application.run()} method no longer unconditionally returns an HTTP response code of \texttt{200}. The returned response code is retrieved from the \class{Request.status()} method. You can call the \method{Request.set_status()} method to override the default HTTP response code of \code{200}. \subsubsection{File Uploading\label{rel-1.10-upload}} The \texttt{} tag now supports \texttt{type="file"} input tags. When you use file input tags the enclosing \texttt{} tag automatically adds the \texttt{enctype="multipart/form-data"} attribute to the generated \texttt{
} tag. The \module{albatross.cgiapp} and \module{albatross.apacheapp} modules define a \class{FileField} class which provides access to uploaded files. During request merging the \method{Request.field_file()} method returns instances of \class{FileField} for uploaded files. \subsubsection{Session Changes\label{rel-1.10-session}} The \method{Application.run()} method now saves the session before flushing the response to the browser. This allows applications to support dynamically generated images. The \method{SessionBase.add_session_vars()} method now raises an \exception{ApplicationError} exception if you attempt to add variables to the session which do not exist in the local namespace. The \method{SessionBase.default_session_var()} method allows you to add a variable to the session and place it in the local namespace at the same time. Session saving previously silently removed session values which could not be pickled. Now unpickleable values are reported via an \exception{ApplicationError} exception. Errors handling and reporting during session loading has been improved. \subsubsection{Exception Formatting and Handling\label{rel-1.10-excfmt}} The exception formatting in \method{Application.handle_exception()} has been moved into the \method{format_exception()} method. Applications can perform their own exception formatting and/or send formatted exceptions to locations other than the browser. \subsubsection{Unicode\label{rel-1.10-unicode}} The \method{ExecuteMixin.write_content()} method now converts unicode to UTF-8. \subsubsection{Execution Context Available to Template Expressions\label{rel-1.10-ctx}} During \method{NamespaceMixin.eval_expr()} the execution context is temporary placed into the local namespace as the variable \var{__ctx__}. \subsubsection{Request Merging\label{rel-1.10-merging}} \method{NamespaceMixin.set_value()} ignores attempts to set Albatross iterators that are not present in the namespace. \method{NamespaceMixin.set_value()} produces a nice syntax error like report when an illegal field name is used. \subsubsection{Locating Globals for Template Expressions\label{rel-1.10-globals}} The \function{_caller_globals()} function has been changed to use the name of a function rather a stack frame count. This is used by the methods \method{AppContext.run_template()}, \method{AppContext.run_template_once()}, \method{RandomPageModuleMixin.display_response()}, and \method{SimpleContext.__init__()} to locate the module whose globals will be used as the global namespace for evaluating template expressions. \subsubsection{Tree Handling\label{rel-1.10-tree}} The \texttt{} tag now has a \texttt{single} attribute which enables the single select mode. In single select mode, selecting a node automatically deselects any previously selected node. The \texttt{} tag now supports the \texttt{treefold="expr"}, \texttt{treeselect="expr"}, and \texttt{treeellipsis="expr"} attributes. The expression specifies a tree node that is used to construct a tree iterator input value. The following methods have been added to the \class{LazyTreeIterator} class; \method{load_children()}, \method{is_selected()}, \method{select_alias()}, \method{open_alias()}. \subsubsection{Lookup Evaluation\label{rel-1.10-lookup}} The \texttt{} tag now registers the lookup during template parsing rather than during evaluation. This allows template code to make use of lookups that are defined later in the same file. The item dictionary is created the first time that the lookup is used rather than when the template is interpreted. \subsubsection{Macro Argument Evaluation\label{rel-1.10-macro}} Macro arguments are now evaluated when they are referenced rather than before they are passed to a macro. This removes a limitation where you could not define macros including \texttt{} tags that retrieved \texttt{} tags from their arguments. Previously the \texttt{} tags passed as macro arguments would have been evaluated outside of the context of the form defined in the macro. This effectively made the input tags invisible to the form recorder. \subsubsection{\texttt{a |-a | |-a | \-b \-b |-a | \-a | |-a | \-b |-b \-c |-a \-b \end{verbatim} The \texttt{alias} handling uses the fact that all Python objects are stored by reference. It obtains a reference to an existing object by resolving an expression and stores that reference under a new name. Since both the original expression and the new name are the same reference, the toolkit can modify the object referenced by the original expression by using the new name. Looking further into the \texttt{samples/tree/tree1.py} code you will note that the tree being iterated is generated once and placed into the session. This ensures that the alias names generated always contain references to the nodes in the tree. If the tree was not entered into the session but was generated from scratch every request, the nodes referenced in the alias names would not be the same nodes as those in the tree so all input would be lost. \subsubsection{\texttt{checked} attribute\label{tag-input-checked}} This attribute is generated in \texttt{radio} and \texttt{checkbox} input field types if the generated \texttt{value} (\ref{tag-input-value}) attribute matches the comparison value from \texttt{valueexpr} (\ref{tag-input-valueexpr}) (or the literal \code{'on'} for the \texttt{checkbox} input field type). Refer to the documentation for individual input types for more details. \subsubsection{\texttt{expr="..."} attribute\label{tag-input-expr}} For \texttt{text}, \texttt{password}, \texttt{submit}, \texttt{reset}, \texttt{hidden}, and \texttt{button} input field types the expression in the \texttt{expr} attribute is evaluated and the result is used to generate the \texttt{value} (\ref{tag-input-value}) attribute. If the result is \code{None} then no \texttt{value} attribute will be written. For \texttt{radio} and \texttt{checkbox} input field types the expression in the \texttt{expr} attribute is evaluated and the result is compared with the generated \texttt{value} attribute to determine whether or not the \texttt{checked} (\ref{tag-input-checked}) attribute should be written. Refer to the documentation for individual input types for more details. \subsubsection{\texttt{list} attribute\label{tag-input-list}} If you are using an execution context that inherits from the \class{NameRecorderMixin} (nearly all do --- see chapter \ref{pack-overview}) then the execution context will raise a \exception{ApplicationError} exception if multiple instances of some types of input tag with the same name are added to a form. The \texttt{list} attribute of the \texttt{} tag is used indicate that multiple instances are intentional. The presence of the \texttt{list} attribute on an \texttt{} tag makes the request merging in the \class{NameRecorderMixin} class place any browser request values for the field into a list (field not present is represented by the empty list). The attribute must not be used for input field types \texttt{radio}, \texttt{submit}, and \texttt{image}. The attribute is ignored for the \texttt{file} input field type. \subsubsection{\texttt{name="..."} attribute\label{tag-input-name}} When determining the generated \texttt{name} attribute the tag looks for a number of attributes. Any supplied \texttt{name} attribute will be ignored if any of the following attributes are present; \texttt{prevpage}, \texttt{nextpage} (\ref{tag-input-page}), \texttt{treefold}, \texttt{treeselect}, \texttt{treeellipsis} (\ref{tag-input-tree}), \texttt{alias} (\ref{tag-input-alias}), or \texttt{nameexpr} (\ref{tag-input-nameexpr}). All of the attributes that automatically generate names and are looked up in the above sequence. The first of those attributes found will be used to determine the name used in the tag. \subsubsection{\texttt{nameexpr="..."} attribute\label{tag-input-nameexpr}} This attribute is ignored if any of the following attributes are present; \texttt{prevpage}, \texttt{nextpage} (\ref{tag-input-page}), \texttt{treefold}, \texttt{treeselect}, \texttt{treeellipsis} (\ref{tag-input-tree}), or \texttt{alias} (\ref{tag-input-alias}). The expression in the value of the \texttt{nameexpr} attribute is evaluated to determine the generated \texttt{name} (\ref{tag-input-name}) attribute. One shortcoming of the \texttt{alias} attribute is that you can only perform input on object attributes. The \texttt{nameexpr} enables you to perform input on list elements. \verbatiminput{doctest/tags-input-nameexpr} When the browser request is merged into the execution context the names elements of the \texttt{names} list will be replaced. \subsubsection{\texttt{node="..."} attribute\label{tag-input-node}} The \texttt{node} attribute is used in conjunction with the \texttt{treeselect}, \texttt{treefold} and \texttt{treeellipsis} (\ref{tag-input-tree}) attributes. It is ignored otherwise. When this attribute is present the node identified by evaluating the expression in the attribute value will be used when generating the \texttt{name} attribute. The \texttt{name} (\ref{tag-input-name}) attribute is generated as follows: \verbatiminput{doctest/tags-input-treeselect-node} Refer to the documentation for \texttt{treeselect}, \texttt{treefold} and \texttt{treeellipsis} (\ref{tag-input-tree}) attributes for more information. \subsubsection{\texttt{noescape} attribute\label{tag-input-noescape}} The \texttt{noescape} attribute can be used in conjunction with the \texttt{text}, \texttt{password}, \texttt{submit}, \texttt{reset}, \texttt{hidden}, and \texttt{button} input fields, it is ignored otherwise. When this attribute is present the \texttt{value} (\ref{tag-input-value}) attribute will not be escaped. \verbatiminput{doctest/tags-input-noescape} \subsubsection{\texttt{prevpage="..."} and \texttt{nextpage="..."} attributes\label{tag-input-page}} The \texttt{prevpage} and \texttt{nextpage} attributes respectively select the previous and next pages of an \texttt{} \class{ListIterator} (\ref{tag-for-listiter}). An attribute value must be supplied that specifies the name of the iterator. The \texttt{name} (\ref{tag-input-name}) attribute is generated as follows: \verbatiminput{doctest/tags-input-nextpage} When merging the browser request the \method{NamespaceMixin.set_value()} (\ref{mixin-namespace}) method looks for field names that contain commas. These names are split into \emph{operation}, \emph{iterator}, and optional \emph{value} then the \method{set_backdoor()} method of the identified iterator is invoked. During request merging the above example will execute code equivalent to the following. \begin{verbatim} ctx.locals.i.set_backdoor('nextpage', 'nextpage,i') \end{verbatim} \subsubsection{\texttt{treeselect="..."}, \texttt{treefold="..."} and \texttt{treeellipsis="..."} attributes\label{tag-input-tree}} The \texttt{treeselect}, \texttt{treefold}, and \texttt{treeellipsis} attributes respectively select, open/close, or expand the ellipsis of an \texttt{} node via a \class{LazyTreeIterator} (\ref{tag-tree-lazytreeiter}) or \class{EllipsisTreeIterator} (\ref{tag-tree-ellipsistreeiter}) iterator. These attributes are ignored if any of the following attributes are present; \texttt{prevpage}, or \texttt{nextpage} (\ref{tag-input-page}). An attribute value must be supplied that specifies the name of the \class{LazyTreeIterator} iterator. The \texttt{name} (\ref{tag-input-name}) attribute is generated as follows: \verbatiminput{doctest/tags-input-treeselect} When merging the browser request the \method{NamespaceMixin.set_value()} (\ref{mixin-namespace}) method looks for field names that contain commas. These names are split into \emph{operation}, \emph{iterator}, and optional \emph{value} then the \method{set_backdoor()} method of the identified iterator is invoked. During request merging the above example will execute code equivalent to the following. \begin{verbatim} ctx.locals.n.set_backdoor('treeselect', 'ino81489', 'treeselect,n,ino81489') \end{verbatim} The ``ino81489'' string is generated by calling the \method{albatross_alias()} method for the tree node. If the \texttt{node}(\ref{tag-input-node}) attribute is not specified, the node referenced by the current value of the iterator will be used to determine the alias. \subsubsection{\texttt{value="..."} attribute\label{tag-input-value}} When determining the generated \texttt{value} attribute the tag looks for a number of attributes. Any supplied \texttt{value} attribute will be ignored if either \texttt{expr} (\ref{tag-input-expr}) or \texttt{valueexpr} (\ref{tag-input-valueexpr}) attributes (depending upon input type) present. If no \texttt{expr}, \texttt{valueexpr}, or \texttt{value} attribute is present then the value identified by the generated \texttt{name} (\ref{tag-input-name}) attribute will be taken from the local namespace. If this value is \code{None} then no \texttt{value} attribute will be written. The name used to look into the local namespace is the result of evaluating all \texttt{name} related attributes. For input field types \texttt{radio} and \texttt{checkbox} the \texttt{valueexpr} attribute if present takes priority over and specified \texttt{value} attribute. Refer to the documentation for individual input types for more details. \subsubsection{\texttt{valueexpr="..."} attribute\label{tag-input-valueexpr}} This attribute is used to generate a \texttt{value} (\ref{tag-input-value}) attribute for \texttt{radio} and \texttt{checkbox} input field types. It is ignored for in all other cases. Refer to the documentation for individual input types for more details. \subsubsection{\texttt{type="..."} attribute (text, password, submit, reset, hidden, button)\label{tag-input-generic}} The tag determines the generated \texttt{name} (\ref{tag-input-name}) from the \texttt{name} related attributes. To determine the generated \texttt{value} (\ref{tag-input-value}) attribute the tag first looks for an \texttt{expr} (\ref{tag-input-expr}) attribute, then a \texttt{value} attribute, and then if that fails it looks up the generated \texttt{name} in the execution context. If the generated \texttt{name} contains a non-empty value it will be written as the \texttt{name} attribute. If the generated \texttt{value} is not \code{None} it will be escaped and written as the \texttt{value} attribute. Escaping values makes all \texttt{\&}, \texttt{<}, \texttt{>}, and \texttt{"} characters safe. For example: \verbatiminput{doctest/tags-input-generic} After writing all tag attributes the execution context \method{input_add()} method is called with the following arguments; input field type (\code{'text'}, \code{'password}, \code{'submit}, \code{'reset}, \code{'hidden}, or \code{'button'}), the generated \texttt{name}, the generated \texttt{value}, and a flag indicating whether or not the \texttt{list} (\ref{tag-input-list}) attribute was present. Application code handling browser requests typically determines the \texttt{submit} input pressed by the user via the execution context \method{req_equals()} method. The \method{req_equals()} method simply tests that the named input is present in the browser request and contains a non-empty value. For example: \begin{verbatim} def page_process(ctx): if ctx.req_equals('login'): user = process_login(ctx.locals.username, ctx.locals.passwd) if user: ctx.locals._user = user ctx.add_session_vars('_user') ctx.set_page('home') \end{verbatim} \subsubsection{\texttt{type="..."} attribute (radio)\label{tag-input-radio}} The tag determines the generated \texttt{name} (\ref{tag-input-name}) from the \texttt{name} related attributes. Then an internal comparison value is determined by evaluating the \texttt{expr} (\ref{tag-input-expr}) attribute if it is present, or by looking up the generated \texttt{name} in the execution context. If the comparison value equals the generated \texttt{value} (\ref{tag-input-value}) attribute then the \texttt{checked} (\ref{tag-input-checked}) attribute is written. Both values are converted to string before being compared. To determine the generated \texttt{value} attribute the tag first looks for a \texttt{valueexpr} (\ref{tag-input-valueexpr}) attribute, then a \texttt{value} attribute. For example: \verbatiminput{doctest/tags-input-radio1} The \texttt{expr} attribute can be used to generate the internal comparison value. This is then compared with the \texttt{value} attribute to control the state of the \texttt{checked} attribute. For example: \verbatiminput{doctest/tags-input-radio2} The \texttt{valueexpr} attribute can be used to dynamically generate the \texttt{value} attribute. For example: \verbatiminput{doctest/tags-input-radio3} After writing all tag attributes the execution context \method{input_add()} method is called with the arguments; input field type (\code{'radio'}), the generated \texttt{name}, the generated \texttt{value}, and a flag indicating whether or not the \texttt{list} (\ref{tag-input-list}) attribute was present. \subsubsection{\texttt{type="..."} attribute (checkbox)\label{tag-input-checkbox}} The tag determines the generated \texttt{name} (\ref{tag-input-name}) from the \texttt{name} related attributes. Then an internal comparison value is determined by evaluating the \texttt{expr} (\ref{tag-input-expr}) attribute if it is present, or by looking up the generated \texttt{name} in the execution context. If the comparison value equals the generated \texttt{value} (\ref{tag-input-value}) attribute then the \texttt{checked} (\ref{tag-input-checked}) attribute is written. Both values are converted to string before being compared. If the internal comparison value is either a list or tuple the \texttt{checked} attribute is written if the generated \texttt{value} attribute is present in the list/tuple. To determine the generated \texttt{value} attribute the tag first looks for a \texttt{valueexpr} (\ref{tag-input-valueexpr}) attribute, then a \texttt{value} attribute, and then if that fails it defaults to the value \texttt{'on'}. For example: \verbatiminput{doctest/tags-input-checkbox} After writing all tag attributes the execution context \method{input_add()} method is called with the arguments; input field type (\code{'checkbox'}), the generated \texttt{name}, the generated \texttt{value}, and a flag indicating whether or not the \texttt{list} (\ref{tag-input-list}) attribute was present. \subsubsection{\texttt{type="..."} attribute (image)\label{tag-input-image}} The tag determines the generated \texttt{name} (\ref{tag-input-name}) from the \texttt{name} related attributes. For example: \verbatiminput{doctest/tags-input-image} After writing all tag attributes the execution context \method{input_add()} method is called with the arguments; input field type (\code{'image'}), the generated \texttt{name}, \code{None}, and a flag indicating whether or not the \texttt{list} (\ref{tag-input-list}) attribute was present. When a browser submits input to an \texttt{image} input it sends an \code{x} and \code{y} value for the field. These are saved as attributes of the field. For example, if an image input named \texttt{map} was clicked by the user, then the code to detect and process the input would look something like this: \begin{verbatim} def page_process(ctx): if ctx.req_equals('map'): map_clicked_at(ctx.locals.map.x, ctx.locals.map.y) \end{verbatim} \subsubsection{\texttt{type="..."} attribute (file)\label{tag-input-file}} The tag determines the generated \texttt{name} (\ref{tag-input-name}) from the \texttt{name} related attributes. If you are using an execution context that inherits from the \class{NameRecorderMixin} (\ref{mixin-recorder}) then using this input field type will automatically cause the enclosing \texttt{} (\ref{tag-form}) tag to include an \texttt{enctype="multipart/form-data"} (\ref{tag-form-enctype}) attribute. For example: \verbatiminput{doctest/tags-input-file} After writing all tag attributes the execution context \method{input_add()} method is called with the arguments; input field type (\code{'file'}), the generated \texttt{name}, \code{None}, and a flag indicating whether or not the \texttt{list} (\ref{tag-input-list}) attribute was present. The request merging allows the user to submit more than one file in a \texttt{file} input field. To simplify application code the \class{Request} always returns a list of \class{FileField} objects for \texttt{file} inputs. Application code to process \texttt{file} inputs typically looks like the following: \begin{verbatim} def page_process(ctx): if ctx.req_equals('resume'): for r in ctx.locals.resume: if r.filename: save_uploaded_resume(r.filename, r.file.read()) \end{verbatim} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-select}} Albatross browser request merging depends upon the functionality provided by the \texttt{} tag. If you do no use this tag in applications then the standard request merging will not work. To determine the \texttt{name} (\ref{tag-select-name}) attribute in the generated tag a number of attributes are used. The generated name is evaluated in the execution context to determine an internal compare value. The compare value is used to control which option tags are generated with the \texttt{selected} (\ref{tag-option-selected}) attribute. \texttt{ \end{verbatim} But if \texttt{abc.isdisabled()} evaluates as \texttt{False}, then the \texttt{disabled} attribute is suppressed entirely: \begin{verbatim} \end{verbatim} In the following example, we change the styling of alternate rows in a table: \verbatiminput{doctest/tags-anytag-tr} \section{Execution and Control Flow\label{tag-execflow}} Tags in this section provide just enough programming capability to allow template files to react to and format values from the execution context. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-require}} This tag is used to specify the minimum version of the Albatross templating system that will correctly parse your template, or to specify templating features (that may be implemented by extension modules) that are required to parse your template. If the templating system has a lower version number, or the extension feature is not available, an \exception{ApplicationError} Exception is raised when the template is parsed. \subsubsection{\texttt{version="..."} attribute\label{tag-include-version}} This attribute specifies the minimum version of the Albatross templating system required to correctly parse your template. Specify the lowest version that will correctly parse your template. \begin{tableiii}{c|c|l}{textrm}{Templating Version}{Albatross Version}{Template feature} \lineiii{1}{up to 1.20}{} \lineiii{2}{1.30 and up}{prefixing any tag with \texttt{al-} now allows any attribute to be evaluated} \end{tableiii} \subsubsection{\texttt{feature="..."} attribute\label{tag-include-feature}} If the \texttt{feature} attribute is present, it specifies a comma-separated list of templating features that will be required to correctly parse your template. At present, no features are available. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-include}} Use this tag to load and execute another template file at the current location. You can specify the name of the included template file by name using the \texttt{name} (\ref{tag-include-name}) attribute or by expression using the \texttt{expr} (\ref{tag-include-expr}) attribute. \subsubsection{\texttt{expr="..."} attribute\label{tag-include-expr}} If the \texttt{expr} attribute is present it is evaluated when the template is executed to generate the name of a template file. The specified template file is loaded and executed with the output replacing the \texttt{} tag. For example: \verbatiminput{doctest/tags-include1} \subsubsection{\texttt{name="..."} attribute\label{tag-include-name}} This attribute is ignored if the \texttt{expr} attribute is present. When the template is executed the specified template file is loaded and executed with the output replacing the \texttt{} tag. For example: \verbatiminput{doctest/tags-include2} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-comment}} This tag suppresses the execution and output of any contained content, although the contained content must be syntactically correct. For example: \verbatiminput{doctest/tags-comment} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-flush}} When the template file interpreter encounters an \texttt{} during execution it flushes all accumulated HTML to output. Usually HTML is accumulated in the execution context and is not sent to the output until the \method{flush_content()} is called. This gives programs the opportunity to handle exceptions encountered during template execution without partial output leaking to the browser. When the program is performing an operation that runs for some time this behaviour may give user the impression that the application has entered an infinite loop. In these cases it is usually a good idea to provide incremental feedback to the user by placing \texttt{} tags in your template files. \verbatiminput{doctest/tags-flush} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}/\texttt{}/\texttt{}\label{tag-if}} Use of these tags parallels the \keyword{if}/\keyword{elif}/\keyword{else} keywords in Python. The \texttt{} tag is a content enclosing tag while \texttt{} and \texttt{} are empty tags that partition the content of the enclosing \texttt{} tag. \subsubsection{\texttt{expr="..."} attribute\label{tag-if-expr}} The \texttt{expr} attribute is used in the \texttt{} and \texttt{} tags to specify a test expression. The expression is evaluated when the template is executed. If the text expression in the \texttt{expr} attribute of the \texttt{} tag evaluates to a \code{TRUE} value then the enclosed content up to either the next \texttt{} or \texttt{} tag will be executed. For example: \verbatiminput{doctest/tags-if1} If the expression in the \texttt{expr} attribute of the \texttt{} tag evaluates \code{FALSE} then the enclosed content following the \texttt{} tag is executed. For example: \verbatiminput{doctest/tags-if2} The \texttt{} tag is used to chain a number of expression that are tested in sequence. The first test that evaluates \code{TRUE} determines the content that is executed. For example: \verbatiminput{doctest/tags-if3} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-value}} This tag allows you to evaluate simple expressions and write the result to output. \subsubsection{\texttt{date="..."} attribute\label{tag-value-date}} If a \texttt{date} attribute is specified then the enclosed format string is passed to the Python \function{time.strftime()} function along with the result of the expression in the \texttt{expr} (\ref{tag-value-expr}) attribute. The result of \function{time.strftime()} is then written to the output. For example: \verbatiminput{doctest/tags-value3} \subsubsection{\texttt{expr="..."} attribute\label{tag-value-expr}} This attribute must the specified. It contains an expression that is evaluated when the template is executed and the result is written as a string to the output. For example: \verbatiminput{doctest/tags-value1} \subsubsection{\texttt{lookup="..."} attribute\label{tag-value-lookup}} When the \texttt{lookup} attribute is specified the result of the expression in the \texttt{expr} (\ref{tag-value-expr}) attribute is used to retrieve content from the lookup table named in the \texttt{lookup} attribute. This is a very useful way to separate the internal representation of program value from the presentation of that value. For example: \verbatiminput{doctest/tags-value4} Please refer to the \texttt{} tag reference for an explanation of that tag and more complex examples. \subsubsection{\texttt{noescape} attribute\label{tag-value-noescape}} If the \texttt{noescape} attribute is present then the value is not escaped. Only use this attribute when you are sure that the result of the expression is safe. Without this attribute all \texttt{\&}, \texttt{<}, \texttt{>}, and \texttt{"} are escaped. For example: \verbatiminput{doctest/tags-value2} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-exec}} This tag allows you to place arbitrary Python code in a template file. \subsubsection{\texttt{expr="..."} attribute\label{tag-exec-expr}} The expression is specified in the \texttt{expr} attribute. It is compiled using \var{kind}\code{~=~'exec'} and evaluated when the template is executed. For example: \verbatiminput{doctest/tags-exec1} If you need to include the same quote character used to enclose the attribute value in your expression you can escape it using a backslash (``$\backslash$''). For example: \verbatiminput{doctest/tags-exec2} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-for}} This tag implements a loop in almost the same way as the \keyword{for} keyword in Python. The tag uses an instance of the \class{ListIterator} (\ref{tag-for-listiter}) identified in the local namespace by the \texttt{iter} (\ref{tag-for-iter}) attribute to iterate over the sequence defined by the expression in the \texttt{expr} (\ref{tag-for-expr}) attribute. \verbatiminput{doctest/tags-for1} Note that you must use the \method{value()} method of the iterator to retrieve the current sequence value, or set a template namespace name via the \texttt{vars} attribute into which it will be stored. When using pagination mode via the \texttt{pagesize} (\ref{tag-for-pagesize}) attribute the \texttt{prevpage} and \texttt{nextpage} attributes of the \texttt{} (\ref{tag-input}) and \texttt{} (\ref{tag-a}) tags can be used to automatically page forwards and backwards through a sequence. The following simulates pagination via the \method{set_backdoor()} \class{ListIterator} method and shows other data that is maintained by the iterator. \verbatiminput{doctest/tags-for7} \subsubsection{\texttt{cols="..."} attribute\label{tag-for-cols}} This attribute is used to format a sequence as multiple columns. The attribute value is an integer that specifies the number of columns. Rather than evaluate the enclosed content once for each item in the sequence, the tag evaluates the content for each \emph{row of items} in the sequence. The items in each row can be formatted by using an inner \texttt{} tag. By default the items flow down columns. To flow across columns you must use the \texttt{flow} (\ref{tag-for-flow}) For example: \verbatiminput{doctest/tags-for5} Multi-column formatting does not support pagination. \subsubsection{\texttt{continue} attribute\label{tag-for-continue}} When paginating items via the \texttt{pagesize} (\ref{tag-for-pagesize}) attribute, the iterator index will reset to the first index displayed on the page if you use an iterator more than once on the page. The \texttt{continue} attribute suppresses the sequence index reset causing the elements to flow on from the previous page. For example: \verbatiminput{doctest/tags-for3} \subsubsection{\texttt{expr="..."} attribute\label{tag-for-expr}} The \texttt{expr} attribute specifies an expression that yields a sequence that the iterator specified in the \texttt{iter} (\ref{tag-for-iter}) attribute will iterate over. All of the enclosed content is then evaluated for each element in the sequence. For example: \verbatiminput{doctest/tags-for1} \subsubsection{\texttt{flow="..."} attribute\label{tag-for-flow}} This attribute is used with the \texttt{cols} (\ref{tag-for-cols}) attribute to control the flow of values across columns. The default value is \texttt{"down"}. Use the value \texttt{"across"} to flow items across columns. Rather than evaluate the enclosed content once for each item in the sequence, the tag evaluates the content for each \emph{row of items} in the sequence. The items in each row can be formatted by using an inner \texttt{} tag. For example: \verbatiminput{doctest/tags-for6} Multi-column formatting does not support pagination. \subsubsection{\texttt{iter="..."} attribute\label{tag-for-iter}} This attribute specifies the name of the \class{ListIterator} (\ref{tag-for-listiter}) that will be used to iterate over the items in the sequence defined by the expression in the \texttt{expr} (\ref{tag-for-expr}) attribute. \subsubsection{\texttt{pagesize="..."} attribute\label{tag-for-pagesize}} This attribute is used to present a sequence of data one page at a time. The attribute value must be an integer that specifies the number of items to display in each page. Use of the \texttt{pagesize} attribute places the sequence iterator into page mode and limits the number of elements that will be displayed. For example: \verbatiminput{doctest/tags-for2} Pagination support requires that session support be present in the execution context. All of the Albatross application objects provide session capable execution contexts by default. The \class{SimpleContext} class does not support sessions so it is necessary to augment the class for the above example. Note also that when the \texttt{} tag processes the \texttt{pagesize} attribute it places the sequence iterator into the session. \subsubsection{\texttt{prepare} attribute\label{tag-for-prepare}} This attribute allows you to place pagination controls before the formatted sequence content. When the \texttt{prepare} attribute is present the \texttt{} tag will perform all processing but will not write any output. This allows you to test pagination results before presenting output. For example: \verbatiminput{doctest/tags-for4} Note the XML empty tag syntax on the \texttt{} tag. \subsubsection{\texttt{vars="..."} attribute\label{tag-for-vars}} If this attribute is set, the current value of the iterator (as returned by \method{value()} will be saved to a variable of this name in the local namespace. For example: \verbatiminput{doctest/tags-for8} If the attribute is set to a comma separated list of variables, the iterator value will be unpacked into these variables. The iterator values must iterable in this case (typically a tuple or list). If there are more variables listed than there are values to be unpacked, then the unused variables are left unchanged. Conversely, if there are more values than variables, only the values with corresponding names will be unpacked. For example: \verbatiminput{doctest/tags-for9} \subsubsection{ListIterator Objects\label{tag-for-listiter}} The iterator named in the \texttt{iter} (\ref{tag-for-iter}) attribute of the \texttt{} tag is an instance of this class. By using an object to iterate over the sequence the toolkit is able to provide additional data that is useful in formatting HTML. The iterator will retrieve each value from the sequence exactly once. This allows you to use objects that act as sequences by implementing the Python sequence protocol. Only \method{__getitem__()} is required unless you use pagination, then \method{__len__()} is also required. \begin{methoddesc}[ListIterator]{pagesize}{} Returns the pagesize that was set in the \texttt{pagesize} attribute. \end{methoddesc} \begin{methoddesc}[ListIterator]{has_prevpage}{} Returns \code{TRUE} if the page \member{start} index is greater than zero indicating that there is a previous page. \end{methoddesc} \begin{methoddesc}[ListIterator]{has_nextpage}{} Returns \code{TRUE} if the sequence length is greater than the page \member{start} index plus the \member{_pagesize} member indicating that there is a next page. If the iterator has not been placed into ``page mode'' by the presence of a \texttt{pagesize} attribute a \class{ListIteratorError} exception will be raised. \end{methoddesc} \begin{methoddesc}[ListIterator]{index}{} Returns the index of the current sequence element. \end{methoddesc} \begin{methoddesc}[ListIterator]{start}{} Returns the index of the first sequence element on the page. \end{methoddesc} \begin{methoddesc}[ListIterator]{count}{} Returns the index of the current sequence element within the current page. This is equivalent to \code{index()~-~start()}. \end{methoddesc} \begin{methoddesc}[ListIterator]{value}{} Returns the current sequence element. \end{methoddesc} Most of the methods and all of the members are not meant to be accessed from your code but are documented below to help clarify how the iterator behaves. \begin{memberdescni}[ListIterator]{_index} Current sequence index --- returned by \member{index()}. \end{memberdescni} \begin{memberdescni}[ListIterator]{_start} Sequence index of first element on page --- returned by \member{start()}. \end{memberdescni} \begin{memberdescni}[ListIterator]{_count} Sequence index of current element on page --- returned by \member{count()}. \end{memberdescni} \begin{memberdescni}[ListIterator]{_seq} Sequence being iterated over --- initialised to \code{None} and set by \method{set_sequence()}. \end{memberdescni} \begin{memberdescni}[ListIterator]{_have_value} Indicates the state of the current element. There are three possible values: \code{None} indicates that the state is unknown and will be established when the sequence is next accessed, zero indicates that the end of sequence has been reached and there is no valid element, and one indicates the current element is valid. \end{memberdescni} \begin{memberdescni}[ListIterator]{_pagesize} Current page size --- initialised to \code{0} and set by the presence of a \texttt{pagesize} attribute in the \texttt{} tag. \end{memberdescni} \begin{methoddesc}[ListIterator]{__getstate__}{} When ``page mode'' is enabled the iterator is saved into the session (via the execution context \method{add_session_vars()} method). This restricts the Python pickler to saving only the \member{_start} and \member{_pagesize} members. \end{methoddesc} \begin{methoddesc}[ListIterator]{__setstate__}{tup} Restores an iterator from the Python pickler. \end{methoddesc} \begin{methoddesc}[ListIterator]{__len__}{} When in ``page mode'' it returns the \member{_pagesize} member else it returns the length of the sequence. \end{methoddesc} \begin{methoddesc}[ListIterator]{set_backdoor}{op, value} The \texttt{} and \texttt{} tags provide \texttt{nextpage} and \texttt{prevpage} attributes that generate names using a special backdoor format. When the browser request is merged the \method{set_value()} method of the \class{NamespaceMixin} directs list backdoor input fields to this method. Refer to the documentation in section \ref{mixin-namespace}. The \var{value} argument is the browser submitted value for the backdoor field. If a value was submitted for the backdoor field then the \var{op} argument is processed. If \var{op} equals \code{"prevpage"} or \code{"nextpage"} then the iterator selects the previous or next page respectively. \end{methoddesc} \begin{methoddesc}[ListIterator]{get_backdoor}{op} When generating backdoor fields for the \texttt{} and \texttt{} tags the toolkit calls this method to determine the value that will assigned to that field. The method returns \code{1}. \end{methoddesc} \begin{methoddesc}[ListIterator]{set_pagesize}{size} Sets the \member{_pagesize} member to \var{size}. \end{methoddesc} \begin{methoddesc}[ListIterator]{has_sequence}{} Returns whether or not a sequence has been placed into the iterator (\member{_seq} is not \code{None}). \end{methoddesc} \begin{methoddesc}[ListIterator]{set_sequence}{seq} Sets the \member{_seq} to \var{seq}. \end{methoddesc} \begin{methoddesc}[ListIterator]{reset_index}{} If the \texttt{} tag does not contain a \texttt{continue} attribute then this is called just before executing the tag content for the first element in the sequence. It sets the \member{_index} member to \member{_start}. \end{methoddesc} \begin{methoddesc}[ListIterator]{reset_count}{} This is called just before executing the tag content for the first element in the sequence. It sets the \member{_count} member to zero. \end{methoddesc} \begin{methoddesc}[ListIterator]{clear_value}{} Sets the \member{_have_value} member to \code{None} which causes the next call of \member{has_value()} to retrieve the sequence element indexed by \member{_index}. \end{methoddesc} \begin{methoddesc}[ListIterator]{has_value}{} When the \member{_have_value} member is \code{None} this method tries to retrieve the sequence element indexed by \member{_index}. If an element is returned by the sequence it is saved in the \member{_value} member and \member{_have_value} is set to one. If an \exception{IndexError} exception is raised by the sequence then \member{_have_value} is set to zero. The method returns TRUE if a sequence member is contained in \member{_value}. By this mechanism the iterator retrieves each value from the sequence exactly once. \end{methoddesc} \begin{methoddesc}[ListIterator]{next}{} Retrieves the next value (if available) from the sequence into the \member{_value} member. \end{methoddesc} \begin{methoddesc}[ListIterator]{set_value}{value} A back door hack for multi-column output that sets respective values of the iterator to sequences created by slicing the sequence in the \texttt{expr} attribute of the \texttt{} tag. \end{methoddesc} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-lookup}} The \texttt{} tag uses a dictionary-style lookup to choose one of the contained \texttt{} HTML fragments. If no \texttt{} tag matches, then the tag returns any content that was not enclosed by an \texttt{} tag. The \texttt{} key values are derived by evaluating their \texttt{expr} attribute. The \texttt{} element will either be expanded in place if an \texttt{expr} attribute is given or, if named with an \texttt{name="..."} attribute, expanded later via an \texttt{} \texttt{lookup="..."} tag. \subsubsection{\texttt{expr="..."} attribute\label{tag-lookup-expr}} If the \texttt{expr} attribute is used, this is evaluated and the content of the matching \texttt{} element is returned. If no match occurs, the unenclosed content is returned. This form of the tag is akin to the switch or case statements that appear in some languages. \subsubsection{\texttt{name="..."} attribute\label{tag-lookup-name}} If the \texttt{name="..."} is used, the tag becomes a named lookup and expansion is deferred until the lookup is referenced via the \texttt{} element. In this case, the lookup is performed on the evaluated value of the \texttt{} \texttt{expr} attribute. For example: \verbatiminput{doctest/tags-lookup} By placing lookup tables in separate template files you can eliminate redundant processing via the \method{run_template_once()} execution context method. This method is defined in the \class{AppContext} class that is used as a base for all application execution contexts. As the above example demonstrates, you are able to place arbitrary template HTML inside the lookup items. As the content of the item is only executed when referenced, all expressions are evaluated in the context of the template HTML that references the item. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-item}} The \texttt{} tag must only be used as a child tag of an \texttt{} (\ref{tag-lookup}) tag. to allow internal application values to be converted to display form. \subsubsection{\texttt{expr="..."} attribute\label{tag-item-expr}} The \texttt{expr} attribute defines an expression that is evaluated to generate a lookup table key for the parent \texttt{} (\ref{tag-lookup}) tag. When the parent \texttt{} is executed all of the \texttt{expr} expressions are evaluated to build a dictionary of items. For example: \verbatiminput{doctest/tags-item} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-tree}} This tag is used to display tree structured data. A pre-order traversal (node visited before each child) of the tree is performed and the enclosed content is evaluated for each node in the tree. The \texttt{treefold}, \texttt{treeselect}, and \texttt{treeellipsis} attributes on the \texttt{} (\ref{tag-input}) tag allow the user to open, close, and select lazy tree nodes. Section \ref{tug-tree} contains an example of a simple usage of the \texttt{} tag. The \texttt{samples/tree/tree1} sample program takes the simple example further and implements an application that places a checkbox next to the name of each node name. A unique input field name is generated for each checkbox by using the \texttt{alias} attribute described in section \ref{tag-input} on page \pageref{tag-input-alias}. The \texttt{samples/tree/tree2} sample program demonstrates the use of the \texttt{lazy} \texttt{} attribute that enables lazy child loading mode. The \texttt{samples/tree/tree3} sample program demonstrates the use of the \texttt{ellipsis} \texttt{} attribute. \subsubsection{\texttt{ellipsis} attribute\label{tag-tree-ellipsis}} The \texttt{ellipsis} attribute extends the \texttt{lazy} (\ref{tag-tree-lazy}) traversal. It collapses nodes close to the root of tree as deeper nodes are opened. \subsubsection{\texttt{expr="..."} attribute\label{tag-tree-expr}} The \texttt{expr} attribute defines the root of the tree traversal. \subsubsection{\texttt{iter="..."} attribute\label{tag-tree-iter}} This attribute specifies the name of the \class{TreeIterator} (\ref{tag-tree-treeiter}) that will be used to iterate over the nodes in the tree defined by the expression in the \texttt{expr} (\ref{tag-tree-expr}) attribute. \subsubsection{\texttt{lazy} attribute\label{tag-tree-lazy}} The \texttt{lazy} attribute allows lazy traversal of the tree, with child nodes only being loaded when their parent is open. \subsubsection{\texttt{single} attribute\label{tag-tree-single}} The \texttt{single} attribute places the tree in single select mode. Whenever a new node is selected by browser input the previous selected node(s) will be deselected. \subsubsection{TreeNode Objects\label{tag-treenode}} There is no actual \class{TreeNode} class. Trees can be constructed from objects of any class as long as they implement the interface described here. \begin{memberdesc}[TreeNode]{children} This member must only be present in non leaf nodes. It contains a list of immediate child nodes of this node. When using lazy loaded trees this member should be initialised to an empty list in the constructor. The presence of the \member{children} member makes the toolkit treat the node as a non-leaf node. \end{memberdesc} \begin{memberdesc}[TreeNode]{children_loaded} This member needs only be present in non leaf nodes that are referenced by an \texttt{} tag in lazy mode. It should be initialised to \code{0} in the node constructor. The toolkit will set the member to \code{1} once it has called the \method{load_children()} method of the node. \end{memberdesc} \begin{methoddesc}[TreeNode]{load_children}{ctx} This method must be defined for nodes that are referenced by an \texttt{} tag in lazy mode. It should must populate the \member{children} member with the immediate child nodes. The toolkit will call this method when it needs to display the child nodes of a node and they have not yet been loaded (\member{children_loaded}\code{~==~0}). The toolkit ``knows'' when it needs to see the child nodes of a particular node so it asks that node to load the children. This allows potentially huge trees to be browsed by having the toolkit only load those nodes that are visible. \end{methoddesc} \begin{methoddesc}[TreeNode]{albatross_alias}{} This method must be defined for nodes that are referenced by an \texttt{} tag in lazy mode. It must return a unique string identifier for this node that is suitable for use as part of an HTML input field name or URL component via the special \texttt{treeselect}, \texttt{treefold} and \texttt{treeellipsis} attributes. The identifier must be the same each time the program is run (so \code{str(id(self))} will not work). The \class{TreeIterator} uses the node identifier to record which nodes are open and which are selected. The same identifier is also used when the node is referenced via an \texttt{alias} (\ref{tag-input-alias}) attribute of an \texttt{} tag. \end{methoddesc} \subsubsection{TreeIterator Objects\label{tag-tree-treeiter}} An instance of \class{TreeIterator} class (or the sub-classes \class{LazyTreeIterator} (\ref{tag-tree-lazytreeiter}) or \class{EllipsisTreeIterator} (\ref{tag-tree-ellipsistreeiter})) will be placed into the execution context using the name specified in the \texttt{iter} (\ref{tag-tree-iter}) attribute. This iterator will contain traversal data for the current node each time the tag content is executed. Note that it is also acceptable to create an instance of one of the TreeIterator classes prior to rendering the template. The \method{set_selected_aliases()} or \method{set_open_aliases()} methods can then be used to render the tree with nodes already selected or open. By using an object to iterate over the tree the toolkit is able to provide additional data that is useful in formatting HTML. The toolkit also places the iterator into the session (you must be using an application class that supports sessions). \begin{methoddesc}[TreeIterator]{value}{} Returns the current node. \end{methoddesc} \begin{methoddesc}[TreeIterator]{tree_depth}{} Returns the depth of the visible tree, from the root to deepest node. A single node tree has a depth of one. \end{methoddesc} \begin{methoddesc}[TreeIterator]{depth}{} Returns the depth of the current node. \end{methoddesc} \begin{methoddesc}[TreeIterator]{span}{} Is shorthand for \code{n.tree_depth()~-~n.depth()}. It is intended to be used for the \texttt{colspan} of the table cell containing the node name when laying the tree out in a table. See the \texttt{samples/tree/tree2.html} template for just such an example. \end{methoddesc} \begin{methoddesc}[TreeIterator]{line}{depth} Only useful when displaying a tree in tabular form where the root is in the first column of the first row. Returns the type of line that should be displayed in each column up to the depth of the current node. A return value of \code{0} indicates no line, \code{1} indicates a line that joins a node later than this node, and \code{2} indicates a line that terminates at this node. The example in section \ref{tug-tree} uses this method. \end{methoddesc} \begin{methoddesc}[TreeIterator]{is_open}{} Returns \code{TRUE} if the current node is open. For non-lazy iterators, this is always \code{TRUE} except on leaf nodes. \end{methoddesc} \begin{methoddesc}[TreeIterator]{is_selected}{} For non-lazy iterators, this always returns \code{FALSE}. \end{methoddesc} \begin{methoddesc}[TreeIterator]{has_children}{} Returns \code{TRUE} if the current node has children (ie. it defines a \member{children} member). \end{methoddesc} Most of the methods and all of the members are not meant to be accessed from your code but are documented below to help clarify how the iterator behaves. \begin{memberdescni}[TreeIterator]{_value} Stores a reference to the current tree node --- returned by \method{value()}. \end{memberdescni} \begin{memberdescni}[TreeIterator]{_stack} As the tree is being traversed this list attribute records all parent nodes between the current node and the root. This is used to determine which branch lines should be drawn for the current node. \end{memberdescni} \begin{memberdescni}[TreeIterator]{_line} Stores the branch line drawing information for the current node. Elements of this list are returned by \method{line(depth)}. \end{memberdescni} \begin{memberdescni}[TreeIterator]{_tree_depth} Stores the depth of the tree from the root to the deepest visible node. A single node tree has a depth of 1. This is calculated immediately before the tree is displayed. This is returned by \method{tree_depth()}. \end{memberdescni} \begin{methoddesc}[TreeIterator]{set_line}{line} Saves the \var{line} argument in \member{_line}. \end{methoddesc} \begin{methoddesc}[TreeIterator]{set_value}{node} Sets the \member{_value} to the \var{node} argument. \end{methoddesc} \begin{methoddesc}[TreeIterator]{node_is_open}{ctx, node} Called internally whenever the toolkit needs to determine the open state of a tree node. For non-lazy iterators, it returns whether or not the node in the \var{node} argument has children (because non-lazy iterators are always open). \end{methoddesc} \subsubsection{LazyTreeIterator Objects\label{tag-tree-lazytreeiter}} \texttt{} tags that include the \texttt{lazy} attribute use an instance of the \class{LazyTreeIterator} class. This class supports all the methods of the \class{TreeIterator} class, as well as the following: \begin{methoddesc}[TreeIterator]{is_open}{} Returns \code{TRUE} is the current node is open. Calls the \method{albatross_alias()} method of the current node and returns \code{TRUE} if the returned alias exists in the \member{_open_aliases} dictionary member. \end{methoddesc} \begin{methoddesc}[TreeIterator]{is_selected}{} Returns \code{TRUE} if the current node is selected. Calls the \method{albatross_alias()} method of the current node and returns \code{TRUE} if the returned alias exists in the \member{_selected_aliases} dictionary member. \end{methoddesc} Some methods are designed to be called from application code, not from templates. \begin{methoddesc}[TreeIterator]{close_all}{} Closes all tree nodes by reinitialising the \member{_open_aliases} to the empty dictionary. \end{methoddesc} \begin{methoddesc}[TreeIterator]{deselect_all}{} Deselects all tree nodes by reinitialising the \member{_selected_aliases} to the empty dictionary. \end{methoddesc} \begin{methoddesc}[TreeIterator]{get_selected_aliases}{} Returns a sorted list of aliases for all nodes that are selected (ie. in the \member{_selected_aliases} member). \end{methoddesc} \begin{methoddesc}[TreeIterator]{set_selected_aliases}{aliases} Builds a new \member{_selected_aliases} member from the sequence of aliases passed in the \var{aliases} argument. \end{methoddesc} \begin{methoddesc}[TreeIterator]{get_open_aliases}{} Returns a sorted list of aliases for all nodes that are open (ie. in the \member{_open_aliases} member). \end{methoddesc} \begin{methoddesc}[TreeIterator]{set_open_aliases}{aliases} Builds a new \member{_open_aliases} member from the sequence of aliases passed in the \var{aliases} argument. \end{methoddesc} \class{LazyTreeIterator} instances add the follow private methods and members: \begin{memberdescni}[LazyTreeIterator]{_key} This member caches the value returned by the \method{albatross_alias()} method for the current node. This key is then used to look up the \member{_open_aliases} and \member{_selected_aliases} members. \end{memberdescni} \begin{memberdescni}[LazyTreeIterator]{_open_aliases} A dictionary that contains the aliases for all tree nodes that are currently open. The contents of this dictionary is maintained via the \method{set_backdoor()} method. \end{memberdescni} \begin{memberdescni}[LazyTreeIterator]{_selected_aliases} A dictionary that contains the aliases for all tree nodes that are currently selected. The contents of this dictionary is maintained via the \method{set_backdoor()} method. \end{memberdescni} \begin{methoddesc}[LazyTreeIterator]{__getstate__}{} Used to save the iterator in the session. This restricts the Python pickler to saving only the \member{_lazy}, \member{_open_aliases} and \member{_selected_aliases} members. \end{methoddesc} \begin{methoddesc}[LazyTreeIterator]{__setstate__}{tup} Restores an iterator from the Python pickler. \end{methoddesc} \begin{methoddesc}[LazyTreeIterator]{set_value}{node} Sets the \member{_value} to the \var{node} argument. When operating in lazy mode the \method{albatross_alias()} method is called for \var{node} and the result is cached in \member{_key}. \end{methoddesc} \begin{methoddesc}[LazyTreeIterator]{node_is_open}{ctx, node} Called internally whenever the toolkit needs to determine the open state of a tree node. It returns whether or not the node in the \var{node} argument is open. This always returns \code{0} for leaf nodes as they do not have children. When in lazy mode the open state of \var{node} is retrieved from \member{_open_aliases}. If the node state is open then the method checks the value of the node \member{children_loaded} member. If \member{children_loaded} is \code{FALSE} then the node \method{load_children()} is called to load the children of \var{node}. \end{methoddesc} \begin{methoddesc}[LazyTreeIterator]{set_backdoor}{op, key, value} The \texttt{} and \texttt{} tags provide \texttt{treefold} and \texttt{treeselect} attributes that generate names using a special backdoor format. When the browser request is processed, the \method{set_value()} method of the \class{NamespaceMixin} directs tree backdoor input fields to this method. Refer to the documentation in section \ref{mixin-namespace}. When the \var{op} argument is \code{"treeselect"} the \member{_selected_aliases} is updated for the node identified by the \var{key} argument. If \var{value} is \code{FALSE} the key is removed else it is added. When the \var{op} argument is \code{"treefold"} and \var{value} argument is \code{TRUE} then the open state of the node identified by the \var{key} argument is toggled. \end{methoddesc} \begin{methoddesc}[LazyTreeIterator]{get_backdoor}{op, key} When generating backdoor fields for the \texttt{} and \texttt{} tags the toolkit calls this method to determine the value that will assigned to that field. When \var{op} is \code{"treeselect"} the method returns the current selected state of the node identified by \var{key}. When \var{op} is \code{"treefold"} the method returns \code{1}. \end{methoddesc} \subsubsection{EllipsisTreeIterator Objects\label{tag-tree-ellipsistreeiter}} \class{EllipsisTreeIterator} objects are created by using the \texttt{ellipsis} attribute on an \texttt{} tag. Ellipsis trees are a variant of lazy trees where nodes at shallower levels are progressively collapsed into ellipses as the user opens deeper nodes. The user can reopen the collapsed nodes by selecting an ellipsis. They support all the methods of the \class{LazyTreeIterator} (\ref{tag-tree-lazytreeiter}), as well as the following methods: \begin{methoddesc}[EllipsisTreeIterator]{node_type}{} Returns \code{0} for a regular node, or \code{1} for nodes that have been collapsed into an ellipsis. This is actually implemented on the \class{LazyTreeIterator}, but will always return \code{0} there. \end{methoddesc} \class{EllipsisTreeIterator} objects also have the following private methods and members: \begin{memberdescni}[EllipsisTreeIterator]{_noellipsis_alias} Records the last ellipsis to be selected by the user, and is used to suppress the generate of an ellipsis at that location next time the tree is rendered. \end{memberdescni} \begin{methoddesc}[EllipsisTreeIterator]{node_use_ellipsis}{ctx, node} Returns \code{TRUE} if it is acceptable to render the specified \texttt{node} as an ellipsis. If the node's alias matches \member{_noellipsis_alias}, \code{FALSE} is return, otherwise \code{TRUE} is returned if any of the node's children are open. \end{methoddesc} The behaviour of the \method{set_backdoor} and \method{get_backdoor} methods has been extended to recognise a \texttt{treeellipsis} op. This is used to process browser requests to open an ellipsis (it sets the \member{_noellipsis_alias} member. \section{Macro Processing\label{tag-macroproc}} Tags in this section provide a simple macro processing environment for template files. The main purpose of Albatross macros is to provide a mechanism to divide your HTML into presentation structure and presentation appearance. By defining appearance presentation tricks inside macros you can make global changes to your web application appearance by changing one macro. The \texttt{} (\ref{tag-macro}) and \texttt{} (\ref{tag-usearg}) tags are used to define macros, while \texttt{} (\ref{tag-expand}) and \texttt{} (\ref{tag-setarg}) are used to invoke and expand previously defined macros. The \class{ResourceMixin} (\ref{mixin-resource}) and \class{ExecuteMixin} (\ref{mixin-execute}) classes provide the Albatross macro definition and execution facilities respectively. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-macro}} The \texttt{} tag is used to define a macro. All enclosed content becomes part of the macro definition. Executing the macro registers the macro with the execution context via the \method{register_macro()} method using the name in the \texttt{name} (\ref{tag-macro-name}) attribute. Note that the execution of the macro content is deferred until later when the macro is expanded via the \texttt{} (\ref{tag-expand}) tag. This means that executing a macro definition produces no output. Output is produced only when the macro is expanded. \verbatiminput{doctest/tags-macro1} The deferred execution also means that you can include content that only works within the context of the \texttt{} tag. \verbatiminput{doctest/tags-macro2} In the above example the content of the macro makes reference to a \var{oops} that is not defined in the execution context when the macro was defined. Inside a macro definition you can use as yet undefined macros. \verbatiminput{doctest/tags-macro4} Care must by taken to ensure that you do not make circular macro references else you will cause a stack overflow. \subsubsection{\texttt{name="..."} attribute\label{tag-macro-name}} The \texttt{name} attribute is used to uniquely identify the macro in the application. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-usearg}} The \texttt{} tag is used inside a macro definition to define the location where content enclosed by the \texttt{} (\ref{tag-expand}) tag should be placed when the macro is expanded. All content enclosed by the \texttt{} tag is passed to the macro as the unnamed argument. The unnamed argument is retrieved in the macro definition by using an \texttt{} tag without specifying a \texttt{name} (\ref{tag-usearg-name}) attribute. When a macro expects only one argument it is best to use this mechanism. \verbatiminput{doctest/tags-macro3} \subsubsection{\texttt{name="..."} attribute\label{tag-usearg-name}} Macros can be defined to accept multiple arguments. The \texttt{name} attribute is used to retrieve named arguments. When invoking a macro that accepts named arguments the \texttt{} (\ref{tag-setarg}) tag and \texttt{name} attribute are used to define the content for each named argument. \verbatiminput{doctest/tags-macro7} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-setdefault}} The \texttt{} tag is used inside a macro definition to specify default content for a named macro argument. The content enclosed by this tag will be used if the caller does not override it with a \texttt{} tag. Note that only named arguments can have a default as the unnamed argument is always set, implicitly or explicitly, by the calling \texttt{} tag. \verbatiminput{doctest/tags-macro11} \subsubsection{\texttt{name="..."} attribute\label{tag-setdefault-name}} The \texttt{name} attribute is used to identify the named macro argument that will receive the enclosed content. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-expand}} The \texttt{} tag is used to expand a previously defined macro. You can pass macro expansions as arguments to other macros. \verbatiminput{doctest/tags-macro5} All arguments to macros are executed each time they are used in the macro definition. This means that you need to be aware of side effects when using arguments more than once inside a macro. \verbatiminput{doctest/tags-macro6} \subsubsection{\texttt{name="..."} attribute\label{tag-expand-name}} The \texttt{name} attribute contains the name of the macro that will be expanded. Macros are defined and names using the \texttt{} (\ref{tag-macro}) tag. \subsubsection{\texttt{...arg="..."} attributes\label{tag-expand-arg}} When macro arguments are simple strings, they can be specified as \texttt{} attributes by appending \texttt{arg} to the argument name. So, to set an argument called \texttt{title}, you could add an \texttt{titlearg} attribute to the \texttt{} tag. \verbatiminput{doctest/tags-macro9} If the macro argument is longer or needs to contain markup, the \texttt{} (\ref{tag-setarg}) tag should be used instead. \subsubsection{\texttt{...argexpr="..."} attributes\label{tag-expand-argexpr}} Macro arguments can also be derived by evaluating a python expression. Attributes of the \texttt{} tag that end in \texttt{argexpr} are evaluated, and the base name becomes the macro argument of that name. For example: \begin{verbatim} \end{verbatim} is functionally equivilent to: \begin{verbatim} \end{verbatim} For a more complete example: \verbatiminput{doctest/tags-macro10} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \subsection{\texttt{}\label{tag-setarg}} The \texttt{} tag is used pass content to a macro. All content enclosed by the tag will be passed as an argument to the macro named by the parent \texttt{} (\ref{tag-expand}) tag. The \texttt{} tag is normally used to pass content to macros that define named arguments, but can also be used to enclose the unnamed argument. \verbatiminput{doctest/tags-macro8} \subsubsection{\texttt{name="..."} attribute\label{tag-setarg-name}} The \texttt{name} attribute is used to identify the named macro argument that will receive the enclosed content. albatross-1.36/doc/modularapp.dia0000644000175000017500000000522210135141327016677 0ustar andrewmandrewm]s8WP-c`&Lv^Ljvg](Fcy$9 {}eCH06v* H9:z~et]t;4m/sF~? ˎ~G(n g#XI0Gu~K9Q$yn,QJYh'$Kz۝<uM9\tHp>M5zN%Uc:-SuD~ˈKUtP$we6.w_/i[].4ӈZЎ7FXcYԙ&zO5k.h֜hnąC3JµU%bZގHةf }L-g:.o)sZ@yNOo_ѱ 6?=wJdjᾜUM?1f=v,T~u?ԟboXז Cmj99lLFMM~]/wY?D@VTl;x>Ԧy= Rv߂m{]v7'o=h[{+*ItScLplcN] #˴(5:V:aN[ 664[Q^.y-MEHEI2VلK'3^8B("At/MqRFm^Yufv2lRuCV۝{$jJ͐=>]oAyQprAI ,-NzYPEۆyP>ChU3gLŪVʕ?ޠK&Y;(o+8}adv6F7o# YF3W9ͼ$r{e<0kNXfԕ){_VdN/kwZcV],Ú=kjԹfXVnCmq82QnڊmZlXPvqGd8ٽ *\<$lo\_ĊG\ұIGQ 8#t$ok`w;ov{{5W.~̪dža1lVn4Lf84FaڕUjh㚬QWy880sR6 Gai%vYvXJ9-u_6gDm4[u \\W]c,]9,XN(*;8u$ ˷5 G"SC;7 8Sq^X2.C 2.C 2.C D\q[<#s&^z倓*+=p˅@Ry.>~ >˷Ɨ6;ƸVi$&n/SgH*2be2SN*Ձ.kbV#7HcXUX=gixڔ6p&R B9 䀐B9 䀐B9 䀐buCXjrOF܇O$X]2VXV%Bzz%h<0tKt%8B8 q0agH>zz^k9CDE'!C(P WQ@|i!A>仒]evfp弍c;3LZf(ߠsvfo0qg;ɺ ߢ(`^b_ˀ}/e ؗ2`_ˀ}/ȯ"zM28LRba 3$<$F)nP/gp#p#p# ϞzAc} 3:t趇&03="C0` X|{^z`R"x UO' x xa%<0B*P@=PԻ^͈n,ucI>>3"fD̟ bPW2 #/ ZM8<0TLzU'j3 ô{iXi7=nYfjra {>  4P@A 4P@A 4P@A*2Ȭ^Af5nȮfWv)"oos{&Qu'V DdaѢGSx_LO+@Jc}3-@=PڷADlw0L~*! C( Qx׾(_WW8 ӠxB. Q0Dak^f z]=LFYiɈS=P@=PPUv%%btalbatross-1.36/doc/copyright.tex0000644000175000017500000000346710576163416016633 0ustar andrewmandrewm%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Copyright 2001 by Object Craft P/L, Melbourne, Australia. % LICENCE - see LICENCE file distributed with this software for details. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \centerline{\strong{Copyright \copyright\ 2001-2007 Object Craft All rights reserved.}} Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: \begin{enumerate} \item Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. \item Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. \item Neither the name of Object Craft nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. \end{enumerate} THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. albatross-1.36/doc/introduction.tex0000644000175000017500000000420607701474554017340 0ustar andrewmandrewm%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Copyright 2001 by Object Craft P/L, Melbourne, Australia. % LICENCE - see LICENCE file distributed with this software for details. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \chapter{Introduction\label{intro}} Albatross is a small and flexible toolkit for developing highly stateful web applications. The toolkit is lightweight enough to use for CGI applications. It provides the following: \begin{itemize} \item Browser based sessions via automatically generated hidden form fields. \item Server side sessions via a session server or file based session store. \item Implicit form population and browser request handling. \item Powerful and extensible templating system which promotes separation of presentation and implementation for improved program maintainability. Pagination of sequences and tree browsing are handled implicitly in the templating system. Macros allow repeated HTML and special effects HTML to be defined in one location. Lookup tables translate internal program values to arbitrary template code. \item Applications can be deployed as either CGI programs or as \texttt{mod_python} module with minor changes to program mainline. Custom deployment can be achieved by developing your own \class{Request} class. \item Highly modular application framework which is flexible and extensible which allows many different application construction models. Many \class{Application} classes are provided and many more are possible. \item Comprehensive documentation including many installable samples. \end{itemize} A primary design goal of Albatross is that it be small and easy to use and extend. Most of the toolkit is constructed from a collection of mixin classes. You are encouraged to look at the code and to think of new ways to combine the Albatross mixin classes with your own classes. Object Craft developed Albatross because there was nothing available with the same capabilities which they could use for consulting work. For this reason the toolkit is important to Object Craft and so is actively maintained and developed. albatross-1.36/doc/branchingsessioncontext.dia0000644000175000017500000000772110445664061021516 0ustar andrewmandrewm][~_AۘK&99T%'.a PVXl,y8Ha_Y-٭kG*#&ixga}FS,; =tq~wwdUDb! #w#;]Dyıd$,CwJ/s)[my y$~wnv9wHt*)u_&2]Tw ETxfjiFCڼס DY _[n3O|'M'"72YS!8%Zj,Z^NTشO_9k<7cq,NhFxtΔoo_ѵt>sE#D ߽7Q=) ܜPl5R۷=ܟr0$L? n-1&νm1tI.]O k+8TJ!m4 /(/mÙJNebڱ>Ix 4Y14Ȍb*Z',4Lյ8FnQRuwAԸ( lܘg:sR&XAgN5v$ [x@2S$[lb}^9~I 3i [b46;@]wg" `Ό,_=t"QHXdaSE|֗JҾy_X z8Oifjߤ Q|w=|ww@yh!'1mK{n>9~\ q6fsm5yl366)F䩍MPpfMV9 6ouDXHX [/oҜȱ%Oy"y Bc|ffzoD5UFj5 aC2̤]׎]+.rhaV^ /JkPѲ/-2o-1Q*6Rc.ܬv;]UTg(TE10A(0K˰{%gƸQMM9m`x+`Bװ*t}c0j^#Dږ 1%1|S L1b0@y7Ly\}p8 B@||ڜ@y؆Un>tsM$syIe8SNtzB;S|R Ȍ9&ReE߽7ٝe, Ga_Yp:hƙ""xVolZzķDښJUNϝT,ΜRuA}k=IR1d[ NnQr/?gW\dN\wI1OK,5nvKݙ~3#KWݥD N8Mi}gH x\BWK!JA(!DD-ʭc4f Jd\H6VT|MҵOw_zt=; =uƬUZcRsm\7Z?=gF3Wm-ڔBffVM!W[ֱsgl-ڃy<ͅ !@f0߀,p8wf󍗝1UΘ> 3J`-Ĺ,lLYWvƙ켆 $XQi BUTAUTAUTAUTAUTAUT +8n*͂J_ F <Lxjo2ZB-/); v0a 'uU5( 8:dhB!0 fs>I3Uy'I ʫn$'(5I,6+gNm0xdJܛccLL+2^M)k|V= H}YGff9k,c"I,HNٚäܽ#Yԗ8 P Gd' Mza,֥:/?tzbsshڌdFTC)2` {ְ21qzyoFٖhS:9jA%~UN]D9@9@9@9@9@9 NptsS3e]G~?e"R@X&K|~ dPӝ# AlUvIJCj(T*TS8Y!C;o* kI场*?BcLtv_dqӹhyW9Ac2:ɠqcEy?IK 4ZHrD#$G$9"IHrD#pb' `GQnvB!@HQD!}W ^)xԭMmHdFPd0P ]Og$k@| >ԃ| AvAᆀ5k `7z`<0wmD"n  `}[cuni`9W.*TF# 3N氝a;_B$|t Us\MQYalbatross-1.36/doc/sessionfileappcontext.dia0000644000175000017500000000763510135141327021176 0ustar andrewmandrewm]s}߿a_YLlݹmۙGWЇ_03@3M@'9>}:w ﺺv ݏ#?3Iu&uQpseH"!5b-7#U]D$yo."&qD;>YлfRľ]ZssO]4}uolsDR-zeܭv"2ɩ'̺T _5is]k$Z9c~֎7|u# 6 C klH'M55'5B'2EY!8%j$cZNءn5t}ldž=7eQ$hJxxJVoo_ѹ<;t>qg'D ̻߾7Q5) ܜ@lFkV7yO&Kx8X(6nMeMnkU"tBY'N&Rvl u)ͣ m4zxI[>4Lүӟ#uX #*Hm&D\P?j~2Q7cEqHua㦄rIwcNm;]e إ㭱; IJ͐;' $)/8W/uc)Eo1 6_uw" `{)Y0./€ e"(_UIĒQYim҄@B`RnM%oBHԤX<)sIQ%vM-Y[Q5YO,~ {Yޚx]G̽uُ9eej玟8gD:e̵f(gr9Nj{(n&,iK{('FbJ.uM4SlW)P9+wns5}悝Ef GTd>U%nݼoQc{cǂ)ZDESvyN-_իWMg4]5Gitu`kCgZj.[Ն FWSב"p*©"p*©"p*©"pj-ԛV#!` vpA<'\E!@ A) I \'\IoWٿ+UufLazW.kTvٽB.em<}-|]?O;oWlUm@VOl[P??_Kߖ56>zeuoU /4~w!|!j »n›:;ڱjA @ H QCHpU܁'GJG<& WAT;.ujx`;l۽ ?^ւV<4ԿY.Sy~c=ݾ~)An^Q0{ТQcC`~`jF$O&'OgԆu;HXQN+;u'hmՑ )ͣ6,WnM/0 .Ǚ"&xVo{|Zz7ڸJUNOT$㊆NRuC؝{$hf4[wN28AA\ɽ"SuU”q~4}eǨ>B.w" `{)Y0./RXvhiJ*>X2Et7UL l&/BsjE-HQɯǛ5)77wi\RAn]SKV|M G]f_z^{&^{fGqܔ#.1{u|SN+6flaѦj43.&55W3k r9Y8>t@ ҮKݮ?d"uݕ2Ƹ(+}waBʈukZP"ԥ"_?U%!CBB2! !d!CBXA qTQGG9DqpC! 1W| ]ֻjH!=ydH_ERqF# 0)}`03X_|_ỷﭏFb%+y{.fKE5 &EFai )A̓$yTB29d s@29vK*G4YuQ?\.B @ A9R؎&ur$!d'pb(=,bh^Д4pFoh1:ւu$UcXXb /ɻ/T7Gz`a #I5C4̆s+v>l=y&7S۫aD5ۉl7Ӎl߃1" v ghәl4^M&/TZ} NIBf<%jU[q'4+:̬Dfn4duS}ѦnF X}7zDl)_GI3WA=K~"Y,4f4߱8rȜJ7."}%;Cr.?Os'Ghq$> fp?j~2f}w%bë>]'ls$Ҧn6{-vTZ:UB:BM>^Hϓɏ1A\$ o;ig##ItN>?DlAl)\Csy4YǬ =B,Cd1.V)O*{f4eۄ(߰h@㢷EVT#Z YZ~QMtSrMﲌ<_ import os import sys from albatross import httpdapp def usage(): sys.exit('usage: %s main-module.app-object port [static-url file-path] ...' % os.path.basename(sys.argv[0])) if __name__ == '__main__': # Append the current working directory to the path. sys.path.append(os.getcwd()) # Parse the command line. try: app_module, app_object = sys.argv[1].split('.') port = int(sys.argv[2]) except: usage() static_resources = [] pairs = sys.argv[3:] while pairs: try: uri, fs = pairs[:2] except ValueError: usage() pairs = pairs[2:] static_resources.append((uri, fs)) # Load the application's module and get application instance. module = __import__(app_module) app = getattr(module, app_object) app._Application__base_url = '/' # Create the HTTP server and serve requests. httpd = httpdapp.HTTPServer(app, port, static_resources) httpd.serve_forever() albatross-1.36/ChangeLog0000644000175000017500000040440710577421456015107 0ustar andrewmandrewm------------------------------------------------------------------------ r8887 | andrewm | 2007-03-16 20:35:33 +1100 (Fri, 16 Mar 2007) | 6 lines Previously, if Request did not contain an __albform__ field, all request fields would be merged. This allows simple GET requests to work, but could be used to modify unexpected namespace attributes. This change suppresses the merge if no __albform__ field is found. Application can request specific fields be merged with the ctx.merge_vars() method. ------------------------------------------------------------------------ r8886 | andrewm | 2007-03-16 20:31:50 +1100 (Fri, 16 Mar 2007) | 2 lines Added ResponseMixin. ------------------------------------------------------------------------ r8885 | andrewm | 2007-03-15 17:26:22 +1100 (Thu, 15 Mar 2007) | 2 lines Bump version, initial pass at release notes for 1.36 ------------------------------------------------------------------------ r8884 | andrewm | 2007-03-15 15:14:41 +1100 (Thu, 15 Mar 2007) | 2 lines Switch in "new" fcgiapp module for old. Old is still available as fcgiappold. ------------------------------------------------------------------------ r8883 | andrewm | 2007-03-15 14:14:02 +1100 (Thu, 15 Mar 2007) | 7 lines Reimplement session server client code to make it more robust. Among other things, EINTR is handled gracefully by retring the system call, partial reads and writes are handled, and requests are retried if the socket is closed (this will be more of an issue when the session server is persistent). Socket error message text is included in the raised ServerError when an error occurs. ------------------------------------------------------------------------ r8880 | andrewm | 2007-03-14 18:55:25 +1100 (Wed, 14 Mar 2007) | 5 lines Introduce ListIteratorLite - a simpler ListIterator for cases were we don't need pagination, etc. DO not index the sequence, use only the iterator protocol. This class is used for all al-for iterators except when pagesize or continue is defined. ------------------------------------------------------------------------ r8879 | andrewm | 2007-03-14 17:54:04 +1100 (Wed, 14 Mar 2007) | 2 lines Document new attribute. ------------------------------------------------------------------------ r8878 | andrewm | 2007-03-14 16:38:02 +1100 (Wed, 14 Mar 2007) | 2 lines Refactor common template test logic. Add a test for iterator paging. ------------------------------------------------------------------------ r8876 | andrewm | 2007-03-14 15:03:33 +1100 (Wed, 14 Mar 2007) | 3 lines Add support for setting iterator value within an with a new "vars" attribute. ------------------------------------------------------------------------ r8874 | andrewm | 2007-03-14 12:05:59 +1100 (Wed, 14 Mar 2007) | 2 lines Change doctest/tags-value1 - use of hash() was architecture dependant. ------------------------------------------------------------------------ r8873 | andrewm | 2007-03-14 11:56:47 +1100 (Wed, 14 Mar 2007) | 3 lines Updated description of validate_request. Thanks to Stan Pinte for pointing out the problem. ------------------------------------------------------------------------ r8871 | andrewm | 2007-03-14 11:29:57 +1100 (Wed, 14 Mar 2007) | 5 lines Fix for problem found by Michael Neel: mixing "implicit multi" inputs with singleton inputs was not raising an error. Fixed by extending the input tracking logic with an additional state: "multiple inputs returning single value" (radio, submit). ------------------------------------------------------------------------ r8851 | andrewm | 2007-03-13 16:29:01 +1100 (Tue, 13 Mar 2007) | 2 lines Use common test_raise method for testing expected template errors. ------------------------------------------------------------------------ r8849 | andrewm | 2007-03-13 15:49:27 +1100 (Tue, 13 Mar 2007) | 4 lines When testing set_value for raised errors, call it directly. Previously, it was done by calling the _test method, which also called eval, so an error raised by eval could mask a failure by set_value to raise an error. ------------------------------------------------------------------------ r8848 | andrewm | 2007-03-13 15:47:43 +1100 (Tue, 13 Mar 2007) | 2 lines Remove completed items from TODO ------------------------------------------------------------------------ r8846 | andrewm | 2007-03-13 14:56:54 +1100 (Tue, 13 Mar 2007) | 2 lines Fix incorrect example used in tag-option-labelexpr section. ------------------------------------------------------------------------ r8845 | andrewm | 2007-03-13 14:40:28 +1100 (Tue, 13 Mar 2007) | 2 lines Update doco for changes to tag. ------------------------------------------------------------------------ r8843 | andrewm | 2007-03-13 11:51:26 +1100 (Tue, 13 Mar 2007) | 3 lines Simplified version of fixes in previous check-in. More tests. Still no doco updates. ------------------------------------------------------------------------ r8842 | andrewm | 2007-03-12 17:04:09 +1100 (Mon, 12 Mar 2007) | 8 lines Fix the tag so that it no longer needs to appear directly within an tag (previously this would silently fail to generate content). This allows it to be used with for cases where optionexpr is too simplistic. Also added support for "expr" and "valueexpr" attributes. Added tests for new code, as well as error conditions. Documentation needs to be updated. Also did a small amount of refactoring of tests. ------------------------------------------------------------------------ r8841 | andrewm | 2007-03-12 14:13:16 +1100 (Mon, 12 Mar 2007) | 3 lines Add a test for application flow (page_enter, page_display, page_process, page_leave). Factor out a common DummyRequest class for tests. ------------------------------------------------------------------------ r8534 | andrewm | 2007-02-13 17:35:34 +1100 (Tue, 13 Feb 2007) | 3 lines Make a note about being silently ignored when not directly contained in . ------------------------------------------------------------------------ r8533 | andrewm | 2007-02-13 17:33:33 +1100 (Tue, 13 Feb 2007) | 2 lines Add note about not supporting ------------------------------------------------------------------------ r8003 | andrewm | 2006-11-20 12:05:51 +1100 (Mon, 20 Nov 2006) | 2 lines Fix incorrect format string args in al-session-daemon. ------------------------------------------------------------------------ r7027 | andrewm | 2006-08-22 09:38:24 +1000 (Tue, 22 Aug 2006) | 2 lines Fix FCGI_MAX_DATA (operator precedence mistake picked up by Tom Limoncelli). ------------------------------------------------------------------------ r6957 | djc | 2006-08-15 12:48:22 +1000 (Tue, 15 Aug 2006) | 2 lines Catch up with official public release. ------------------------------------------------------------------------ r6953 | djc | 2006-08-14 12:36:35 +1000 (Mon, 14 Aug 2006) | 2 lines Forgot to migrate this from CVS. ------------------------------------------------------------------------ r6639 | andrewm | 2006-06-26 15:50:25 +1000 (Mon, 26 Jun 2006) | 3 lines Add tag to set default value for named macro arguments (this and the previous checkin from an idea by Greg Bond). ------------------------------------------------------------------------ r6638 | andrewm | 2006-06-26 13:39:25 +1000 (Mon, 26 Jun 2006) | 3 lines Added "simple" macro arguments ( and ). ------------------------------------------------------------------------ r6623 | andrewm | 2006-06-23 18:40:43 +1000 (Fri, 23 Jun 2006) | 2 lines Additional tests for macros. ------------------------------------------------------------------------ r6622 | andrewm | 2006-06-23 18:07:39 +1000 (Fri, 23 Jun 2006) | 3 lines Simplify macro arg handling in Expand tag (avoid recreating copy of arg dict each time). ------------------------------------------------------------------------ r6621 | andrewm | 2006-06-23 18:04:17 +1000 (Fri, 23 Jun 2006) | 3 lines Simplify macro arg handling (avoid searching parent name-spaces by using cloned dictionaries). ------------------------------------------------------------------------ r6620 | andrewm | 2006-06-23 17:32:54 +1000 (Fri, 23 Jun 2006) | 3 lines Put generalised versions of ...expr and ...bool attribute processing on Tag class. ------------------------------------------------------------------------ r6619 | andrewm | 2006-06-23 14:56:26 +1000 (Fri, 23 Jun 2006) | 4 lines Add get_param() method to fcgiapp.Request class, which allows base class to access http "environment" variables in a portable way (for CGI, they're try environment variables, but FastCGI does it differently). ------------------------------------------------------------------------ r6618 | andrewm | 2006-06-23 14:44:26 +1000 (Fri, 23 Jun 2006) | 3 lines Merge from branch py2k: changes to use more python 2 constructs, rename local variables that conflict with builtins. ------------------------------------------------------------------------ r6617 | andrewm | 2006-06-23 14:41:22 +1000 (Fri, 23 Jun 2006) | 4 lines Merge from branch: Add proof-of-concept tool to spawn a fastcgi-enabled app as a stand-alone FastCGI server (inet or unix socket). Still needs "daemonisation". ------------------------------------------------------------------------ r6570 | andrewm | 2006-06-20 19:58:15 +1000 (Tue, 20 Jun 2006) | 2 lines Add 1.35 release announcement. ------------------------------------------------------------------------ r6569 | andrewm | 2006-06-20 19:46:17 +1000 (Tue, 20 Jun 2006) | 4 lines Change the way the doc Makefile finds the python doc tools (previous way failed if python was installed to /usr/local). Changed dia invocation to support dia 0.95 (with comment for 0.94 version). Updated ChangeLog and TODO. ------------------------------------------------------------------------ r6568 | andrewm | 2006-06-20 18:10:53 +1000 (Tue, 20 Jun 2006) | 3 lines Expand "Developing Custom Tags" documentation and update documention "changes" information. ------------------------------------------------------------------------ r6567 | djc | 2006-06-20 16:52:27 +1000 (Tue, 20 Jun 2006) | 2 lines Use the installed documentation tools for building. ------------------------------------------------------------------------ r6563 | andrewm | 2006-06-20 13:14:57 +1000 (Tue, 20 Jun 2006) | 2 lines Fix file broken by cvs2svn. ------------------------------------------------------------------------ r6562 | andrewm | 2006-06-20 12:40:42 +1000 (Tue, 20 Jun 2006) | 2 lines Check for session server - if not running, warn and skip test. ------------------------------------------------------------------------ r6561 | andrewm | 2006-06-20 11:35:51 +1000 (Tue, 20 Jun 2006) | 3 lines In test Makefiles, allow python interpreter to be overridden by user from command line, for example: "make PYTHON=python2.4" ------------------------------------------------------------------------ r6267 | andrewm | 2006-05-29 15:18:04 +1000 (Mon, 29 May 2006) | 3 lines If client closed connection while server had write data pending, a subsequent del_write_file would generate an exception, killing the server. ------------------------------------------------------------------------ r6266 | andrewm | 2006-05-03 12:11:23 +1000 (Wed, 03 May 2006) | 2 lines - update ChangeLog, bump version to 1.35 ------------------------------------------------------------------------ r6265 | andrewm | 2006-05-03 12:10:54 +1000 (Wed, 03 May 2006) | 2 lines - update ChangeLog, bump version to 1.35 2006-04-07 17:40 andrewm * albatross/tags.py: Input tags needed to be taught about disabledbool attribute. 2006-03-24 12:07 andrewm * albatross/fcgiappnew.py: - fastcgi: break stdout data up into chunks < 64k (16 length field), check for oversized data in send method. 2006-03-20 17:23 andrewm * albatross/fcgiappnew.py: - fix a bug in the code that unpacks large attribute-value pairs (headers) in the new FastCGI implementation. 2006-02-23 18:13 andrewm * albatross/: context.py, template.py: - when a user registers an extension tag, check the name against the template parsing regexp to ensure the name can be matched. 2006-02-20 10:57 djc * redhat/albatross.spec: Bump release to 1.34. 2006-02-14 18:00 andrewm * setup.py, albatross/__init__.py, albatross/sessionfile.py, doc/albatross.tex, doc/installation.tex: - bump version to 1.34 2006-02-10 17:05 andrewm * albatross/template.py: Give the AnyTag knowledge of HTML tags for which the close tag is forbidden, so it can avoid generating XHTML empty tag (which can cause the doc to fail HTML validation). 2006-02-09 21:26 andrewm * albatross/cgiapp.py: Some minor refactoring to support the fcgiappnew module. 2006-02-09 21:22 andrewm * albatross/fcgiappnew.py: Drop-in replacement for fcgiapp.py - this version implements the FastCGI protocol itself, rather than relying on an external module to implement the protocol (we have not been able to clarify the license of the fcgi.py module). This new module seems to address several problems with fcgi.py, and should be faster, although it hasn't received much testing yet (but it works for me with Apache 1.3). 2006-01-10 15:56 andrewm * test/templates/: macro.py, macro/unnamed.html: - add test for "unnamed" macro argument. 2006-01-09 15:49 andrewm * doc/: tags.tex, doctest/tags-anytag-tr, doctest/tags-exception: - document the "anytag" functionality. - fix mangled al-for paging example. 2006-01-09 15:47 andrewm * doc/test_examples.py: - reimplemented the doc examples tester to make the code slightly more readable and to make it's handling of trailing whitespace a little more explicit. 2005-12-16 17:04 andrewm * ChangeLog, dist/release-1.33: Update changelog and release notes. 2005-12-16 16:59 andrewm * setup.py, albatross/__init__.py, doc/albatross.tex, doc/changes.tex, doc/installation.tex: - bump version to 1.33 2005-12-16 16:19 andrewm * albatross/context.py, test/namespace/__init__.py, test/namespace/set_value.py: - In NamespaceMixin.set_value, use state machine for evaluating iterator backdoors, rather than calling get_value. - Add tests for set_value state machine. - Improve error set_value state machine error reporting. 2005-08-16 16:47 andrewm * dist/release-1.32: * add release notice. 2005-08-16 16:30 andrewm * ChangeLog, doc/albatross.tex, doc/changes.tex: - Update ChangeLog and doc "Changes" appendix for 1.32 release. 2005-08-16 15:32 andrewm * albatross/app.py: - Greg noted earlier that using set_page within the start page would result calls to page_enter, page_display, etc on the wrong page object (the initial page, rather than the requested page) - this is a more comprehensive fix to the problem. 2005-08-16 10:02 djc * albatross/app.py: Fixed incorrect empty argument list for page_enter() in load_page(). 2005-08-15 16:04 andrewm * albatross/: app.py, branchingsession.py, context.py, fcgiapp.py, pidfile.py, request.py, sessionfile.py, tags.py, template.py: - whitespace normalisation 2005-08-15 15:59 andrewm * setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: - bump version to 1.32. 2005-08-15 15:49 andrewm * albatross/context.py: - _caller_globals was raising and catching an exception, then referencing sys.exc_traceback to get the frame objects - sys.exc_traceback was deprecated in python 1.5, and appears to be unreliable in 2.4, so _caller_globals now uses sys._getframe(). 2005-08-15 15:39 andrewm * setup.py: - revert change of #! line back to /usr/bin/env 2005-08-05 14:15 gregh * albatross/app.py: - load_page sets start_page manually instead of calling set_page. Avoids possible nested calls to set_page which can result in a very confused session. 2005-07-28 15:04 djc * ChangeLog, setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: - Bump release to 1.31 2005-06-21 15:30 gregh * albatross/randompage.py: - handle ImportError for pages with '/' in the path. 2005-06-21 12:45 gregh * albatross/randompage.py: - fixed broken error handling for attempt to access unknown page using RandomPageModuleMixin 2005-05-31 15:23 andrewm * ChangeLog, setup.py, albatross/__init__.py, dist/release-1.30, doc/albatross.tex, doc/changes.tex, doc/installation.tex: - updates for 1.30 release. 2005-05-31 12:28 andrewm * albatross/app.py: - covert a tab to spaces. 2005-05-20 00:04 andrewm * albatross/context.py, doc/mixins.tex, test/namespace/recorder.py, test/templates/albatross_test.py: - add discard_file_resources method to ResourceMixin - drops macros and lookups associated with a given file when the template is reloaded. - restructured application and context objects used by unit tests to be more like normal application usage (specifically TemplaterLoader on app, rather than ctx). - start with a fresh application object for each unit test - macros and lookups are cached on the application object, and these were previously carried across multiple tests. 2005-05-19 11:18 andrewm * albatross/fcgiapp.py: - call fcgi.Finish() method from fcgiapp return_code() method (which is somewhat of a hack) - otherwise the fcgi stdout and stderr buffers are not reliably written to the web server. 2005-05-19 11:16 andrewm * albatross/app.py: - Explicitly delete traceback from local namespace (otherwise cycles result) 2005-05-12 15:11 andrewm * albatross/context.py: Add test to prevent macros and lookups being redefined (duplicate definitions cause obscure application failures). 2005-05-06 12:25 gregh * albatross/context.py: default args to imp_hook match args to __import__ 2005-05-05 18:14 andrewm * TODO, albatross/tags.py, doc/albatross.tex, doc/tags.tex, test/templates/lookup.py, test/templates/lookup/inline.html, test/templates/lookup/lookup.html, test/templates/lookup/no-close.html, test/templates/lookup/no-default.html: - add "inline" form of al-lookup tag. 2005-04-19 19:57 andrewm * albatross/tags.py: - bugfix - when used on al-select, albatross-specific "list" attribute was leaking into resulting markup. 2005-04-19 19:53 andrewm * albatross/tags.py, doc/doctest/tags-form-action, doc/doctest/tags-form-file, test/templates/form.py, test/templates/tree.py, test/templates/form/select-name-expr.html: - bugfix - "expr" attribute used with al-select resulted in invalid markup. - tests - update for
wrapping hidden fields. 2005-04-17 22:54 andrewm * albatross/branchingsession.py, albatross/context.py: - Issue raised by Robert Fendt: Albatross-generated hidden field input element must not appear naked inside a form element for strict HTML conformance. Solution is to wrap input elements in div's. 2005-03-01 14:12 andrewm * setup.py, albatross/__init__.py, albatross/tags.py, albatross/template.py, doc/albatross.tex, doc/installation.tex, doc/tags.tex, test/templates/__init__.py, test/templates/albatross_test.py, test/templates/require.py: - Add tag and bump version. 2005-03-01 10:53 andrewm * albatross/branchingsession.py: - reinstate "session id" in branching sessions - if session id or transaction id is not valid, the session has expired. This allows us to invalidate a group of transaction id's when a user logs out, preventing the browser button being used to return to a previous logged in state. 2004-11-03 16:48 gregh * albatross/app.py: Added 'Cache-Control: no-cache' to HTTP headers because IE sometimes ignores 'Pragma: no-cache'. 2004-10-27 16:51 djc * albatross/session.py, albatross/sessionfile.py: - Remove timeout on session cookie. Change session loading to that it raises SessionExpired only when a session id was present but no session was at the server. No special casing on GET requests any more. Added SessionServerContextMixin.new_session() method which creates a new session in the context and session server. SessionServerContextMixin.remove_session() no longer creates a new session, it removes the session and clears the session id so that the session cookie is sent with an empty id. 2004-10-21 17:02 andrewm * albatross/template.py: - Add new "AnyTag" wildcard tag. Any HTML tag can be prefixed with "al-", and the *expr or *bool attribute evalution will be applied. 2004-10-21 15:44 andrewm * albatross/template.py: - Base Tag class can now evaluate any attribute. If attribute name ends with "expr", the attribute string is evaluated and the result substituted. If the attribute name ends with "bool", an attribute is only output if the value evaluates to true. 2004-10-20 16:44 andrewm * albatross/tags.py, albatross/template.py: - moved eval_attrib() method to Tag class - renamed EvalAttribMixin to InputNameValueMixin 2004-10-20 16:38 andrewm * TODO, albatross/fcgiapp.py, albatross/tags.py, albatross/template.py, doc/customtags.tex: - move "assert_has_attribs" from EvalAttribMixin to Tags class, rename "assert_any_attrib" and add documentation. 2004-10-20 14:31 andrewm * doc/appuser.tex: - add examples of cgi and fastcgi mainlines. - added documention of albatross exception classes. 2004-10-20 13:07 andrewm * doc/: albatross.tex, appuser.tex, mixins.tex: - Add new subsection on Application Deployment Options to the manual, and collect all information about Request classes there (and write documentation on FastCGI and stand-alone deployment). 2004-10-19 23:50 andrewm * dist/release-1.20: - add short "changes" list to release announcement. 2004-10-19 23:24 andrewm * dist/release-1.20, doc/changes.tex: More 1.2 -> 1.20 changes 2004-10-19 23:21 andrewm * setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: - changed version from 1.2 to 1.20 so it will sort correctly. 2004-10-19 22:57 andrewm * test/sessions/request.py: - dummy request object used by session tests was missing get_method(). 2004-10-19 17:57 andrewm * ChangeLog, setup.py, albatross/__init__.py, dist/release-1.20, doc/albatross.tex, doc/installation.tex: - Bump version to 1.2 - Update ChangeLog 2004-10-19 17:41 andrewm * .cvsignore, doc/.cvsignore, doc/packaged.tex: * Reorganise "Prepackaged Application and Execution Context Classes" into Application classes, Context classes, and other stuff. 2004-10-19 17:31 djc * doc/: Makefile, appcontext.dia, application.dia, branchingsessioncontext.dia, modularapp.dia, modularsessapp.dia, modularsessfileapp.dia, randmodapp.dia, randmodsessapp.dia, randmodsessfileapp.dia, sessionappcontext.dia, sessionfileappcontext.dia, simpleapp.dia, simpleappcontext.dia, simplecontext.dia, simplesessapp.dia, simplesessfileapp.dia: Re-align the diagrams - dia buggers this up from time to time. 2004-10-19 14:35 andrewm * ChangeLog, setup.py, albatross/__init__.py, albatross/branchingsession.py, dist/release-1.2, doc/AlbatrossObjects.dia, doc/Makefile, doc/albatross.tex, doc/branchingsessioncontext.dia, doc/installation.tex, doc/mixins.tex, doc/packaged.tex: * Update doco for BranchingSession functionality. * Run dia export with --export-to-format=eps-builtin for better font rendering. 2004-10-19 10:14 andrewm * doc/changes.tex: * Updated changes.tex with 1.2 changes. 2004-10-18 22:01 andrewm * doc/changes.tex: * Update doco changes list to include differences between pre1 and pre2 2004-10-18 21:48 andrewm * ChangeLog: * ChangeLog included items from baseline (not just branch). 2004-10-18 18:32 andrewm * ChangeLog: * refresh ChangeLog 2004-10-18 17:58 andrewm * setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: * package 1.11 release (which is just 1.11pre2 with the version numbers changed). 2004-10-15 14:32 andrewm * albatross/: .cvsignore, __init__.py, branchingsession.py: * Add BranchingSessionMixin - a context mixin that stores sessions server-side, encoding a fresh session id in a hidden field for every interaction with the server. By maintaining a large number of "old" session contexts, and storing a session identifier in a hidden field, we can match sessions with browser state (in the face of reloads and "back" button usage). 2004-08-24 16:07 andrewm * doc/mixins.tex: * fix typo 2004-03-23 16:54 andrewm * dist/dist-albatross.sh: Quick fix to version munging in dist building script. 2004-03-23 16:52 andrewm * samples/templates/: content1/content.py, content2/content.py: Quick fix to abs template path problem. 2004-03-15 19:53 andrewm * setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: Bumped version numbers. 2004-03-15 19:47 andrewm * albatross/: session.py, sessionfile.py: Issue a new session after remove_session is called - this is wrong, but makes server side sessions consistent with hidden field behaviour, and satisfies other assumptions that a valid sesion always exist. 2004-03-15 14:18 andrewm * albatross/: apacheapp.py, cgiapp.py, common.py, fcgiapp.py, httpdapp.py, session.py, sessionfile.py: Raise SessionExpired if request method is not GET (i.e. POSTing form data) and there is no session (either no cookie, or session data missing). Also try to clear cookie after a remove_session (unlikely to work in many cases). 2004-03-10 13:02 andrewm * albatross/: session.py, sessionfile.py: * Don't attempt to save or remove a session after it has been removed. 2004-03-10 12:04 andrewm * albatross/: session.py, sessionfile.py: * Set cookie max-age based on session server age param 2004-03-10 11:49 andrewm * albatross/app.py, albatross/session.py, albatross/sessionfile.py, test/sessions/ses_file.py, test/sessions/ses_server.py: * Set "secure" attribute on cookies used via https (which, in theory, prevents their later use via http). * Pulled cookie setting and getting code out of SessionServerContextMixin and SessionFileContextMixin into new SessionCookieMixin * Exposed request URI parsing method of app.py as parsed_request_uri() 2004-02-03 11:16 andrewm * ChangeLog, setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: Updated for development snapshot 20040203 2004-01-15 16:31 andrewm * albatross/apacheapp.py, albatross/app.py, albatross/cgiapp.py, albatross/httpdapp.py, albatross/request.py, doc/albatross.tex, doc/mixins.tex, test/misc/pagemodule.py: Created new RequestBase class containing RequestField and RequestStatus classes, changed apacheapp, cgiapp, fcgiapp and httpdapp over to use the new base. Added new return_code() method to Request objects. Fixed mod_python (apacheapp) status handling (always return apache.OK). 2004-01-15 16:28 andrewm * test/misc/uriparse.py: Some tests for URI parsing (missed from previous checkin). 2004-01-14 16:10 andrewm * ChangeLog, setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: Update for 1.2-dev-20040114 2004-01-14 15:22 andrewm * albatross/app.py, test/misc/__init__.py: Fixes to redirect_url() suggested by Sheila King and Michael Neel, and some associated tests. 2004-01-14 13:57 andrewm * albatross/app.py, albatross/context.py, doc/.cvsignore, doc/mixins.tex, test/all.py, test/misc/.cvsignore, test/misc/__init__.py, test/misc/pagemodule.py, test/misc/modules/.cvsignore, test/misc/modules/missing_methods.py, test/misc/modules/run_template.py, test/misc/modules/simple.html, test/misc/modules/simple_module.py, test/misc/modules/submod.py, test/misc/modules/submod/.cvsignore, test/misc/modules/submod/module_decode.py, test/misc/modules/submod/module_hierarchy.py: Import page modules into a dummy module to prevent them from poluting the python module namespace. Use an import hook to load page modules, rather than prefixing sys.path around the cPickle.loads in decode_session(). Added tests for the new page module loader. 2004-01-14 11:19 andrewm * albatross/tags.py: - minor rationalisation of attribute handling. 2004-01-09 23:46 andrewm * albatross/cgiapp.py: * fix last minute typo in previous check-in. 2004-01-09 23:38 andrewm * albatross/apacheapp.py, albatross/app.py, albatross/cgiapp.py, albatross/common.py, albatross/httpdapp.py, standalone-server/al-httpd: * added HTTP/1.0 status header definitions from RFC1945 as well as some helper methods to classify status codes and a method to convert a status code into a string. Converted all hard coded status codes to use the new symbolic HTTP_* names. * in cgiapp.Request, if status is not "200 OK", then emit a "Status:" header. * when an exception occurs, set status to HTTP_INTERNAL_SERVER_ERROR * refactored httpdapp - it's Request class now proxies requests directly to the handler - previously it saved the data and the handler pulled it out and called methods on the base. * httpdapp status_resources is now a list of tuples, although a dictionary will still be accepted. This allows the resources to be ordered by priority. * httpdapp: output content even when status != 200 (otherwise no tracebacks!) * changed al-httpd's argument parsing to give the usage message when the status resources aren't paired. 2004-01-09 15:34 andrewm * TODO, albatross/apacheapp.py, albatross/app.py, albatross/httpdapp.py, doc/mixins.tex: * bugfix - response header handling should not have been case sensitive. * enhancement - allow multiple headers with the same name (Cookies, in particular). NOTE: that ResponseMixin.get_header() now returns a list. * Changed httpdapp to store headers in a list, rather than a dictionary (allows multiple headers of the same name). 2004-01-09 11:32 andrewm * TODO, albatross/app.py, doc/appuser.tex, doc/tags.tex, samples/popview1/popview.py, samples/popview2/popview.py: * Fixed: is confusing. Consider having ctx.req_equals() check for "name.x" if "name" doesn't exist? (Michael C. Neel) 2003-11-10 11:35 djc * albatross/app.py: Fix Cookie path bug noticed in Safari where absolute_base_url() was generating /path/app.cgi/ instead of /path/app.cgi. This was causing problems for requests like /path/app.cgi?blah in that Safari did not send the cookie (probably correctly). 2003-10-03 13:56 djc * dist/dist-albatross.sh, doc/Makefile, doc/albatross.tex, doc/installation.tex, doc/pstumble: Remove the PostScript booklet from the distribution - acroread 5 doesn't like the PDF created by pdflatex while converting to PS. 2003-10-03 11:46 djc * setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: Prepare for 1.11pre2 release. 2003-10-03 11:32 djc * albatross/tags.py: Make work exactly the same way as with respect to name and value attributes. 2003-09-21 16:58 djc * doc/installation.tex: Explain how to make an application private installation. 2003-09-21 15:59 djc * dist/dist-albatross.sh: Do not specify DISPLAY. 2003-09-21 14:45 djc * ChangeLog: Update changelog for new release. 2003-09-21 14:40 djc * albatross/context.py, doc/doctest/tags-form-action, doc/doctest/tags-form-file, doc/doctest/tags-img, doc/doctest/tags-img-noescape, doc/doctest/tags-input-checkbox, doc/doctest/tags-input-file, doc/doctest/tags-input-generic, doc/doctest/tags-input-image, doc/doctest/tags-input-nameexpr, doc/doctest/tags-input-nextpage, doc/doctest/tags-input-noescape, doc/doctest/tags-input-radio1, doc/doctest/tags-input-radio2, doc/doctest/tags-input-radio3, doc/doctest/tags-input-treeselect, doc/doctest/tags-input-treeselect-node, test/namespace/recorder.py, test/sessions/request.py, test/sessions/ses_file.py, test/sessions/ses_server.py, test/templates/basic.py, test/templates/form.py, test/templates/tree.py: Forgot to update the unittests - ooooooops. 2003-09-21 14:06 djc * doc/changes.tex: Forgot to document a change. 2003-09-21 14:03 djc * setup.py, albatross/__init__.py, albatross/app.py, albatross/fcgiapp.py, albatross/session.py, albatross/sessionfile.py, doc/albatross.tex, doc/appcontext.dia, doc/changes.tex, doc/doc_methods.py, doc/installation.tex, doc/packaged.tex, doc/sessionappcontext.dia, doc/sessionfileappcontext.dia, doc/simpleappcontext.dia: Remove the new page module loader - now releasing 1.11pre1 not 1.20pre1. Make sure that all of the fixes on the mailing list were applied. Bring documentation up to date. 2003-09-20 22:49 djc * doc/changes.tex: Document the changes since 1.10 2003-09-20 22:27 djc * setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: Prepare for 1.20pre1 release. 2003-09-20 22:26 djc * albatross/app.py: Improved page module loader. Does not pollute sys.modules. WARNING will cause breakage - but it is worth it. 2003-09-20 22:16 djc * doc/: albatross.tex, appuser.tex, customtags.tex, fakeapp.py: Update documentation to match changes to samples. 2003-09-20 21:54 djc * standalone-server/al-httpd: Very nasty hack to override base_url member in Application. This allows the RandomPageModuleMixin to work without the browser having to explicitly specify the base_url in the request. 2003-09-20 21:45 djc * samples/random/: install.py, paginate.html, paginate.py, tree.html, tree.py, pages/paginate.html, pages/paginate.py, pages/tree.html, pages/tree.py: Allow sample to be run via al-httpd. 2003-09-20 21:20 djc * samples/: mpperf/mpperf.py, sybase/sybase.py, tree1/tree.py, tree2/tree.py, tree3/tree.py: More samples working under al-httpd. 2003-09-20 21:07 djc * standalone-server/al-httpd: Allow uri/fs static resource pairs to be specified on the command line. 2003-09-20 20:46 djc * samples/random/: install.py, randompage.py, tree.py, utils.py: Do not define Node in a page module. This breaks the MVC idea (and stops the page module from working with the new page module loader...). 2003-09-20 20:00 djc * doc/Makefile: Clean up more files. 2003-09-20 19:59 djc * samples/random/: install.py, random.py, randompage.py: Rename random.py to randompage.py to prevent clash with Python standard module. 2003-09-20 17:32 djc * samples/: extension/cal.py, form4/form.py, paginate/paginate.py, popview1/popview.py, popview2/popview.py, popview3/popview.py, popview4/popview.py, popview5/popview.py, random/random.py: Make use of al-httpd possible for samples. 2003-09-08 10:11 djc * samples/templates/: form2/form.html, form3/form.html: Added name="" attribute to tags to fix reported error. 2003-08-17 21:17 djc * samples/extension/cal.py: Works with al-httpd. 2003-08-17 21:00 djc * setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: Bump release number in preparation for next release. 2003-08-17 20:59 djc * albatross/httpdapp.py: Applied latest and greatest patches from Matt Goodall. 2003-08-17 20:39 djc * albatross/cgiapp.py: Applied get_Servername() patch from Damien Elmes. 2003-08-10 22:02 djc * albatross/tags.py: Added patch from Matt Goodall to make and produce XHTML. 2003-08-10 21:22 djc * albatross/: session.py, sessionfile.py: Cookie path fix from Matt Goodall. 2003-08-10 21:20 djc * albatross/cgiapp.py: Yet another mod_python problem fixed by Greg Bond. 2003-08-10 21:18 djc * doc/: albatross.tex, tags.tex: Added explanation that non-recognised tag attrivutes are passed through to output unchanged. 2003-07-20 17:27 djc * ChangeLog, setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: Chnage release to 1.10. 2003-07-20 17:16 djc * dist/release-1.10, doc/albatross.tex: Wrote 1.10 release notice. 2003-07-13 16:41 djc * albatross/app.py: SMall bugfix - decided to test this time... 2003-07-13 16:28 djc * doc/: albatross.tex, appcontext.dia, sessionappcontext.dia, sessionfileappcontext.dia, simpleappcontext.dia: Added send_redirect() to ResponseMixin diagrams. 2003-07-13 16:13 djc * setup.py, albatross/__init__.py, albatross/app.py, doc/albatross.tex, doc/installation.tex, doc/mixins.tex: Some idiot introduced an new redirect() method in ResposeMixin that was shadowed by AppContext. Renamed to send_redirect(). 2003-07-13 12:27 djc * albatross/app.py, doc/mixins.tex: Added redirect() method to ResponseMixin so that it sets cookie on a redirect. 2003-07-13 12:26 djc * albatross/randompage.py: Added latest randompage patch from Matt. Hopefully this is the one. 2003-07-12 17:19 djc * setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: Bump release ready for pre3 release. 2003-07-12 17:00 djc * doc/mixins.tex: Added some more explanation of ResponseMixin. 2003-07-12 16:49 djc * doc/: albatross.tex, mixins.tex: Documented the ResponseMixin class. 2003-07-12 14:42 djc * albatross/: apacheapp.py, app.py, cgiapp.py, fcgiapp.py, httpdapp.py: Added get_path_info() to all requests as per Matt Goodalls patch. Reverted previous changes to base_url setup in Application constructor. 2003-07-12 14:09 djc * albatross/httpdapp.py: Return only path in get_uri(). Thanks Matt. 2003-07-08 22:56 djc * albatross/context.py, albatross/randompage.py, doc/albatross.tex, doc/mixins.tex, doc/randmodapp.dia, doc/randmodsessapp.dia, doc/randmodsessfileapp.dia, doc/sessionappcontext.dia, doc/sessionfileappcontext.dia, doc/simpleappcontext.dia: Couldn't help myself - I implemented session_vars() and get_page_from_uri(). 2003-07-08 09:55 andrewm * albatross/fcgiapp.py: Fix from Matt Goodall - get_header was using __req, rather than __fcgi. 2003-07-07 23:08 djc * albatross/app.py: Applied new and improved random page patch from Matt Goodall. 2003-07-07 22:35 djc * doc/: Makefile, TODO, albatross.tex, doc_methods.py, packaged.tex: Automatically generate method location tables. 2003-07-07 22:35 djc * doc/modularsessfileapp.dia: Fixed inheritance error spotted by Dougal Scott. 2003-07-06 14:51 djc * doc/mixins.tex: First pass at making mixin reference match the current code. 2003-07-06 14:06 djc * doc/customtags.tex: Documented the Tag.set_attrib_order() method. 2003-07-06 13:40 djc * samples/: paginate/paginate.py, popview1/popview.py, popview2/popview.py, popview3/popview.py, popview4/popview.py, random/random.py, tree1/tree.py, tree2/tree.py, tree3/tree.py: Make all samples runnable via al-httpd. 2003-07-06 13:40 djc * doc/Makefile: Run pstumble from current directory. 2003-07-06 13:40 djc * doc/doctest/tags-comment: Improved example by adding before and after content. 2003-07-05 20:46 djc * doc/: changes.tex, tempuser.tex: Final documentation updates before release. 2003-07-05 20:29 djc * albatross/httpdapp.py, standalone-server/al-httpd: Added copyright message for Matt Goddall's contributed code. 2003-07-05 17:52 djc * albatross/: app.py, randompage.py: Applied Matt Goodall's patch for fixing random page applications with standalone server. 2003-07-05 17:36 djc * doc/changes.tex: Added change notice for FastCGI and BaseHTTPServer support. 2003-07-05 17:35 djc * samples/templates/: content1/content.py, content2/content.py: Use TemplateLoadError exception. 2003-07-05 17:08 djc * doc/doctest/tags-textarea: Renamed to tags-textarea1 2003-07-05 17:08 djc * doc/introduction.tex: More points in introduction as per Matt Goodall suggestion. 2003-07-05 17:07 djc * doc/thanks.tex: More thanks for Matt Goodall. 2003-07-05 17:07 djc * doc/albatross.tex: Added more see also links. 2003-07-05 17:06 djc * doc/installation.tex: Small update clarifying dia release and Python release for documentation building. 2003-07-05 17:06 djc * doc/: tags.tex, doctest/tags-img-noescape, doctest/tags-input-noescape, doctest/tags-textarea1, doctest/tags-textarea2: Documented the new noescape tags. 2003-07-05 13:49 djc * albatross/: app.py, cgiapp.py, context.py, session.py, sessionfile.py: PEP-8 compliant imports. 2003-07-05 13:48 djc * albatross/tags.py: Forgot to include noescape in write_attribs_except(). 2003-07-05 13:12 djc * albatross/apacheapp.py, albatross/app.py, albatross/cgiapp.py, albatross/common.py, albatross/context.py, albatross/fcgiapp.py, albatross/pidfile.py, albatross/randompage.py, albatross/session.py, albatross/sessionfile.py, albatross/simpleserver.py, albatross/tags.py, albatross/template.py, samples/extension/cal.py, samples/form4/form.py, samples/mpperf/mpperf.py, samples/paginate/paginate.py, samples/popview1/popview.py, samples/popview1/popviewlib.py, samples/popview2/popview.py, samples/popview2/popviewlib.py, samples/popview3/popview.py, samples/popview3/popviewlib.py, samples/popview4/detail.py, samples/popview4/list.py, samples/popview4/login.py, samples/popview4/popview.py, samples/popview4/popviewlib.py, samples/popview5/detail.py, samples/popview5/list.py, samples/popview5/login.py, samples/popview5/popview.py, samples/popview5/popviewlib.py, samples/random/random.py, samples/random/tree.py, samples/sybase/sybase.py, samples/templates/tree/tree.py, samples/tree1/tree.py, samples/tree2/tree.py, samples/tree3/tree.py, session-server/al-session-daemon, standalone-server/al-httpd: Went through all files and applied PEP-8 rules on blank lines. 2003-07-05 12:53 djc * albatross/httpdapp.py, samples/form4/form.py, standalone-server/al-httpd: Small fixes for al-httpd. Tested with samples/form4. 2003-07-05 12:38 djc * MANIFEST.in, setup.py, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: Preparing for release 1.10pre2 2003-07-05 12:38 djc * albatross/httpdapp.py, standalone-server/al-httpd: Added the standalone server from Matt Goodall. Still need to put a copyright notice on it. 2003-07-04 11:49 djc * albatross/tags.py: Add noescape attribute to , , (values returned by expression), and . 2003-07-03 22:41 djc * doc/appuser.tex: Added more execution context/application explanation as per Tim's suggestion. 2003-07-01 21:42 andrewm * albatross/: cgiapp.py, context.py: Fix a couple of Python2.1 incompatibilities (True/False and list/str being factory functions rather than types). 2003-07-01 20:15 djc * doc/appuser.tex: Fixed unclear popview3 sample documentation. 2003-06-29 19:47 djc * TODO: Removed tasks that have been completed. 2003-06-29 19:32 djc * doc/tags.tex: Added small explanation of how to detect and process browser input for some types. 2003-06-29 15:56 djc * doc/doctest/tags-for7: Simplified example. 2003-06-29 15:44 djc * doc/: tags.tex, doctest/tags-for7: Expanded explanation of in response to concerns from Sheila King and Matt Goodall. 2003-06-29 14:21 djc * doc/thanks.tex: Make html and pdf more consistant. 2003-06-29 14:21 djc * doc/copyright.tex: Update years (not sure this is really necessary). 2003-06-29 14:20 djc * doc/Makefile: Move to Python2.3 for correct verbatiminput downloads. 2003-06-29 14:03 djc * doc/: albatross.tex, tags.tex, doctest/tags-macro8: Finished reorganising the tags reference. Yay! 2003-06-23 15:10 djc * albatross/tags.py: Do not need the content trap for macro arguments anymore. 2003-06-19 23:08 andrewm * test/templates/: form.py, form/missing-name.html, form/null-name.html: Added tests for missing name and null name on input tags. 2003-06-19 23:07 andrewm * albatross/tags.py: Added check for null name attribute. 2003-06-19 22:33 andrewm * albatross/tags.py: Refactored tags.py: * moved duplicated 'name', 'alias' and 'nameexpr' code into a common method * made compilation of expr attributes lazy (compilation now occurs at template run time, rather than template load time, and only when the attribute is accessed). * other *expr-style attributes now use common code object caching and lazy compilation. The result has some minor semantic differences - previously an attribute was tested for __nonzero__, the changed code tests against attribute existance. This will effect cases such as , 2003-06-19 18:41 andrewm * doc/: changes.tex, mixins.tex, tempuser.tex: Reinstate TemplateLoadError as a subclass of ApplicationError. 2003-06-19 18:28 andrewm * albatross/: common.py, context.py: Reinstate TemplateLoadError as a subclass of ApplicationError. 2003-06-19 18:20 andrewm * albatross/context.py, test/templates/form.py, test/templates/form/nested-form.html: Added __needs_close attribute to NameRecorderMixin - raise an ApplicationError in form_close if not set (i.e., form_close() has already been called, which implies the template contains nested forms). 2003-06-19 13:21 andrewm * albatross/app.py: Added optional "pop until" page name argument to pop_page(). 2003-06-17 21:58 djc * doc/: appuser.tex, changes.tex: Name more documentation nodes for HTML version. 2003-06-17 21:28 djc * doc/tags.tex: Restructured the documentation. Could probably do with more examples and text explaining the various attribute subsubsections. 2003-06-17 21:03 djc * doc/: albatross.tex, tags.tex, doctest/tags-item: Updated and documentation. 2003-06-17 14:59 andrewm * albatross/apacheapp.py: Mod_python 3 has done away with the "server" member of the "connection" object - use request.hostname member instead (Paul Hart). 2003-06-15 21:04 djc * doc/: albatross.tex, tags.tex, doctest/tags-for4, doctest/tags-for5, doctest/tags-for6: Restructured the documentation. 2003-06-13 14:29 andrewm * albatross/tags.py: Unknown was causing an exception in the exception handler - wrong variable used in diag string. 2003-06-12 13:04 andrewm * albatross/tags.py: Don't record inputs that are "disabled". 2003-06-09 20:00 andrewm * albatross/: apacheapp.py, app.py, context.py, randompage.py, session.py, simpleserver.py, tags.py, template.py: Replace apply(fn, args) with fn(*args) (apply is 300 times slower due to Deprecation warning). Use string methods rather than string module. 2003-06-09 16:18 djc * doc/: tags.tex, doctest/tags-exec, doctest/tags-exec1, doctest/tags-exec2: Expanded and segmented the documentation. 2003-06-09 16:06 djc * doc/: tags.tex, doctest/tags-value3: Split documentation into attribute subsubsections. 2003-06-09 15:56 djc * doc/: tags.tex, doctest/tags-if, doctest/tags-if1, doctest/tags-if2, doctest/tags-if3: Expanded the documentation of the tag. 2003-06-09 15:29 djc * doc/: tags.tex, doctest/tags-include, doctest/tags-include1, doctest/tags-include2: Updated documentation for and . 2003-06-09 14:49 djc * doc/: albatross.tex, tags.tex: Updated documentation. 2003-06-09 11:12 djc * doc/thanks.tex: Add some more thanks. 2003-06-08 16:29 djc * LICENCE: Not sure we need to update the year in the copyright. Did it anyway as per Matt Goodalls suggestion. 2003-06-08 16:24 djc * albatross/fcgiapp.py: Changes from Matt Goodall. 2003-06-08 00:35 djc * setup.py: Forgot the version in the setup.py - D'oh. Time to update wiki page. 2003-06-08 00:33 djc * dist/dist-albatross.sh: Attempting to version the HTML documentation tar file. 2003-06-08 00:29 djc * ChangeLog, albatross/__init__.py, doc/albatross.tex, doc/installation.tex: Final changes for 1.10pre1 release - just following wiki instructions. 2003-06-07 18:54 djc * samples/sybase/traceback.html: More meaningful title in exception template. 2003-06-07 18:53 djc * albatross/fcgiapp.py: Fixed module so that it actually works. 2003-06-07 18:53 djc * albatross/context.py: Oops - default_session_var() was obviously never tested. 2003-06-07 15:22 djc * doc/appuser.tex: Fixed documentation for installing mod_python applications. 2003-06-07 15:17 djc * samples/sybase/: sybase.py, table-view.html, traceback.html: Fixes for latest Albatross. 2003-06-07 15:16 djc * albatross/app.py: Handle exception during execution context constructor. 2003-06-07 00:21 andrewm * albatross/fcgiapp.py: Added Matt Goodall's FastCGI application deployment support module. 2003-06-07 00:13 andrewm * albatross/: apacheapp.py, cgiapp.py: Added support for file uploads to mod_apache deployed applications. Restructured cgiapp and apacheapp to use a common RequestFields class. Upated cgiapp to Python2 constructs (list comprehensions, string methods). 2003-06-06 23:00 andrewm * albatross/app.py: Fix bug in when used with SimpleAppContext (need_enc_type flag was not being returned by form_close method. 2003-06-05 21:49 djc * doc/tags.tex: Finished documentation. 2003-06-05 21:32 djc * doc/: tags.tex, doctest/tags-input-select5: Added another example. 2003-06-05 21:21 djc * TODO: Added two tasks for 1.10pre1 release. 2003-06-05 21:15 djc * doc/: albatross.tex, tags.tex: Removed the subsection on Mixin functions used. Reworked the and docuementation. 2003-06-05 21:04 djc * doc/changes.tex: List each change item as a subsubsection rather than a description item. 2003-06-05 15:33 andrewm * setup.py, albatross/tags.py, doc/tags.tex, doc/doctest/tags-input-select1, doc/doctest/tags-input-select2, doc/doctest/tags-input-select3, doc/doctest/tags-input-select4, doc/doctest/templ-white2, doc/doctest/templ-white3, doc/doctest/templ-white4, doc/doctest/templ-white5, test/templates/form.py: Minor bugfix, doctest and unittest fixes, change package name to albatross_utf8. 2003-05-29 15:15 andrewm * albatross/context.py: Use cPickle.dumps(..., -1), which selects the highest pickling protocol in all versions of python (in particular, in 2.3, this allows us to pickle slotted objects). 2003-05-23 00:14 djc * doc/: albatross.tex, tags.tex: Whew! Almost finished the tag reference. 2003-05-23 00:13 djc * doc/packaged.tex: Caption fixup from Sheila King. 2003-05-21 21:22 djc * doc/tags.tex: Small fix before I call it a night. 2003-05-21 21:09 djc * doc/: albatross.tex, fakeapp.py, tags.tex, test_examples.py, doctest/tags-form-action, doctest/tags-form-file, doctest/tags-input-generic, doctest/tags-input-hidden, doctest/tags-input-nextpage, doctest/tags-input-password, doctest/tags-input-submit, doctest/tags-input-text, doctest/tags-input-treeselect, doctest/tags-input-treeselect-node: Made significant progress on improving the tag documentation. Still needs more work though. 2003-05-21 14:13 djc * albatross/tags.py: Ooops - was ignoring node attribute on treeselect,... input tags. 2003-05-20 22:34 djc * albatross/tags.py: Ooops - forgot to pass ctx to Lookup._build_item_dict(). 2003-05-18 19:04 djc * doc/appuser.tex: Fixed up for all latest changes. One day Ben will fix the sample popview application. 2003-05-18 19:03 djc * doc/albatross.tex: Changes are now an appendix. 2003-05-14 22:41 djc * doc/: albmvc.dia, appcontext.dia, application.dia, dataflow.dia, modularapp.dia, modularsessapp.dia, modularsessfileapp.dia, mvc.dia, pagemap.dia, randmodapp.dia, randmodsessapp.dia, randmodsessfileapp.dia, sessionappcontext.dia, sessionfileappcontext.dia, simpleapp.dia, simpleappcontext.dia, simplecontext.dia, simplesessapp.dia, simplesessfileapp.dia, twolayer.dia, twolayerctx.dia: Update diagrams to match current code. Funny dia crap still in files due to downgrade to 0.88 to get back to useable result. 2003-05-14 16:32 andrewm * TODO: Added samples/popview1 todo points raised by Sheila King. 2003-05-10 23:42 djc * doc/tempuser.tex: Checked for correctness with new changes. Small wording improvements. 2003-05-10 23:03 djc * albatross/tags.py: Item dictionary *must* be created when lookup is executed (if not already created by early lookup). 2003-05-10 22:41 djc * doc/mixins.tex, doc/tempuser.tex, samples/templates/content1/content.py, samples/templates/content2/content.py: TemplateLoadError is now ApplicationError. 2003-05-10 22:15 djc * doc/introduction.tex: Remove fixed number of Application classes - just say "many". 2003-05-10 22:14 djc * doc/installation.tex: If we depend on at least Python 2.1 we do not need distutils. 2003-05-10 22:13 djc * doc/: albatross.tex, changes.tex: Included a chapter on changes between releases. Should probably be an appendix. 2003-05-02 14:12 andrewm * albatross/app.py, albatross/context.py, doc/appcontext.dia, doc/packaged.tex, test/namespace/recorder.py: Implemented a stack for application pages (push_page, pop_page). 2003-05-02 14:10 andrewm * albatross/simpleserver.py: Add crude counting of memory usage to the session server. 2003-05-01 16:40 andrewm * albatross/tags.py: Fix bug in checkbox "checked" logic. 2003-04-28 12:43 djc * TODO: Macro argument evaluation now happens when evaluated. 2003-04-28 12:42 djc * albatross/context.py, albatross/tags.py, doc/albatross.tex, doc/doctest/tags-macro6: Delay evaluation of macro arguments until they are referenced by an tags. now temporarily pops argument stack before evaluating argument content to avoid infinite recursion. 2003-03-24 17:10 djc * TODO: Added task to reorder macro argument evaluation. 2003-03-21 16:43 djc * albatross/context.py: Force unicode strings to utf-8. 2003-03-21 16:33 djc * albatross/context.py: Clear all parts of the ExecuteMixin when reset_content() is called. 2003-03-21 15:50 djc * albatross/context.py: Undo unicode handling. 2003-03-21 15:20 djc * albatross/context.py: Change ExecuteMixin.reset_content() so it resets trap_stack and macro_stack as well as content_parts. This lets exception handling code get the execution context back to a known state. Any content traps on the stack will prevent flush_content() from producing output. 2003-02-14 16:35 andrewm * albatross/: app.py, context.py, tags.py, template.py: Unicode output largely works now - the context gains a new method, set_content_charset, which is the character set used to encode the content - this probably should be on the request object. The TemplateLoader mixins gained a set_template_encoding method (defaults to utf-8). Calls to str() in template.py have been replaced with calls to unicode() - however this isn't a drop in replacement - it tries to decode strings using the "ascii" codec, which will throw an exception if it's input isn't 7-bit clean - the user now has to be careful to pass a unicode string or a 7 bit clean string. 2003-02-14 15:08 andrewm * albatross/tags.py, doc/doctest/tags-input-select1, doc/doctest/tags-input-select2, test/templates/form.py: Missed another instance of this bug: The option tag was never being closed - remarkably the tests were also expecting this erroneous result. 2003-02-14 14:54 andrewm * doc/tags.tex: Added note to tag about enclosed content needing to be syntactically correct. 2003-02-14 14:43 andrewm * albatross/tags.py, doc/doctest/tags-input-select3, doc/doctest/tags-input-select4, test/templates/form.py: Bugfix - The option tag was never being closed - remarkably the tests were also expecting this erroneous result. 2003-01-28 16:17 andrewm * albatross/cgiapp.py: Use the dict get method to fetch REQUEST_URI and SERVER_NAME, which allows scripts to be run from the command line - suggested by Greg Bond. 2003-01-27 21:23 djc * albatross/apacheapp.py, albatross/app.py, albatross/cgiapp.py, albatross/randompage.py, doc/albatross.tex, test/sessions/request.py, test/sessions/ses_file.py, test/sessions/ses_server.py: Moved all header management into HeadersMixin which was renamed as ResponseMixin. Application send_content() method moved into ResponseMixin so end of headers can be detected automatically. Now raises exception if application attempts to set headers after they have been sent. Headers automatically sent when the application sends content. __sent_headers removed from cgiapp and apacheapp. 2003-01-24 23:45 andrewm * albatross/context.py: When encoding session, don't silently discard unpickleable elements (raise ApplicationError instead, including the name of the offending element). cPickle will raise all sorts of garbage exceptions - make sure we catch the common ones. 2003-01-24 23:35 andrewm * albatross/common.py: Forgot the most important bit - the exception definitions! 2003-01-24 22:34 andrewm * TODO, albatross/__init__.py, albatross/app.py, albatross/context.py, albatross/randompage.py, albatross/session.py, albatross/sessionfile.py, albatross/simpleserver.py, albatross/tags.py, albatross/template.py, test/namespace/request.py, test/templates/basic.py, test/templates/control_flow.py, test/templates/form.py, test/templates/include.py, test/templates/iterator.py, test/templates/lookup.py, test/templates/macro.py, test/templates/whitespace.py: Rationalised Albatross's use of exceptions, all exceptions are now subclasses of AlbatrossError, moved all exceptions to a "common" module. Also fixed a minor glitch in the tests where template execution output needed to be suppressed. 2003-01-24 22:24 andrewm * albatross/tags.py: Make single_select on the LazyTreeIterator an optional argument (defaults to "no"). 2003-01-24 14:48 andrewm * doc/appuser.tex: Added a little more information about setting up mod_python. 2002-12-28 16:47 djc * albatross/: app.py, randompage.py: Move call to ctx.flush_content() into Application.run() to allow session to be saved back at server before the HTML is sent to the browser. This allows applications to embed image tags for dynamically generated images. 2002-12-27 16:40 djc * albatross/tags.py: Added load_children() method to LazyTreeIterator so applications can stop abusing the node_is_open() method. 2002-12-27 12:34 djc * albatross/app.py: Create exception formatting context using different name so the we remove the original session when we call ctx.remove_session. Derrr. 2002-12-24 21:02 djc * albatross/app.py: Move escaping into formatted exception user. 2002-12-24 20:53 djc * albatross/app.py: Move exception formatting into separate method to allow subclasses to make use of formatting without needing to roll it themselves. 2002-12-23 16:48 djc * albatross/: app.py, context.py, randompage.py: Patches for problems reported by Greg Bond. Changed the way that _caller_globals() locates the desired stack frame it now searches by function name. run_template_once() now generates the same exception message as run_template(). 2002-12-23 12:07 djc * albatross/tags.py: Added NODE attribute to and tags which perform tree navigation. This allows tree navigation requests to be generated outside of the tag. Something like this: 2002-12-23 12:04 djc * albatross/context.py: Place the execution context into the execution context for the duration of expression evaluation under the name __ctx__. This allows you to do things like: 2002-12-18 17:06 andrewm * albatross/tags.py: * Added select_alias and open_alias to TreeIterators. * Move Lookup registration into object initialisation, lazy evaluation of item_dict (defered until first lookup_html) - to_html() method of Lookup becomes a NOOP. 2002-12-17 12:21 gregh * albatross/app.py: handle_exception now gets 'ctx' aswell as 'req' 2002-12-17 11:28 andrewm * albatross/context.py: Bugfix - If a user's session expires at the server side but not at the client, method="get" is being used, and the form contains a iterator, a traceback would result (attempt to call set_backdoor() on None). 2002-12-15 19:32 djc * albatross/tags.py: Implemented a single select option on LazyTreeIterator which is activated via the SINGLE attribute on the tag. 2002-12-11 19:36 andrewm * test/namespace/request.py: Added tests for file input handling (single field single file, multiple field, single field multiple file, __albform__, etc). 2002-12-11 16:08 andrewm * albatross/cgiapp.py, test/Makefile, test/namespace/__init__.py, test/namespace/request.py: Added first cut at request merging tests (tests for GET and POST exist cgi methods exist, although __albform__ functionality isn't being exercised yet). 2002-12-11 15:10 andrewm * albatross/: cgiapp.py, context.py, tags.py: Added support for . For inputs of this sort, the local context now gets a list of FileField instances. The FileField class contains attributes for filename (filename), open file object (file), and mime type (type). The NameRecorder Mixin tracks whether a type="file" input has been seen, and the to_html method of the Form class generates the appropriate enctype attribute if seen (otherwise file inputs simply pass back the filename). On type="file" inputs, "value" attributes are now ignored. Currently this is only supported on the cgiapp request class, and no documentation or tests have been written. 2002-12-11 12:23 djc * albatross/tags.py: Included explanation of why the content trap is needed for the tag. 2002-12-06 18:06 andrewm * albatross/context.py, doc/mixins.tex: - Raise an exception (SessionError) if add_session_vars is called when the named variable isn't in the local namespace (minor compatibility breakage). - New method on SessionBase, default_session_var, which adds a variable to the local namespace if it isn't already defined, and registers the variable in the session dictionary. - Update doco for the above changes. 2002-12-06 18:01 andrewm * doc/doctest/tags-input-text: Doco example fix for this bugfix: - was not generating a value attribute when the value evaluated to 0 (zero) - fixed and added test. Bug discovered by Michael Neel (thanks). 2002-12-05 15:11 andrewm * albatross/app.py: New improved version of req_equals (one less hash lookup!) 2002-12-05 14:45 andrewm * albatross/app.py: Make req_equals return true if the request evaluates true, rather than "not None" - this allows null strings to return false (found by Michael C. Neel). 2002-12-05 11:26 djc * albatross/randompage.py, test/templates/basic.py, test/templates/include.py: Ran tabnanny and noticed some problems. 2002-12-05 11:17 djc * TODO: Added some suggestions from Michael C. Neel. 2002-11-29 21:12 djc * albatross/: apacheapp.py, app.py, cgiapp.py: Apply proposed changes for altering the status returned to the browser. 2002-11-29 14:58 andrewm * TODO: Added a couple of items from the albatross-users list to the TODO list. 2002-11-28 16:35 andrewm * albatross/context.py: NamespaceMixin.set_value() now has a funky exception handler. This method is responsible for parsing form elements (a somewhat critical task) and it was possible to cause it to throw an exception that was completely lacking in any useful information. The exception handler now identifies the field by name, attempts to identify where in the field name the exception occured, and includes the current "index" where an IndexError is raised, and re-raises the original exception (in case it really is a bug in set_value()). 2002-11-28 15:40 djc * albatross/app.py, albatross/session.py, albatross/sessionfile.py, doc/albatross.tex, test/sessions/ses_file.py, test/sessions/ses_server.py: Change the way that response headers are managed to allow applications to do things like: ctx.set_header('Content-Type', 'image/gif') if ctx.get_header('Pragma') == 'no-cache': ctx.del_header('Pragma') The defaults are established in HeadersMixin.__init__() so the application is free to alter them at any point during processing. 2002-11-21 13:49 andrewm * albatross/sessionfile.py: - SessionFileContextMixin._get_sesid_from_cookie was not coping with cookies other than the one we wanted being in the Cookie header sent by the browser (it was catching the wrong exception). Found by Greg Bond. 2002-11-19 10:18 andrewm * albatross/tags.py, test/templates/form.py: - was not generating a value attribute when the value evaluated to 0 (zero) - fixed and added test. Bug discovered by Michael Neel (thanks). 2002-11-10 12:24 djc * albatross/randompage.py: Fixed import error problem reported by Brian Brown. 2002-10-28 22:51 djc * dist/release-checklist: Documented in the OcWiki. 2002-10-28 12:41 djc * dist/dist-albatross.sh: Fix up script which moves files to web server. 2002-10-28 12:16 djc * albatross/__init__.py: Fixed up release numbers. 2002-10-28 12:15 djc * setup.py, dist/dist-albatross.sh: Fix release numbers. 2002-10-28 11:55 djc * ChangeLog, dist/release-1.01, doc/albatross.tex, doc/installation.tex, doc/mixins.tex, doc/tags.tex: Release 1.01. 2002-10-28 10:43 djc * dist/: dist-albatross.sh, index.ahtml, make_webpage.py: Release process does not alter web page any more. 2002-10-28 10:40 djc * dist/release-1.01: All changes for 1.0.1 2002-10-26 12:36 djc * albatross/app.py: Work around problem where reloading a module causes the pickler to refuse to dump existing instances from that module. 2002-10-24 16:52 djc * albatross/context.py: Be a bit more careful with the pickle exceptions and log them when they occur. 2002-10-04 16:34 andrewm * test/templates/: basic.py, basic/null-attr.html: - Added test for the bug Detlef Lannert picked up in null attribute handling. 2002-09-25 15:18 andrewm * albatross/template.py: - Applied bugfix from Detlef Lannert - null attributes would raise a TypeError (for example: ). Once fixed, this exposed another bug where an attribute with a null value would lose it's value altogether (XHTML requires attributes to have a value, null or otherwise). 2002-09-10 20:17 andrewm * albatross/context.py, test/namespace/recorder.py: - Prevent variables starting with an underscore from being merged into the context local namespace from request objects. This prevents attackers spoofing variables such as __page__, and any user variables such as authentication data. 2002-09-05 00:07 andrewm * albatross/tags.py: - bugfix - TextArea wasn't accepting "nameexpr". 2002-09-04 15:04 andrewm * albatross/tags.py: Radio input was converting "value_attr" to str(), but not "value", which was causing the "checked" test to fail if value was anything but a string. Also changed checkbox input to convert both to strings before doing the comparison. 2002-09-04 13:33 andrewm * albatross/app.py: Exception text wasn't being escaped. 2002-08-30 00:46 djc * setup.py, doc/albatross.tex, doc/installation.tex: Fixed some version numbers and improved checking in setup.py sdist. 2002-08-23 23:17 andrewm * albatross/tags.py: - bugfix - mandatory attribute test didn't include nameexpr. 2002-08-23 12:38 andrewm * TODO: Added "build debian package". 2002-08-21 14:16 andrewm * dist/release-checklist: - first pass at a release checklist. 2002-08-20 22:54 djc * dist/release-1.0: Speeling error. 2002-08-20 22:37 djc * dist/release-1.0: Get recipient list correct. 2002-08-20 22:30 djc * dist/release-1.0: Added information about mailing list. 2002-08-20 22:28 andrewm * dist/release-1.0: Slight formatting change for release notice. 2002-08-20 22:21 djc * dist/release-1.0: Initial release notice. 2002-08-20 21:40 djc * dist/dist-albatross.sh: Gets files to staging location in /var/www/projects/albatross/.new 2002-08-20 20:51 djc * dist/dist-albatross.sh: First attempt at new distribution process. 2002-08-17 23:49 djc * doc/appuser.tex, samples/form4/form.html, samples/form4/form.py, samples/form4/install.py: Added new version of the form sample in the application guide. 2002-08-17 21:45 djc * doc/: Makefile, packaged.tex, randmodsessfileapp.dia: Added documentation for RandomModularSessionFileApp. Fixed documentation for create_context() for all *SessionFileApp classes, returns SessionFileAppContext not SessionAppContext. 2002-08-17 21:23 djc * doc/: albatross.tex, mixins.tex, sessionappcontext.dia, sessionfileappcontext.dia, simpleappcontext.dia, simplecontext.dia, tags.tex: Removed get_field_names() from all RecorderMixin classes. Added merge_request() to all RecorderMixin classes. Documented usage of list attribute in tags and NamespaceMixin. 2002-08-17 21:21 djc * albatross/tags.py: Added 'list' to write_attribs_except() in . 2002-08-16 10:48 andrewm * dist/release-1.0: Changed "MULTIPLE" to "LIST" and reformat several paragraphs to be less than 75 characters (to allow e-mail reply quoting). 2002-08-13 12:30 andrewm * ChangeLog, setup.py, albatross/tags.py, doc/doctest/tags-input-checkbox, doc/doctest/tags-input-file, doc/doctest/tags-input-password, doc/doctest/tags-input-radio1, doc/doctest/tags-input-radio2, test/templates/form.py: Extensive work on the tag class to make it's behaviour more consistent - a common generic_to_html() method is shared by most input types, and "value=" is now honoured for these types (all except checkbox, radio and image). These changes altered the order in which attributes appear in the generated html, necessitating minor alterations to the tests. 2002-08-12 16:43 andrewm * albatross/tags.py: * Silently enable "list" mode when a tag. 2001-12-24 15:03 djc * doc/tags.tex: Added documentation for valueexpr attribute in radio/checkbox. 2001-12-24 15:02 djc * albatross/tags.py: Fixed bug in checkbox/radio where the value attribute was being ignored. 2001-12-24 13:09 djc * doc/installation.tex: Bring the prerequisites list up to date. Update tar file version. 2001-12-24 12:25 djc * ChangeLog: Constructed a ChangeLog from recent history via cvs2cl.pl --fsf --accum 2001-12-24 02:09 djc * doc/albatross.tex: Bump release to 0.03. 2001-12-24 02:07 djc * setup.py, albatross/__init__.py: Bump release to 0.03. 2001-12-24 01:50 djc * TODO, doc/albatross.tex, doc/tags.tex: Trying to get documentation up to date. 2001-12-24 01:50 djc * albatross/tags.py: Added nameexpr attribute to . Added alias and nameexpr attributes to . 2001-12-23 18:15 djc * doc/tags.tex: Documentation fixes. 2001-12-23 17:53 djc * doc/Makefile, doc/albatross.tex, doc/appcontext.dia, doc/application.dia, doc/mixins.tex, doc/modularapp.dia, doc/modularsessapp.dia, doc/packaged.tex, doc/sessionappcontext.dia, doc/simpleapp.dia, doc/simpleappcontext.dia, doc/simplecgiapp.dia, doc/simplecontext.dia, doc/simplesessapp.dia, doc/tags.tex, doc/tempuser.tex, samples/templates/content1.py, samples/templates/content2.py: Bring documentation slightly up to date. 2001-12-22 17:47 djc * albatross/: apacheapp.py, cgiapp.py: Rename __done_headers to __sent_headers. 2001-12-20 23:14 djc * samples/tree/tree2.html: Remove some unnecessary HTML. 2001-12-20 23:13 djc * albatross/tags.py: Implemented TreeIterator.selected_nodes() / open_nodes() 2001-12-20 23:12 djc * albatross/: context.py, session.py: Make sure remove_session() works for all types of session. 2001-12-20 14:50 andrewm * albatross/tags.py: - fixed bug in the input (mods to add alias support had broken the recording of names. - added close_all() and deselect_all() methods to the TreeIterator. 2001-12-19 22:44 djc * albatross/tags.py: Added alias attribute to 2001-12-19 22:15 djc * albatross/tags.py: Added 2001-12-19 21:54 djc * TODO: Fixed cross-site scripting hole. 2001-12-19 21:54 djc * albatross/tags.py: Removed cross-site scripting hole in TextArea and Value. 2001-12-19 00:11 djc * TODO: Add/remove stuff. 2001-12-18 23:08 djc * samples/paginate/: paginate.html, paginate.py: Added simple pagination sample. 2001-12-18 22:37 djc * albatross/app.py: Use generic name 'func' in page_enter()/page_leave()/process_request()/ display_response(). 2001-12-18 22:32 djc * samples/tree/: tree1.py, tree2.py: Convert to new parent/leaf node scheme. 2001-12-18 22:32 djc * albatross/tags.py: Leaf nodes must not define children attribute in 2001-12-18 22:31 djc * albatross/app.py: Extra checks for detecting change of page before calling page_leave(). Remove args from page_leave() call. 2001-12-18 00:39 djc * albatross/tags.py: Added call to ctx.input_add() in thanks to Andrew. 2001-12-18 00:38 djc * albatross/app.py: Added page_leave() method to PageModuleMixin and PageObjectMixin. 2001-12-16 22:34 djc * TODO: Removed a task or two. 2001-12-16 22:28 djc * doc/packaged.tex: Put label in overview. 2001-12-16 22:17 djc * setup.py, albatross/__init__.py, doc/albatross.tex: Bump release to 0.02. 2001-12-16 22:16 djc * samples/tree/: tree1.html, tree1.py, tree2.html, tree2.py: Added some tree samples. 2001-12-16 22:14 djc * doc/: AlbatrossObjects.dia, Makefile, albatross.tex, packaged.tex: Added Andrew's excellent diagram. 2001-12-16 21:36 djc * albatross/tags.py: Forgot to flag node as loaded in node_is_open(). 2001-12-16 21:13 djc * albatross/tags.py: Fix up ListIterator backdoor. 2001-12-16 21:06 djc * albatross/tags.py: Changed ListIterator pagination to use iterator backdoor. Implemented tree browse backdoor. 2001-12-16 21:04 djc * albatross/context.py: Added iterator backdoor to NamespaceMixin.set_value()/_get_value() Minor variable rename in SessionBase.encode_session. 2001-12-14 00:36 djc * albatross/tags.py: Initial support for lazy loading tree. 2001-12-12 23:13 djc * albatross/tags.py: Implemented ALIAS attribute for tag. 2001-12-12 23:12 djc * albatross/context.py: Added NamespaceMixin.make_alias() Reworked NamespaceMixin.set_value() to fix a.b bug. 2001-12-11 23:54 djc * doc/albatross.tex, albatross/__init__.py, setup.py: Bump release to 0.01. 2001-12-11 23:46 djc * doc/albatross.tex: Update date. 2001-12-11 23:45 djc * albatross/tags.py: Implement textarea. 2001-12-10 23:00 djc * albatross/tags.py: Add valueexpr attribute to checkbox and radiobox. Rename Input.expr_or_value() to get_name_and_value(). 2001-12-10 22:59 djc * albatross/session.py: Initialise SessionServerContextMixin.__sesid to None 2001-11-29 23:15 djc * doc/tempuser.tex: Pre-order traversal not in-order - failed CS101. 2001-11-29 22:30 djc * doc/: appuser.tex, tempuser.tex: Fixed typos spotted by Andrew. 2001-11-29 22:28 djc * samples/popview/popview1.py: Fixed missing import poplib - thanks Andrew. 2001-11-29 22:27 djc * src/session-server.py: Fixed typo in getopt parsing. 2001-11-29 22:27 djc * albatross/tags.py: Added NAMEEXPR attribute to . 2001-11-29 22:25 djc * albatross/session.py: Added SessionServerContextMixin.sesid() Delete session if error during decode. Added SessionServerContextMixin.remove_session(). 2001-11-29 22:24 djc * albatross/context.py: Fixed reversed globals/locals in NamespaceMixin.eval_expr() Implemented smarter parser in NamespaceMixin.set_value() - now handles a[12].b or a.b[12] - still needs some testing... Added NamespaceMixin._get_value() which uses eval(). Reimplemented get_value()/has_value() using _get_value(). Added remove_session() to StubSessionMixin and HiddenFieldSessionMixin. 2001-11-29 22:20 djc * albatross/app.py: Remove session when an exception is detected during processing. Added Application.remove_session()/Context.remove_session(). Call page_enter() on first page application sees. 2001-11-29 22:18 djc * albatross/: apacheapp.py, cgiapp.py: Only write headers once. Automatically write headers once output is sent. 2001-11-25 21:32 djc * test/basic.py: Fixed unittest. 2001-11-24 20:17 djc * albatross/random.py: Page modules and templates in same directory tree now. 2001-11-20 23:23 djc * TODO, albatross/random.py: Added RandomModularSessionApp. 2001-11-20 23:22 djc * doc/: Makefile, albatross.tex, appuser.tex, dataflow.dia, packaged.tex: Added dataflow diagram. Started documenting RandomModularApp and RandomModularSessionApp. 2001-11-20 22:26 djc * TODO: Updates. 2001-11-20 22:24 djc * albatross/session.py: Ignore Cookie import error for now on 1.5.2 2001-11-20 22:24 djc * albatross/context.py: Added TemplateLoadError exception. 2001-11-20 22:23 djc * albatross/: apacheapp.py, cgiapp.py: Added get_uri() method to Request. 2001-11-20 22:23 djc * albatross/: __init__.py, random.py: Added random.py to process random page modules. albatross-1.36/.cvsignore0000644000175000017500000000003710313372276015315 0ustar andrewmandrewm*-stamp* .*.swp build MANIFEST albatross-1.36/TODO0000644000175000017500000000252410576144061014010 0ustar andrewmandrewmWork for release 1.10pre1: * Implement apacheapp.FileField class. * Merge Unicode support. Py3k is the deadline as str will become unicode. Doco suggestions: * From Michael C. Neel: For the documentation, I'd love to see the tag pages show both the using and the parsing. I've read the doc completely once, so now I just use it as reference. The two things I look up most is how do I access data when submitted and what member functions/variables does object x have. The first sometimes takes some searching; the latter usually takes some digging though base classes - a link to the base class on those pages would help a lot. General stuff: * tacks the whitespace onto the default case, rather than all cases. * Missing "view message" image in samples/popview1 - Netscape 6 is taking users back to the list page (no message details). From Sheila King. Also, samples installer should adjust path to python interpreter, and flag the fact the pop host name might need changing (or just query the user for it?). * Hook the tag into the form recorder. * and for non 'for i in range()' loops. * should register all it's options with RecorderMixin. * Should have a "constructor" like the optionexpr tag of ? Tests: albatross-1.36/debian/0000755000175000017500000000000010577422307014542 5ustar andrewmandrewmalbatross-1.36/debian/.cvsignore0000644000175000017500000000006410313372276016537 0ustar andrewmandrewmcontrol files python*-albatross python*-albatross.* albatross-1.36/setup.py0000644000175000017500000000442210576163416015036 0ustar andrewmandrewm#!/usr/bin/env python # To use: # python setup.py install # import distutils, os, sys, re from distutils.core import setup, Extension from distutils.command.sdist import sdist class PreReleaseCheck: def __init__(self, distribution): self.distribution = distribution self.check_rev('doc/albatross.tex', r'^\\release.*\{([^}]+)\}') self.check_rev('doc/installation.tex', r'^tar xz.*albatross-(.*)\.tar\.gz') self.check_rev('doc/installation.tex', r'{albatross-(.*)}') self.check_rev('albatross/__init__.py', r'__version__ = \'(.*)\'') def _extract_rev(self, filename, pattern): regexp = re.compile(pattern) match = None revs = [] line_num = 0 f = open(filename) try: for line in f.readlines(): line_num += 1 match = regexp.search(line) if match: revs.append((line_num, match.group(1))) finally: f.close() return revs def check_rev(self, filename, pattern): file_revs = self._extract_rev(filename, pattern) if not file_revs: sys.exit("Could not locate version in %s" % filename) line_num, file_rev = file_revs[0] for num, rev in file_revs[1:]: if rev != file_rev: sys.exit("%s:%d inconsistent version on line %d" % \ (filename, line_num, num)) setup_rev = self.distribution.get_version() if file_rev != setup_rev: sys.exit("%s:%d version %s does not match setup.py version %s" % \ (filename, line_num, file_rev, setup_rev)) class my_sdist(sdist): def run(self): PreReleaseCheck(self.distribution) self.announce("Pre-release checks pass!") sdist.run(self) setup(name = "albatross", version = "1.36", maintainer = "Object Craft", maintainer_email = "albatross@object-craft.com.au", description = "Albatross Web Toolkit", url = "http://www.object-craft.com.au/projects/albatross/", packages = ['albatross'], scripts = ['session-server/al-session-daemon', 'standalone-server/al-httpd'], license = 'BSD - see file LICENCE', cmdclass = {'sdist': my_sdist}, ) albatross-1.36/LICENCE0000644000175000017500000000301207670553707014312 0ustar andrewmandrewmCopyright (C) 2002, 2003, Object Craft P/L, Melbourne, Australia. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Object Craft nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. albatross-1.36/albatross/0000755000175000017500000000000010577422307015312 5ustar andrewmandrewmalbatross-1.36/albatross/fcgispawn.py0000644000175000017500000000734210446670362017654 0ustar andrewmandrewm# # Copyright 2006 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # import os import sys import getopt import socket import time import errno import signal ourname = os.path.basename(sys.argv[0]) workers = {} class Worker: def __init__(self, sock, *cmdline): self.pid = os.fork() self.cmd = cmdline[0] if self.pid: workers[self.pid] = self self.start_time = time.time() else: time.sleep(0.1) os.dup2(sock.fileno(), 0) os.close(1) sock.close() os.execl(self.cmd, *cmdline) def reap(self, status): aborted = False if os.WIFSIGNALED(status): if os.WTERMSIG(status) != signal.SIGTERM: print '%s: %s: pid %s killed by signal %s' %\ (ourname, self.cmd, self.pid, os.WTERMSIG(status)) aborted = True elif os.WIFEXITED(status): if os.WEXITSTATUS(status): print '%s: %s: pid %s exited with status %s' %\ (ourname, self.cmd, self.pid, os.WEXITSTATUS(status)) aborted = True if aborted and (time.time() - self.start_time) < 2: print '%s restarting too rapidly' % (self.cmd) else: del workers[self.pid] def reap_children(): try: pid, status = os.wait() except OSError, (eno, estr): if eno == errno.ECHILD: sys.exit('No more children, exiting') if eno != errno.EINTR: raise else: try: workers[pid].reap(status) except KeyError: pass run = True def stop(*args): print "HERE" run = False def spawn(sock, nprocs, *cmd): signal.signal(signal.SIGINT, stop) signal.signal(signal.SIGTERM, stop) while run: while len(workers) < nprocs: Worker(sock, *cmd) reap_children() def get_unix_socket(arg): s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.bind(arg) s.listen(8) return s def get_inet_socket(arg): try: addr, port = arg.split(':', 1) except ValueError: addr, port = '', arg try: port = int(port) except ValueError: usage() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((addr, port)) s.listen(8) return s def usage(): sys.exit('''\ Usage: %s [opts] -u, --unix= Unix domain socket address (path) -i, --inet= Internet domain socket (port, or addr:port) -m, --min= Prespawn minimum -M, --max= Maximum processes -I, --idle= Max idle time''' % ourname) def fatal(msg): sys.exit('%s: %s' % (ourname, msg)) if __name__ == '__main__': sock = None nprocs = 2 optlist, args = getopt.getopt(sys.argv[1:], 'd:i:n:u:', ['dir=', 'unix=', 'inet=', 'min=', 'max=', 'idle=']) for opt, arg in optlist: if opt in ('-d', '--dir'): os.chdir(arg) if opt in ('-u', '--unix'): if sock: fatal('-u and -i are mutually exclusive') sock = get_unix_socket(arg) elif opt in ('-i', '--inet'): if sock: fatal('-u and -i are mutually exclusive') sock = get_inet_socket(arg) elif opt in ('-n', '--nprocs'): try: nprocs = int(arg) except ValueError, e: fatal('-n: %s' % e) elif opt == '?': usage() if not sock: fatal('no listen socket specified?') if not args: fatal('no command specified') spawn(sock, nprocs, *args) albatross-1.36/albatross/request.py0000644000175000017500000000363010446670652017361 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # Request base classes from albatross.common import * class FileField: def __init__(self, field): self.filename = field.filename self.file = field.file self.type = field.type def __repr__(self): return 'FileField(%s, %s, %s)' % (self.filename, self.file, self.type) class RequestFields: def __init__(self, fields): # Fields is a cgi.FieldStorage() work-alike self.__fields = fields def has_field(self, name): return name in self.__fields def field_value(self, name): field = self.__fields[name] if isinstance(field, list): if isinstance(field[0], str): # For checkboxes, this is a vec of strings in mod_python 2 return field # But a vec of utils.FieldStorage objects in mod_python 3 return [f.value for f in field] elif isinstance(field, str): return field # Weird - blame mod_apache? else: return field.value def field_file(self, name): field = self.__fields[name] if isinstance(field, list): return [FileField(f) for f in field] if field.type.startswith('multipart'): return [FileField(f) for f in field.value] else: return [FileField(field)] def field_names(self): return self.__fields.keys() class RequestStatus: def __init__(self): self.__status = HTTP_OK def set_status(self, status): self.__status = status def status(self): return self.__status class RequestBase(RequestFields, RequestStatus): def __init__(self, fields): RequestFields.__init__(self, fields) RequestStatus.__init__(self) def return_code(self): pass albatross-1.36/albatross/randompage.py0000644000175000017500000000763410255723000017776 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # import urlparse from albatross.template import Template from albatross.context import CachingTemplateLoaderMixin, PickleSignMixin,\ _caller_globals from albatross.app import Application, PageModuleMixin, SimpleAppContext,\ SessionAppContext from albatross.session import SessionServerAppMixin from albatross.common import ApplicationError class RandomPageModuleMixin(PageModuleMixin): def load_page(self, ctx): # Get page name from request URI uri = ctx.request.get_uri() page = self.get_page_from_uri(ctx, uri) if not page: ctx.redirect(self.start_page()) # Try to load the page module self.__page_found = 1 try: self.load_page_module(ctx, page) except ApplicationError, e: if not str(e).startswith('No module named %s' % page.split('/')[-1]): raise ctx.locals.page_name = page self.__page_found = 0 if self.__page_found: self.page_enter(ctx) def get_page_from_uri(self, ctx, uri): try: base_path = urlparse.urlparse(self.base_url())[2] uri_path = urlparse.urlparse(uri)[2] # Fixup base_path to end with a '/' so that it can be used as # a root path. if not base_path.endswith('/'): base_path = base_path + '/' return uri_path.split(base_path, 1)[1] except IndexError: return None def load_badurl_template(self, ctx): return Template(ctx, '', ''' 404 Not Found

Oops!

URL was not found on this server.

''') def page_enter(self, ctx): if self.__page_found: return PageModuleMixin.page_enter(self, ctx, ()) return None def process_request(self, ctx): if self.__page_found: return PageModuleMixin.process_request(self, ctx) return None def display_response(self, ctx): if self.__page_found: PageModuleMixin.display_response(self, ctx) else: # Display error page - no page module so use the mainline # globals templ = self.load_badurl_template(ctx) ctx.set_globals(_caller_globals('run')) templ.to_html(ctx) class RandomModularApp(PickleSignMixin, Application, CachingTemplateLoaderMixin, RandomPageModuleMixin): def __init__(self, base_url, page_path, start_page, secret): PickleSignMixin.__init__(self, secret) Application.__init__(self, base_url) CachingTemplateLoaderMixin.__init__(self, page_path) RandomPageModuleMixin.__init__(self, page_path, start_page) def create_context(self): return SimpleAppContext(self) # Same as RandomModularApp with server-side session support class RandomModularSessionApp(PickleSignMixin, Application, CachingTemplateLoaderMixin, RandomPageModuleMixin, SessionServerAppMixin): def __init__(self, base_url, page_path, start_page, secret, session_appid, session_server = 'localhost', server_port = 34343, session_age = 1800): PickleSignMixin.__init__(self, secret) Application.__init__(self, base_url) CachingTemplateLoaderMixin.__init__(self, page_path) RandomPageModuleMixin.__init__(self, page_path, start_page) SessionServerAppMixin.__init__(self, session_appid, session_server, server_port, session_age) def create_context(self): return SessionAppContext(self) albatross-1.36/albatross/__init__.py0000644000175000017500000000114510576163416017426 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # from albatross.common import * from albatross import tags from albatross.template import * from albatross.context import * from albatross.session import * from albatross.app import * from albatross.randompage import * from albatross.sessionfile import * from albatross.branchingsession import * ListIterator = tags.ListIterator TreeIterator = tags.TreeIterator LazyTreeIterator = tags.LazyTreeIterator EllipsisTreeIterator = tags.EllipsisTreeIterator __version__ = '1.36' albatross-1.36/albatross/pidfile.py0000644000175000017500000000457410300030143017263 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # import sys import os from errno import * import signal import time class PidFileError(Exception): pass class PidFile: def __init__(self, pidfilename): self._pid_filename = pidfilename def getpid(self): try: pid = open(self._pid_filename,"r").readline() except IOError, (eno, estr): if eno == ENOENT: return 0 raise PidFileError("%s: %s" % (self._pid_filename, estr)) try: pid = int(pid) except ValueError: raise PidFileError("%s: unknown format" % \ self._pid_filename) if pid <= 1: raise PidFileError("%s: unknown format" % \ self._pid_filename) try: os.kill(pid,0) except OSError, (eno, estr): if eno == ESRCH: # Stale lock file, remove and sleep to avoid creation race sys.stderr.write("Removing state lock for pid %d\n" % pid) os.unlink(self._pid_filename) time.sleep(2) return 0 raise PidFileError("pid %d: %s" % (pid, estr)) return pid def is_running(self): return (self.getpid() != 0) def kill(self, sig = signal.SIGTERM): pid = self.getpid() if pid: os.kill(pid, sig) def start(self, pid = None): if pid is None: pid = os.getpid() daemon_pid = self.getpid() if daemon_pid: raise PidFileError("daemon already running, pid %d" % daemon_pid) # We use fd ops because we want O_EXCL semantics on the created file try: pid_fd = os.open(self._pid_filename, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0644) except OSError, (eno, estr): raise PidFileError("can't create pid file %s: %s" %\ (self._pid_filename, estr)) os.write(pid_fd, "%d\n" % pid) os.close(pid_fd) def stop(self): pid = self.getpid() if pid != os.getpid(): raise PidFileError("Pid file doesn't belong to us - pid %d" % pid) try: os.unlink(self._pid_filename) except: pass albatross-1.36/albatross/.cvsignore0000644000175000017500000000001510133651366017303 0ustar andrewmandrewm.*.swp *.pyc albatross-1.36/albatross/session.py0000644000175000017500000001502110576134772017353 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # import socket import errno import base64 import os.path try: import Cookie except ImportError: pass try: import zlib have_zlib = True except ImportError: have_zlib = False from albatross.context import SessionBase from albatross.common import * class ServerDisconnect(ServerError): pass class SessionCookieMixin: def _get_sesid_from_cookie(self): hdr = self.request.get_header('Cookie') if hdr: c = Cookie.SimpleCookie(hdr) try: return c[self.app.ses_appid()].value except KeyError: pass return None def _set_sesid_cookie(self, sesid): c = Cookie.SimpleCookie() appid = self.app.ses_appid() if sesid is None: c[appid] = '' else: c[appid] = sesid path = self.absolute_base_url() c[appid]['path'] = path if self.parsed_request_uri()[0] == 'https': c[appid]['secure'] = True prefix_len = len('Set-Cookie: ') self.set_header('Set-Cookie', str(c)[prefix_len:]) # Simple session server class SessionServerContextMixin(SessionCookieMixin, SessionBase): def __init__(self): SessionBase.__init__(self) self.__sesid = None def sesid(self): return self.__sesid def load_session(self): sesid = self._get_sesid_from_cookie() text = None if sesid: text = self.app.get_session(sesid) if not text: self._set_sesid_cookie(None) raise SessionExpired('Session expired or browser does not support cookies') else: sesid = self.app.new_session() self.__sesid = sesid if text: text = base64.decodestring(text) try: if have_zlib: text = zlib.decompress(text) self.decode_session(text) except: self.app.del_session(sesid) raise self._set_sesid_cookie(self.__sesid) def new_session(self): self.__sesid = self.app.new_session() self._set_sesid_cookie(self.__sesid) def save_session(self): if self.should_save_session() and self.__sesid is not None: text = self.encode_session() if have_zlib: text = zlib.compress(text) text = base64.encodestring(text) self.app.put_session(self.__sesid, text) def remove_session(self): SessionBase.remove_session(self) if self.__sesid is not None: self.app.del_session(self.__sesid) self.__sesid = None self._set_sesid_cookie(self.__sesid) class SessionServerAppMixin: def __init__(self, appid, server = 'localhost', port = 34343, age = 1800): self.__appid = appid self.__server = server self.__port = port self.__sock = None self.__buf = '' self.__age = age try: self._server_connect() except ServerError: pass def ses_appid(self): return self.__appid def ses_age(self): return self.__age def _server_connect(self): if self.__sock: return while 1: try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((self.__server, self.__port)) self.__sock = sock return except socket.error, (eno, estr): if eno != errno.EINTR: raise ServerError('could not connect to session server: %s' % estr) def _server_close(self): self.__sock = None self.__buf = '' def _server_read(self, eol='\r\n'): while 1: n = self.__buf.find(eol) if n >= 0: line = self.__buf[:n] self.__buf = self.__buf[n+len(eol):] return line try: buf = self.__sock.recv(16384) except socket.error, (eno, estr): if eno != errno.EINTR: self._server_close() raise ServerError('lost session server: %s' % estr) else: if not buf: self._server_close() raise ServerDisconnect self.__buf += buf def _server_write(self, text): while text: try: count = self.__sock.send(text) text = text[count:] except socket.error, (eno, estr): if eno in (errno.ECONNRESET, errno.EPIPE): self._server_close() raise ServerDisconnect if eno != errno.EINTR: self._server_close() raise ServerError('lost session server: %s' % estr) def _server_read_response(self): resp = self._server_read() if not resp.startswith('OK'): raise ServerError('Session server returned: %s' % resp) return resp[2:].strip() def get_session(self, sesid): while 1: try: self._server_connect() self._server_write('get %s %s\r\n' % (self.__appid, sesid)) resp = self._server_read() if not resp.startswith('OK'): return '' return self._server_read('\r\n\r\n') except ServerDisconnect: pass def new_session(self): while 1: try: self._server_connect() self._server_write('new %s %s\r\n' % (self.__appid, self.__age)) return self._server_read_response() except ServerDisconnect: pass def put_session(self, sesid, text): while 1: try: self._server_connect() self._server_write('put %s %s\r\n' % (self.__appid, sesid)) self._server_read_response() self._server_write(text.replace('\n', '\r\n') + '\r\n') return self._server_read_response() except ServerDisconnect: pass def del_session(self, sesid): while 1: try: self._server_connect() self._server_write('del %s %s\r\n' % (self.__appid, sesid)) return self._server_read_response() except ServerDisconnect: pass albatross-1.36/albatross/fcgiappold.py0000644000175000017500000000123310576144061017770 0ustar andrewmandrewm# # Copyright 2003 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # AUTHOR(S) # Matt Goodall import fcgi from albatross import cgiapp class Request(cgiapp.Request): def __init__(self, fields = None): self.__fcgi = fcgi.FCGI() if fields is None: fields = self.__fcgi.getFieldStorage() cgiapp.Request.__init__(self, fields) def get_param(self, key, default=None): return self.__fcgi.env.get(key, default) def return_code(self): self.__fcgi.Finish() def running(): return fcgi.isFCGI() albatross-1.36/albatross/template.py0000644000175000017500000004167510575166531017516 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # Templating core functionality # import re from albatross.common import * # Templates can test for a given level of template support. Bump # template_version when new tags or parsing features are added. # # History: # 1 .. Albatross 1.20 and before # 2 .. 1.30pre - added AnyTag features template_version = 2 # Alternative scheme - list specific features - templates can test that # a particular feature is available. template_features = () # The template file parser treats HTML files as plain text with # embedded template tags. In parsing the file a tag tree is # constructed. Tags are either empty or enclosing. # Plain text from the template file is managed by a Text object. This # allows the template interpreter to treat text no differently from a # tag. class Text: name = None def __init__(self, text): self.text = text def __repr__(self): return 'Text(%s)' % self.text def to_html(self, ctx): ctx.write_content(self.text) # All tags in the toolkit and application are implemented by # subclassing either EmptyTag or EnclosingTag. Each of these classes # inherit from Tag. class Tag: '''Functionality common to all HTML template tags. ''' def __init__(self, ctx, filename, line_num, attribs): '''Store where the tag was defined for execution trace generation ''' self.filename = filename self.line_num = line_num self.attribs = attribs self.attrib_order = None def __repr__(self): parts = [] parts.append('<') parts.append(self.name) for name in self.attrib_order: value = self.attribs[name] if value is not None: parts.append(' %s="%s"' % (name, value)) else: parts.append(' %s' % name) parts.append('>') return ''.join(parts) def raise_error(self, msg): raise ApplicationError('%s:%s: %s' % \ (self.filename, self.line_num, msg)) def has_attrib(self, name): '''Was the named attribute named with the tag ''' return name in self.attribs def assert_has_attrib(self, name): if name not in self.attribs: self.raise_error('missing "%s" attribute' % name) def assert_any_attrib(self, *attrs): """ Check that the tag has at least one of the attributes """ for attr in attrs: if self.has_attrib(attr): return if len(attrs) == 1: self.raise_error('missing %s attribute' % repr(attrs[0])) else: attr_list = ', '.join([repr(attr) for attr in attrs[:-1]]) self.raise_error('missing %s or %s attribute' %\ (attr_list, repr(attrs[-1]))) def assert_only_attrib(self, *attrs): """ Check that the tag only has the listed attributes """ for name in self.attribs.keys(): if name not in attrs: self.raise_error('"%s" attribute not support by this tag' % name) def get_attrib(self, name, default = None): '''Return the value of the named attribute ''' return self.attribs.get(name, default) def get_expr_attrib(self, ctx, name, default=None): """ If /name/expr version of attribute exists, fetch it, evaluate and return the result, otherwise fetch the value of /name/. """ if name + 'expr' in self.attribs: return self.eval_attrib(ctx, name + 'expr') else: return self.attribs.get(name, default) def get_bool_attrib(self, ctx, name, default=False): """ If /name/bool version of attribute exists, fetch it, evaluate it in a boolean context and return the result, otherwise return whether /name/ exists. """ if name + 'bool' in self.attribs: return bool(self.eval_attrib(ctx, name + 'bool')) else: return name in self.attribs def set_attrib_order(self, order): self.attrib_order = order def set_attrib(self, name, value): '''Set the value of the named attribute ''' self.attribs[name] = value if name not in self.attrib_order: self.attrib_order.append(name) def attrib_items(self): return self.attribs.items() def write_attribs_except(self, ctx, *exclude): for name in self.attrib_order: if name in exclude: continue if name.endswith('bool'): if self.eval_attrib(ctx, name): ctx.write_content(' %s' % name[:-len('bool')]) else: if name[:-4] and name.endswith('expr'): value = self.eval_attrib(ctx, name) name = name[:-len('expr')] else: value = self.attribs[name] if value is not None: ctx.write_content(' %s="%s"' % (name, value)) else: ctx.write_content(' %s' % name) def eval_attrib(self, ctx, attr, kind = 'eval'): code_attr = 'co_' + attr try: code_obj = getattr(self, code_attr) except AttributeError: code_obj = compile(self.get_attrib(attr), '', kind) setattr(self, code_attr, code_obj) return ctx.eval_expr(code_obj) # Tags such as , , do not enclose # content. These tags just use the information in their attributes to # generate output. # # When you subclass the EmptyTag class you must supply a name class # attribute and you should almost certainly override the to_html() # method. # # class HR(template.EmptyTag): # name = 'al-hr' # # def to_html(self, ctx): # ctx.write_content('


') # # The name attribute allows the code which interprets the parsed # template to identify each tag. It is also used by the parser to # locate which class to instantiate when it finds a tag of the same # name in the template file. class EmptyTag(Tag): '''All tags which do not enclose content inherit from this class ''' def has_content(self): return 0 def to_html(self, ctx): pass # Content objects are the basic building block of the parsed HTML # template. Converting a Content object to HTML simply converts each # item in the object to HTML. All content enclosing tags use Content # objects to store their content and then use the special tag logic to # control when and how often the content should be sent as output. class Content: '''Manage a list of content items. ''' def __init__(self): self.items = [] def __repr__(self): return 'Content(%s)' % self.items def __len__(self): return len(self.items) def append(self, item): self.items.append(item) def to_html(self, ctx): for item in self.items: item.to_html(ctx) # Tags such as , , , do # enclose content. # # When you subclass the EnclosingTag class you must supply a name # class attribute and you should almost certainly override the # to_html() method. For example, the following is very simple if: # # class If(template.ContentTag): # name = 'al-if' # # def __init__(self, ctx, filename, line_num, attribs): # template.ContentTag.__init__(self, ctx, filename, line_num, attribs) # if not self.has_attrib('expr'): # self.raise_error('missing "expr" attribute') # self.expr = compile(self.get_attrib('expr'), '', 'eval') # # def to_html(self, ctx): # if ctx.eval_expr(self.expr): # template.ContentTag.to_html(self, ctx) class EnclosingTag(Tag): '''All tags which enclose content inherit from this class ''' def __init__(self, ctx, filename, line_num, attribs): Tag.__init__(self, ctx, filename, line_num, attribs) self.content = Content() def __repr__(self): return Tag.__repr__(self) + repr(self.content) + '' % self.name def has_content(self): return 1 def append(self, item): self.content.append(item) def to_html(self, ctx): self.content.to_html(ctx) class AnyTag(EnclosingTag): empty_tags = { 'area': None, 'base': None, 'basefont': None, 'br': None, 'col': None, 'frame': None, 'hr': None, 'img': None, 'input': None, 'isindex': None, 'link': None, 'meta': None, 'param': None, } def __init__(self, ctx, filename, line_num, name, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) self.name = name def has_content(self): return self.name[3:] not in self.empty_tags def to_html(self, ctx): name = self.name[3:] ctx.write_content('<%s' % name) self.write_attribs_except(ctx) ctx.write_content('>') if self.has_content(): EnclosingTag.to_html(self, ctx) ctx.write_content('' % name) # A Template object contains the parsed HTML content of a single # template file. Loading a template parses the HTML and compiles the # expressions within the file. The execution phase happens later once # the application has established an execution context. # # A tag "manager" is passed in which resolves the tag name to a Python # class which implements that tag. This allows the application to # extend the standard tag set without changing the toolkit. # Parsing works by splitting the file into a stream of text, tag, # text, tag, ... The current parent object for content is maintained # on a stack, each time a new content enclosing tag is encountered, # the current parent is saved and all new content is directed to the # new parent. When the close tag is encountered the old parent is # popped off the stack. # Locate tags; remember start, attribs, end _sre_dblquote_nogrp = r'"(?:[^"\\]*(?:\\.[^"\\]*)*)"' # eg, "hello \"bill\"" _sre_sglquote_nogrp = r"'(?:[^'\\]*(?:\\.[^'\\]*)*)'" # eg, 'sam\'s ball' _sre_attr = r'(?:' + \ r'\s+\w+=' + _sre_dblquote_nogrp + '|' + \ r'\s+\w+=' + _sre_sglquote_nogrp + '|' + \ r'\s+\w+' + ')' # eg, attr="value" _sre_attrs = r'(' + _sre_attr + r'*)' # eg, a1="v1" a2="v2" _sre_tagname = r'(/?alx?-\w+)' # eg, al-value _re_tagname = re.compile('^'+_sre_tagname+'$', re.I) _sre_tag = r'<'+_sre_tagname+_sre_attrs+r'(\s*/?>)' # a full albatross tag _re_tag = re.compile(_sre_tag, re.I|re.M|re.S) _re_leading = re.compile(r'^\s+') # Extract attributes _sre_dblquote_grp = r'"([^"\\]*(?:\\.[^"\\]*)*)"' _sre_sglquote_grp = r"'([^'\\]*(?:\\.[^'\\]*)*)'" _sre_attr_grp = r'(\w+)=(?:'+_sre_dblquote_grp+'|'+_sre_sglquote_grp+')|(\w+)' _re_attrib = re.compile(_sre_attr_grp, re.I|re.M) _re_dequote = re.compile(r'\\(.)') def check_tagname(name): if not _re_tagname.match(name): raise ApplicationError('Invalid tag name %r' % name) class Template: '''Manage a single HTML template file ''' def __init__(self, ctx, filename, text): '''The ctx argument is is used to resolve tag names to tag classes. filename is to identify where the template text came from. The the file identified by filename is never referenced to allow external code to implement caching schemes. It also allows you to do things like this: t = Template(tags, "", """ """) ''' TAG_TEXT = 0 TAG_NAME = 1 TAG_ATTRIBS = 2 TAG_CLOSE = 3 WHITE_ALL = 'all' WHITE_STRIP = 'strip' WHITE_INDENT = 'indent' WHITE_NEWLINE = 'newline' state = TAG_TEXT white_space = WHITE_STRIP self.content = Content() parent_stack = [] parent = self.content line_num = 1 for part in _re_tag.split(text): if state == TAG_TEXT: text = part if text: if white_space == WHITE_STRIP: if text.startswith('\n'): text = _re_leading.sub('', text) elif white_space == WHITE_INDENT: if text.startswith('\n'): text = text[1:] elif white_space == WHITE_NEWLINE: text = '\n' if text: parent.append(Text(text)) white_space = WHITE_STRIP state = TAG_NAME elif state == TAG_NAME: name = part.lower() if name[0] == '/': # close tag - pop parent name = name[1:] if not parent_stack: raise ApplicationError('%s:%s: "%s"; unexpected "%s" end tag' % (filename, line_num, part, name)) if parent.name != name: raise ApplicationError('%s:%s: "%s"; expected "%s" end tag' % (filename, line_num, part, parent.name)) parent = parent_stack[-1] del parent_stack[-1] is_open = 0 else: # open tag - find class tagclass = ctx.get_tagclass(name) if not tagclass: if name.startswith('al-'): tagclass = AnyTag tagname = name else: raise ApplicationError('%s:%s: undefined tag "%s"'%\ (filename, line_num, name)) is_open = 1 state = TAG_ATTRIBS elif state == TAG_ATTRIBS: bits = _re_attrib.split(part) attribs = {} attrib_order = [] offset = 0 while 1: attrbits = bits[offset: offset + 5] if len(attrbits) < 5: break offset = offset + 5 white, name1, value1, value2, name2 = attrbits if name1: name = name1.lower() if value1 is not None: value = _re_dequote.sub(r'\1', value1) else: value = _re_dequote.sub(r'\1', value2) else: name = name2.lower() value = None if name == 'whitespace': if value not in (WHITE_ALL, WHITE_STRIP, WHITE_INDENT, WHITE_NEWLINE, None): raise ApplicationError('%s:%s: illegal whitespace value "%s"' % \ (filename, line_num, value)) white_space = value else: attribs[name] = value attrib_order.append(name) if is_open: if tagclass is AnyTag: tag = tagclass(ctx, filename, line_num, tagname, attribs) else: tag = tagclass(ctx, filename, line_num, attribs) tag.set_attrib_order(attrib_order) parent.append(tag) if tag.has_content(): parent_stack.append(parent) parent = tag state = TAG_CLOSE elif state == TAG_CLOSE: if part.endswith('/>'): if tag.has_content(): parent = parent_stack[-1] del parent_stack[-1] else: pass state = TAG_TEXT line_num += part.count('\n') if state not in (TAG_TEXT, TAG_NAME): raise ApplicationError('%s:%s: incomplete tag definition' % \ (filename, line_num)) if parent_stack: raise ApplicationError('%s:%s: missing close tag for "%s"' % \ (filename, parent.line_num, parent.name)) def __repr__(self): return 'Template(%s)' % self.content def to_html(self, ctx): '''Execute template within an execution context. ''' self.content.to_html(ctx) if __name__ == '__main__': import sys from albatross import context ctx = context.SimpleContext('../test') t = Template(ctx, '', ''' Zero One Two Many ''') t.to_html(ctx) ctx.flush_content() albatross-1.36/albatross/context.py0000644000175000017500000005732010577416325017362 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # Minimum functionality which is required for the template interpreter # to run import os import sys import re import cPickle import __builtin__ try: import zlib have_zlib = 1 except ImportError: have_zlib = 0 import base64 import hmac, sha from albatross.template import Template, check_tagname from albatross.common import * # old cPickle used string exceptions, and therefore couldn't subclass a common # exception. New cPickle has a common cPickle.PickleError, but will also raise # some other random exceptions - bah - these are the ones I've seen: pickle_errors = (cPickle.PicklingError, cPickle.UnpicklingError, TypeError, EOFError) # Templates execute within an execution context. The context is # required to handle the following: # # - HTML output # - Macro and function arguments # - Expression evaluation # - Iterators # - Global resources; templates, lookups, macros # # Applications which are session based will extend the execution # context to prime it with values retreived from the session. # ------------------------------------------------------------------ # Template interpreter resources - for all templates in application # ------------------------------------------------------------------ class ResourceMixin: '''Maintains a global registry of tags, macros, and lookups. ''' def __init__(self): self.__macros = {} self.__lookups = {} self.__tags = {} def get_macro(self, name): return self.__macros.get(name) def register_macro(self, name, macro): existing = self.__macros.get(name) if existing: try: existing_loc = existing.filename, existing.line_num macro_loc = macro.filename, macro.line_num except AttributeError: pass else: if existing_loc != macro_loc: raise ApplicationError('macro %r already defined in %s:%s' % (name, existing_loc[0], existing_loc[1])) self.__macros[name] = macro def get_lookup(self, name): return self.__lookups.get(name) def register_lookup(self, name, lookup): existing = self.__lookups.get(name) if existing: try: existing_loc = existing.filename, existing.line_num lookup_loc = lookup.filename, lookup.line_num except AttributeError: pass else: if existing_loc != lookup_loc: raise ApplicationError('lookup %r already defined in %s:%s'% (name, existing_loc[0], existing_loc[1])) self.__lookups[name] = lookup def discard_file_resources(self, filename): if filename is not None: for name, macro in self.__macros.items(): if getattr(macro, 'filename', None) == filename: del self.__macros[name] for name, lookup in self.__lookups.items(): if getattr(lookup, 'filename', None) == filename: del self.__lookups[name] def get_tagclass(self, name): return self.__tags.get(name) def register_tagclasses(self, *tags): for tag in tags: check_tagname(tag.name) self.__tags[tag.name] = tag # ------------------------------------------------------------------ # Template interpreter "stack" - used to execute a single template # ------------------------------------------------------------------ class ExecuteMixin: '''Manages a template execution context ''' def __init__(self): self.reset_content() def reset_content(self): self.__macro_stack = [{}] self.__active_select = None self.__trap_stack = [] self.__content_parts = [] def get_macro_arg(self, name): try: return self.__macro_stack[-1][name] except KeyError: raise ApplicationError('undefined macro argument "%s"' % name) def push_macro_args(self, args, defaults=None): if defaults is not None: ns = defaults.copy() ns.update(self.__macro_stack[-1]) else: ns = self.__macro_stack[-1].copy() ns.update(args) self.__macro_stack.append(ns) def pop_macro_args(self): return self.__macro_stack.pop(-1) def set_active_select(self, select, value): if self.__active_select is not None: raise ApplicationError('Can not nest ') self.__active_select = select, value def clear_active_select(self): self.__active_select = None def get_active_select(self): if self.__active_select is None: raise ApplicationError(' must be within an ') return self.__active_select def push_content_trap(self): self.__trap_stack.append(self.__content_parts) self.__content_parts = [] def pop_content_trap(self): data = ''.join(self.__content_parts) self.__content_parts = self.__trap_stack[-1] del self.__trap_stack[-1] return data def write_content(self, data): if isinstance(data, unicode): data = data.encode('utf-8') self.__content_parts.append(data) def flush_content(self): if self.__trap_stack: return data = ''.join(self.__content_parts) self.send_content(data) self.__content_parts = [] flush_html = flush_content def send_content(self, data): sys.stdout.write(data) sys.stdout.flush() # ------------------------------------------------------------------ # Template file loaders # ------------------------------------------------------------------ # A simple template file loader which reads and parses the template # file every time it is accessed. class TemplateLoaderMixin: '''Basic template loader ''' def __init__(self, base_dir): self.__base_dir = base_dir self.__loaded_names = {} def load_template(self, name): self.__loaded_names[name] = True path = os.path.join(self.__base_dir, name) try: text = open(path).read() except IOError, e: raise TemplateLoadError("%s: %s" % (path, e.strerror)) self.discard_file_resources(path) return Template(self, path, text) def load_template_once(self, name): if name in self.__loaded_names: return return self.load_template(name) # A caching template file loader which only reloads and parses the # template file if it has been modified. class CachingTemplateLoaderMixin: '''Caching template file loader ''' def __init__(self, base_dir): self.__base_dir = base_dir self.__cache = {} def load_template(self, name): path = os.path.join(self.__base_dir, name) try: mtime = os.path.getmtime(path) except OSError, e: raise TemplateLoadError("%s: %s" % (path, e.strerror)) templ = self.__cache.get(path) if templ: if mtime > templ.__mtime__: templ = None if not templ: try: text = open(path).read() except IOError, e: raise TemplateLoadError(e.strerror) self.discard_file_resources(path) templ = Template(self, path, text) templ.__mtime__ = mtime self.__cache[path] = templ return templ def load_template_once(self, name): path = os.path.join(self.__base_dir, name) old_templ = self.__cache.get(path) if old_templ: old_mtime = old_templ.__mtime__ new_templ = self.load_template(name) if old_templ is None or old_mtime != new_templ.__mtime__: return new_templ return None # ------------------------------------------------------------------ # Form element recorders. # ------------------------------------------------------------------ # Do not record form elements. class StubRecorderMixin: def form_open(self): pass def form_close(self): pass def input_add(self, itype, name, value = None, return_list = 0): pass def merge_request(self): for name in self.request.field_names(): value = self.request.field_value(name) self.set_value(name, value) # Record form element names in a hidden field to allow request # processing to set fields not supplied to None in execution context. class Point: def __init__(self, x, y): self.x = x self.y = y def __str__(self): return '(%s,%s)' % (self.x, self.y) class NameRecorderMixin: NORMAL = 0 MULTI = 1 MULTISINGLE = 2 FILE = 3 modes = { NORMAL: 'single input returning single value', MULTI: 'multiple inputs returning a list', MULTISINGLE: 'multiple inputs returning single value', FILE: 'file input', } def __init__(self): self.__elem_names = {} def form_open(self): self.__elem_names = {} self.__need_multipart_enc = False self.__needs_close = True def form_close(self): if not self.__needs_close: raise ApplicationError(' elements must not be nested') self.__needs_close = False text = cPickle.dumps(self.__elem_names, -1) text = self.app.pickle_sign(text) if have_zlib: text = zlib.compress(text) text = base64.encodestring(text) self.write_content('
\n') self.__elem_names = {} return self.__need_multipart_enc def input_add(self, itype, name, unused_value = None, return_list = 0): if itype == 'file': self.__need_multipart_enc = True mode = self.FILE elif itype in ('radio', 'submit', 'image'): if return_list: raise ApplicationError('%s input "%s" should not be defined ' 'as "list"' % (itype, name)) mode = self.MULTISINGLE elif return_list: mode = self.MULTI else: mode = self.NORMAL if name in self.__elem_names: prev_mode = self.__elem_names[name] if prev_mode != mode: raise ApplicationError('%s input "%s" was "%s", now defined ' 'as "%s"' % (itype, name, self.modes.get(prev_mode), self.modes.get(mode))) elif mode == self.NORMAL: raise ApplicationError('input "%s" appears more than once, ' 'but is not defined as "list"' % name) else: self.__elem_names[name] = mode def merge_request(self): if self.request.has_field('__albform__'): text = self.request.field_value('__albform__') text = base64.decodestring(text) if have_zlib: text = zlib.decompress(text) text = self.app.pickle_unsign(text) if not text: return elem_names = cPickle.loads(text) for name, mode in elem_names.items(): if mode == self.FILE: value = self.request.field_file(name) elif self.request.has_field(name): value = self.request.field_value(name) else: x_name = '%s.x' % name y_name = '%s.y' % name if self.request.has_field(x_name) \ and self.request.has_field(y_name): value = Point(int(self.request.field_value(x_name)), int(self.request.field_value(y_name))) else: value = None if mode == self.MULTI: if not value: value = [] elif not isinstance(value, list): value = [value] self.set_value(name, value) # ------------------------------------------------------------------ # Handle execution context local namespace - for expression eval(). # ------------------------------------------------------------------ _re_tokens = re.compile(r'([][.])') class Vars: pass class NamespaceMixin: def __init__(self): self.locals = Vars() self.__globals = {} def clear_locals(self): self.locals = Vars() def set_globals(self, dict): self.__globals = dict def eval_expr(self, expr): self.locals.__ctx__ = self try: return eval(expr, self.__globals, self.locals.__dict__) finally: del self.locals.__ctx__ def set_value(self, name, value): if name.startswith('_'): raise SecurityError('cannot merge %s into namespace' % name) # handle iterator back door; op,iter[,value] elems = name.split(',') method = None if len(elems) > 1: method = 'set_backdoor' name = elems[1] if len(elems) > 2: args = (elems[0], elems[2], value) else: args = (elems[0], value) # handle a[12].b or a.b[12] try: ID = 0; DOT_OR_OPEN = 1; INDEX = 2; CLOSE = 3 parent = self.locals index = None state = ID elems = filter(None, _re_tokens.split(name)) elem = elems[-1] # For error reporting for elem in elems[:-1]: if state == ID: if elem in '.[': raise SyntaxError parent = getattr(parent, elem) state = DOT_OR_OPEN elif state == DOT_OR_OPEN: if elem == '.': state = ID elif elem == '[': state = INDEX else: raise SyntaxError elif state == INDEX: index = elem state = CLOSE elif state == CLOSE: if index is None or elem != ']': raise SyntaxError parent = parent[int(index)] index = None state = DOT_OR_OPEN if state == ID: if method: meth = getattr(getattr(parent, elems[-1]), method) meth(*args) else: setattr(parent, elems[-1], value) elif state == CLOSE and elems[-1] == ']' and index is not None: if method: meth = getattr(parent[int(index)], method) meth(*args) else: parent[int(index)] = value else: raise SyntaxError except: try: exc_type, exc_value, exc_tb = sys.exc_info() try: msg = ' in al-input field "%s"' % name exc_value = '%s\n%s' % (exc_value, msg) if exc_type == IndexError and index is not None: exc_value = '%s (index %d, max %d)' % \ (exc_value, index, len(parent)) if elem: # Work out where, if possible... ptr = 'near ^' pad = msg.find('"') - len(ptr) + 2 for e in elems[:-1]: if e is elem: break pad = pad + len(e) exc_value = '%s\n%s%s' % (exc_value, ' ' * pad, ptr) except: pass raise exc_type, exc_value, exc_tb finally: del exc_tb def merge_vars(self, *vars): """ Copy named fields from the request object to the local namespace. Does intelligent prefix matching, so 'foo.bar' matches 'foo.bar[23]' or 'foo.bar.baz', but not 'foo.bargin'. """ for name in self.request.field_names(): for var in vars: if (name == var or (name.startswith(var) and name[len(var)] in '.[')): self.set_value(name, self.request.field_value(name)) def _get_value(self, name): # handle iterator back door; op,iter[,value] elems = name.split(',') if len(elems) >= 2: op = elems[0] iter = self.get_value(elems[1]) if len(elems) > 2: # tree operation return iter.get_backdoor(op, elems[2]) else: return iter.get_backdoor(op) # handle a[12].b or a.b[12] return eval(name, {}, self.locals.__dict__) def make_alias(self, name): pos = name.rindex('.') value = self._get_value(name[:pos]) new_name = value.albatross_alias() setattr(self.locals, new_name, value) self.add_session_vars(new_name) return new_name + name[pos:] def get_value(self, name): try: return self._get_value(name) except (AttributeError, IndexError, NameError): return None def has_value(self, name): try: self._get_value(name) return True except (AttributeError, IndexError, NameError): return False def has_values(self, *names): for name in names: if not self.has_value(name): return False return True # ------------------------------------------------------------------ # Sign pickles to detect tampering at the client side # ------------------------------------------------------------------ class PickleSignMixin: def __init__(self, secret): self.__secret = secret def pickle_sign(self, text): m = hmac.new(self.__secret, digestmod=sha) m.update(text) text = m.digest() + text return text def pickle_unsign(self, text): m = hmac.new(self.__secret, digestmod=sha) digest = text[:m.digest_size] text = text[m.digest_size:] m.update(text) if m.digest() == digest: return text raise SecurityError # ------------------------------------------------------------------ # Session handlers # ------------------------------------------------------------------ # Nul session handler class StubSessionMixin: def add_session_vars(self, *names): pass def del_session_vars(self, *names): pass def encode_session(self): return '' def load_session(self): pass def save_session(self): pass def remove_session(self): pass def set_save_session(self, flag): pass def should_save_session(self): return 0 class SessionBase: def __init__(self): self.__init_vars() self.__save_session = True def add_session_vars(self, *names): if len(names) == 1 and isinstance(names[0], (list, tuple)): names = names[0] for name in names: assert isinstance(name, str) if not hasattr(self.locals, name): raise ApplicationError('add "%s" to locals first' % name) for name in names: self.__vars[name] = True def default_session_var(self, name, value): if not hasattr(self.locals, name): setattr(self.locals, name, value) self.__vars[name] = True def del_session_vars(self, *names): if len(names) == 1 and isinstance(names[0], (list, tuple)): names = names[0] for name in names: if name in self.__vars: del self.__vars[name] def session_vars(self): return self.__vars.keys() def __init_vars(self): self.__vars = {'__page__': True, '__pages__': True} def remove_session(self): self.__init_vars() self.clear_locals() def decode_session(self, text): def imp_hook(name, globals=None, locals=None, fromlist=None): if self.app.is_page_module(name): return self.app.load_page_module(self, name) else: return real_imp(name, globals, locals, fromlist) real_imp, __builtin__.__import__ = __builtin__.__import__, imp_hook try: try: vars = cPickle.loads(text) except pickle_errors, e: sys.stderr.write('cannot unpickle - %s\n' % e) raise ApplicationError("can't unpickle session") self.locals.__dict__.update(vars) for name in vars.keys(): self.__vars[name] = True finally: __builtin__.__import__ = real_imp def encode_session(self): vars = {} for name in self.__vars.keys(): if hasattr(self.locals, name): vars[name] = getattr(self.locals, name) try: return cPickle.dumps(vars, -1) except pickle_errors, e: for name, value in vars.items(): try: cPickle.dumps(value, -1) except pickle_errors, e: raise ApplicationError('locals "%s": %s' % (name, e)) raise ApplicationError('cannot pickle ctx.locals: %s' % e) def set_save_session(self, flag): self.__save_session = flag def should_save_session(self): return self.__save_session # Session in hidden fields class HiddenFieldSessionMixin(SessionBase): def encode_session(self): text = SessionBase.encode_session(self) text = self.app.pickle_sign(text) if have_zlib: text = zlib.compress(text) return base64.encodestring(text) def load_session(self): if not self.request.has_field('__albstate__'): return text = self.request.field_value('__albstate__') text = base64.decodestring(text) if have_zlib: text = zlib.decompress(text) text = self.app.pickle_unsign(text) if text: SessionBase.decode_session(self, text) def save_session(self): pass def form_close(self): if self.should_save_session(): text = self.encode_session() self.write_content('
\n') # ------------------------------------------------------------------ # An execution context for stand-alone template file use. # ------------------------------------------------------------------ def _caller_globals(name): frame = sys._getframe(1) while frame.f_code.co_name != name: frame = frame.f_back while frame.f_code.co_name == name: frame = frame.f_back return frame.f_globals class SimpleContext(NamespaceMixin, ExecuteMixin, ResourceMixin, TemplateLoaderMixin, StubRecorderMixin, StubSessionMixin): def __init__(self, template_path): from albatross import tags NamespaceMixin.__init__(self) ExecuteMixin.__init__(self) ResourceMixin.__init__(self) TemplateLoaderMixin.__init__(self, template_path) apply(self.register_tagclasses, tags.tags) self.set_globals(_caller_globals('__init__')) albatross-1.36/albatross/simpleserver.py0000644000175000017500000002655610446670652020425 0ustar andrewmandrewm#!/usr/bin/python # # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # import socket import select import random import time import binascii import struct import bisect import sys import getopt import errno import traceback # Max consecutive server code failures a client can trigger before we # close our connection to them. MAX_CLIENT_EXCEPTIONS = 3 class Session: def __init__(self, sesid, life): self.sesid = sesid self.life = life self.text = '' self.last_access = time.time() def die_time(self): return self.last_access + self.life class SessionDict: def __init__(self): self.dict = {} self.usage = 0 def new_session(self, sesid, life): ses = Session(sesid, life) self.dict[sesid] = ses return ses def set_session(self, sesid, text): if sesid not in self.dict: return ses = self.dict[sesid] self.usage = self.usage - len(ses.text) + len(text) ses.text = text ses.last_access = time.time() def get_session(self, sesid, reset_access = 1): ses = self.dict.get(sesid) if ses and reset_access: ses.last_access = time.time() return ses def del_session(self, sesid): if sesid in self.dict: self.usage = self.usage = len(self.dict[sesid].text) del self.dict[sesid] def has_session(self, sesid): return sesid in self.dict class SesLife: def __init__(self, sesid, die_time): self.sesid = sesid self.die_time = die_time def __cmp__(self, other): return cmp(self.die_time, other.die_time) class Reaper: def __init__(self, sessions): self.sessions = sessions self.list = [] def add(self, sesid, life): bisect.insort(self.list, SesLife(sesid, time.time() + life)) def process(self): now = time.time() while self.list: event = self.list[0] if event.die_time > now: break del self.list[0] ses = self.sessions.get_session(event.sesid, 0) if ses: die_time = ses.die_time() if die_time <= now: self.sessions.del_session(event.sesid) else: event.die_time = die_time bisect.insort(self.list, event) if self.list: event = self.list[0] return event.die_time - now class Context: def __init__(self, log_file): self.log_file = log_file self.sessions = SessionDict() self.reaper = Reaper(self.sessions) self.read_files = [] self.write_files = [] def log(self, msg): if self.log_file: self.log_file.write(msg) self.log_file.flush() def add_write_file(self, file): self.write_files.append(file) def del_write_file(self, file): try: self.write_files.remove(file) except ValueError: pass def add_read_file(self, file): self.read_files.append(file) def del_read_file(self, file): try: self.read_files.remove(file) except ValueError: pass def del_file(self, file): self.del_read_file(file) self.del_write_file(file) def select(self): timeout = self.reaper.process() try: read_out, write_out, oob_out = \ select.select(self.read_files, self.write_files, [], timeout) except select.error, (eno, estr): if eno in (errno.EAGAIN, errno.EINTR): return [], [] raise return read_out, write_out def reaper_add(self, sesid, life): self.reaper.add(sesid, life) def new_session(self, sesid, life): return self.sessions.new_session(sesid, life) def del_session(self, sesid): return self.sessions.del_session(sesid) def get_session(self, sesid): ses = self.sessions.get_session(sesid) if ses: self.log("session get %s, %d bytes, %d total\n" % \ (sesid, len(ses.text), self.sessions.usage)) return ses def set_session(self, sesid, text): self.log("session put %s, %d bytes, %d total\n" % \ (sesid, len(text), self.sessions.usage)) return self.sessions.set_session(sesid, text) def has_session(self, sesid): return self.sessions.has_session(sesid) COMMAND = 0 PUT = 1 class Client: def __init__(self, context, sock, addr): self.context = context self.sock = sock self.addr = addr self.sock.setblocking(0) self.input = '' self.output = [] self.state = COMMAND self.error_count = 0 self.context.log('new client: %s from %s\n' % (sock.fileno(), addr[0])) def __del__(self): self.context.log('del client: %s\n' % self.sock.fileno()) def fileno(self): return self.sock.fileno() def do_read(self): try: str = self.sock.recv(16384) except socket.error: return 0 if not str: return 0 self.input = self.input + str while 1: if self.state == COMMAND: pos = self.input.find('\r\n') if pos < 0: return 1 command = self.input[:pos] self.input = self.input[pos + 2:] if not self.handle_command(command): return 0 elif self.state == PUT: pos = self.input.find('\r\n\r\n') if pos < 0: return 1 text = self.input[:pos] self.input = self.input[pos + 4:] self.context.set_session(self.key, text) self.write('OK\r\n') self.state = COMMAND def do_write(self): while self.output: str = self.output[0] try: num = self.sock.send(str) except socket.error: return 0 if num == len(str): del self.output[0] else: self.output[0] = str[num:] return 1 self.context.del_write_file(self) return 1 def write(self, str): if not self.output: self.context.add_write_file(self) self.output.append(str) def make_sesid(self, app): while 1: try: text = open('/dev/urandom').read(8) except: text = struct.pack('d', random.random()) sesid = binascii.hexlify(text) if not self.context.get_session((app, sesid)): return sesid def handle_command(self, str): self.context.log('client %s cmd: %s\n' % (self.sock.fileno(), str)) try: words = str.split() if not words: return 1 cmd = words[0].lower() if cmd == 'new' and len(words) == 3: # new app life\r\n -> sesid\r\n # generate a new session id app = words[1] try: life = int(words[2]) except: self.write('ERROR bad life\r\n') return 1 sesid = self.make_sesid(words[1]) self.write('OK %s\r\n' % sesid) key = (app, sesid) self.context.new_session(key, life) self.context.reaper_add(key, life) elif cmd == 'get' and len(words) == 3: # get app sesid\r\n -> text\r\n # get the data for a session app, sesid = words[1:] key = (app, sesid) ses = self.context.get_session(key) if not ses: self.write('ERROR no such session\r\n') else: self.write('OK - session follows\r\n') self.write(ses.text) self.write('\r\n\r\n') elif cmd == 'put' and len(words) == 3: # put app sesid\r\ntext\r\n -> OK\r\n # put session data app, sesid = words[1:] key = (app, sesid) if not self.context.has_session(key): self.write('ERROR no such session\r\n') else: self.key = key self.state = PUT self.write('OK - send data now, terminate with blank line\r\n') elif cmd == 'del' and len(words) == 3: # del app sesid\r\n -> OK\r\n # delete session app, sesid = words[1:] key = (app, sesid) ses = self.context.get_session(key) if not ses: self.write('ERROR no such session\r\n') else: self.context.del_session(key) self.write('OK\r\n') elif cmd == 'quit' and len(words) == 1: return 0 else: self.write('ERROR unrecognised command: %s\r\n' % str) if self.error_count > 0: self.error_count -= 1 return 1 except: self.error_count += 1 if self.error_count > MAX_CLIENT_EXCEPTIONS: return 0 traceback.print_exc() self.write('ERROR session server failure - check logs\r\n') return 1 class Server: def __init__(self, port, log_file): self.context = Context(log_file) self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.listen_sock.setblocking(0) self.listen_sock.bind(('', port)) self.listen_sock.listen(5) self.context.add_read_file(self.listen_sock) def select_loop(self): self.run = 1 while self.run: read_out, write_out = self.context.select() for s in read_out: if s is self.listen_sock: c, addr = self.listen_sock.accept() self.context.add_read_file(Client(self.context, c, addr)) del c # Otherwise socket won't be GC'ed in a timely manner else: if not s.do_read(): self.context.del_file(s) for s in write_out: if not s.do_write(): self.context.del_file(s) read_out = write_out = s = None def stop(self): # This allows signal handlers to ask us to stop self.run = 0 def usage(): sys.stderr.write('usage: session-server.py [-h] [-p port] [-l log-file]\n') if __name__ == '__main__': port = 34343 log_file = None try: opts, args = getopt.getopt(sys.argv[1:], "hp:l:", ["help", "port=", "log="]) except getopt.GetoptError: usage() sys.exit(2) for opt, arg in opts: if opt in ("-h", "--help"): usage() sys.exit() elif opt in ("-p", "--port"): port = int(arg) elif opt in ("-l", "--log"): log_file = open(arg, 'a') server = Server(port, log_file) server.select_loop() albatross-1.36/albatross/apacheapp.py0000644000175000017500000000301410446670652017607 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # from mod_python import util, apache from albatross.request import RequestBase from albatross.common import * class Request(RequestBase): def __init__(self, req): self.__req = req RequestBase.__init__(self, util.FieldStorage(self.__req)) def get_uri(self): return self.__req.uri def get_method(self): return self.__req.method def get_path_info(self): return self.__req.path_info def get_servername(self): try: return self.__req.hostname # mod_python 3 except AttributeError: return self.__req.connection.server.server_hostname # mod_python 2 def get_header(self, name): if name in self.__req.headers_in: return self.__req.headers_in[name] def write_header(self, name, value): if name.lower() == 'content-type': self.__req.content_type = value else: self.__req.headers_out.add(name, value) def end_headers(self): self.__req.send_http_header() def redirect(self, loc): self.write_header('Location', loc) return apache.HTTP_MOVED_PERMANENTLY def write_content(self, data): self.__req.write(data) def set_status(self, status): self.__req.status = status RequestBase.set_status(self, status) def return_code(self): return apache.OK albatross-1.36/albatross/tags.py0000644000175000017500000012475410575725155016644 0ustar andrewmandrewm# Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # Standard albatross tags # import time from albatross.template import Content, EmptyTag, EnclosingTag, Text, \ template_version, template_features from albatross.common import * # Empty content object which tags can use as default value for # optional child tag content. , , etc. not_specified = Content() # Escape text for attribute values def escape(text): text = str(text) text = text.replace('&', '&') text = text.replace('<', '<') text = text.replace('>', '>') text = text.replace('"', '"') text = text.replace("'", ''') return text # Implement the expr, nameattr, etc attribute behaviour class InputNameValueMixin: def get_name(self, ctx): alias = self.get_attrib('alias') if alias: name = ctx.make_alias(alias) else: name = self.get_expr_attrib(ctx, 'name') if not name: self.raise_error('"name" attribute is null') return name def get_name_and_value(self, ctx): name = self.get_name(ctx) if self.has_attrib('expr'): return name, self.eval_attrib(ctx, 'expr') value = self.get_attrib('value') if value is not None: return name, value else: return name, ctx.get_value(name) def input_add(self, ctx, itype, name, value, multiple): if not self.get_bool_attrib(ctx, 'disabled'): ctx.input_add(itype, name, value, multiple) # ------------------------------------------------------------------ # The HTML tag is wrapped so that variable values can be # automatically pulled out of the execution context and rendered as # the VALUE attribute. When using Albatross application objects, the # corresponding browser request is automatically merged back into the # execution context. class Input(EmptyTag, InputNameValueMixin): name = 'al-input' naming_attrs = ('name', 'nameexpr', 'alias', 'prevpage', 'nextpage', 'treefold', 'treeselect', 'treeellipsis') all_attrs = ('expr', 'valueexpr', 'list', 'noescape', 'node') + naming_attrs def __init__(self, ctx, filename, line_num, attribs): EmptyTag.__init__(self, ctx, filename, line_num, attribs) self.__itype = self.get_attrib('type', 'text').lower() if self.__itype not in self.type_dict: self.raise_error('unrecognised ' % self.__itype) self.assert_any_attrib(*self.naming_attrs) def write_attribs_except(self, ctx, *attribs): EmptyTag.write_attribs_except(self, ctx, *(self.all_attrs + attribs)) def get_name(self, ctx): for name in ('prevpage', 'nextpage'): if self.has_attrib(name): iter_name = self.get_attrib(name) return '%s,%s' % (name, iter_name) for name in ('treefold', 'treeselect', 'treeellipsis'): if self.has_attrib(name): iter_name = self.get_attrib(name) if self.has_attrib('node'): node = self.eval_attrib(ctx, 'node') else: iter = ctx.get_value(iter_name) node = iter.value() return '%s,%s,%s' % (name, iter_name, node.albatross_alias()) return InputNameValueMixin.get_name(self, ctx) def get_name_value_checked(self, ctx, default = None): # Name name = self.get_name(ctx) # Value if self.has_attrib('expr'): value = self.eval_attrib(ctx, 'expr') else: value = ctx.get_value(name) # Checked value if self.has_attrib('valueexpr'): return name, value, self.eval_attrib(ctx, 'valueexpr') elif self.has_attrib('value'): return name, value, self.get_attrib('value') else: return name, value, default def to_html(self, ctx): ctx.write_content('') def generic_to_html(self, ctx): self.write_attribs_except(ctx, 'value') name, value = self.get_name_and_value(ctx) if name: ctx.write_content(' name="%s"' % name) if value is not None: if self.has_attrib('noescape'): ctx.write_content(' value="%s"' % value) else: ctx.write_content(' value="%s"' % escape(value)) self.input_add(ctx, self.__itype, name, value, self.has_attrib('list')) def radio_to_html(self, ctx): self.write_attribs_except(ctx, 'value', 'checked') name, value, value_attr = self.get_name_value_checked(ctx) ctx.write_content(' name="%s"' % name) ctx.write_content(' value="%s"' % value_attr) if str(value) == str(value_attr): ctx.write_content(' checked') self.input_add(ctx, 'radio', name, value_attr, self.has_attrib('list')) def checkbox_to_html(self, ctx): self.write_attribs_except(ctx, 'value', 'checked') name, value, value_attr = self.get_name_value_checked(ctx, 'on') if value and self.has_attrib('treeselect'): value = 'on' ctx.write_content(' name="%s"' % name) ctx.write_content(' value="%s"' % value_attr) if isinstance(value, (list, tuple)): if str(value_attr) in map(str, value): ctx.write_content(' checked') elif str(value) and str(value) == str(value_attr): ctx.write_content(' checked') self.input_add(ctx, 'checkbox', name, value_attr, self.has_attrib('list')) def generic_novalue_to_html(self, ctx): # value= not applicable self.write_attribs_except(ctx, 'value') name = self.get_name(ctx) ctx.write_content(' name="%s"' % name) self.input_add(ctx, self.__itype, name, None, self.has_attrib('list')) type_dict = { 'text': generic_to_html, 'password': generic_to_html, 'radio': radio_to_html, 'checkbox': checkbox_to_html, 'submit': generic_to_html, 'reset': generic_to_html, 'image': generic_novalue_to_html, 'file': generic_novalue_to_html, 'hidden': generic_to_html, 'button': generic_to_html, } # Allows the application need to modify the HREF attribute of the # tag to include extra information from the execution context. class Href(EnclosingTag, InputNameValueMixin): name = 'al-a' def __init__(self, ctx, filename, line_num, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) def to_html(self, ctx): ctx.write_content('') EnclosingTag.to_html(self, ctx) ctx.write_content('') # Allows the application need to modify the SRC attribute of the # tag to include extra information from the execution context. class Img(EmptyTag, InputNameValueMixin): name = 'al-img' def __init__(self, ctx, filename, line_num, attribs): EmptyTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('expr') def to_html(self, ctx): ctx.write_content('') # Using the albatross tag allows Albatross to record the # contents of each form. This is used to register valid browser # requests with the toolkit. # # We use a content_trap to capture the enclosed content so we know before # emiting the element whether or not to use a multipart/form-data # encoding (required to make work correctly). class Form(EnclosingTag): name = 'al-form' def to_html(self, ctx): ctx.push_content_trap() ctx.form_open() EnclosingTag.to_html(self, ctx) use_multipart_enc = ctx.form_close() content = ctx.pop_content_trap() ctx.write_content('') ctx.write_content(content) ctx.write_content('') # Applications which need to know the list of valid # options will need to use tags in template files. The # alternative is to define the option list in Python code. # # HTML 4.01 defines the ') # Some tags have their option list supplied by an # application value. There are two forms: # # 12 # # class Select(EnclosingTag, InputNameValueMixin): name = 'al-select' def __init__(self, ctx, filename, line_num, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('name', 'alias', 'nameexpr') def to_html(self, ctx): name, value = self.get_name_and_value(ctx) ctx.write_content('' % name) if isinstance(value, (list, tuple)): value = map(str, value) else: value = str(value) if self.has_attrib('optionexpr'): seq = self.eval_attrib(ctx, 'optionexpr') for item in seq: self.option_to_html(ctx, item, value) else: ctx.set_active_select(self, value) EnclosingTag.to_html(self, ctx) ctx.clear_active_select() ctx.write_content('') self.input_add(ctx, 'select', name, None, self.has_attrib('list') or self.has_attrib('multiple')) def option_to_html(self, ctx, item, select_value): if isinstance(item, (list, tuple)): value, text = map(str, item) else: value = str(item) text = value ctx.write_content('') if self.has_attrib('noescape'): ctx.write_content(text) else: ctx.write_content(escape(text)) ctx.write_content('') ctx.write_content('\n') class TextArea(EnclosingTag, InputNameValueMixin): name = 'al-textarea' def __init__(self, ctx, filename, line_num, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('name', 'alias', 'nameexpr') def to_html(self, ctx): name = self.get_name(ctx) ctx.write_content('' % name) if ctx.has_value(name): value = ctx.get_value(name) if not value: ctx.write_content('') elif self.has_attrib('noescape'): ctx.write_content(value) else: ctx.write_content(escape(value)) else: EnclosingTag.to_html(self, ctx) ctx.write_content('') self.input_add(ctx, 'textarea', name, None, self.has_attrib('list')) # ------------------------------------------------------------------ # Send HTML accumulated in execution context to output. class Flush(EmptyTag): name = 'al-flush' def to_html(self, ctx): ctx.flush_content() # Do not execute enclosed content class Comment(EnclosingTag): name = 'al-comment' def to_html(self, ctx): pass # Include a template file. Can name the file directly, or can use an # expression to yield a filename. class Include(EmptyTag, InputNameValueMixin): name = 'al-include' def __init__(self, ctx, filename, line_num, attribs): EmptyTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('name', 'expr') def to_html(self, ctx): if self.has_attrib('expr'): name = self.eval_attrib(ctx, 'expr') else: name = self.get_attrib('name') templ = ctx.load_template(name) templ.to_html(ctx) # Conditional content - emulates Python # if expr: # elif expr: # else: # # # Value > 12 # # Value > 5 and <= 12 # # Value <= 5 # class Elif(EmptyTag, InputNameValueMixin): name = 'al-elif' def __init__(self, ctx, filename, line_num, attribs): EmptyTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('expr') def is_true(self, ctx): return self.eval_attrib(ctx, 'expr') class Else(EmptyTag): name = 'al-else' class If(EnclosingTag, InputNameValueMixin): name = 'al-if' def __init__(self, ctx, filename, line_num, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('expr') self.elif_list = [] self.else_content = not_specified self.current_content = self.content def to_html(self, ctx): if self.eval_attrib(ctx, 'expr'): EnclosingTag.to_html(self, ctx) return for tag, content in self.elif_list: if tag.is_true(ctx): content.to_html(ctx) return self.else_content.to_html(ctx) def append(self, tag): if isinstance(tag, Elif): self.current_content = Content() self.elif_list.append((tag, self.current_content)) elif isinstance(tag, Else): self.current_content = Content() self.else_content = self.current_content else: self.current_content.append(tag) # Execute some Python code # # class Exec(EmptyTag, InputNameValueMixin): name = 'al-exec' def __init__(self, ctx, filename, line_num, attribs): EmptyTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('expr') def to_html(self, ctx): self.eval_attrib(ctx, 'expr', 'exec') # Iterated content - emulates Python # for iter in sequence: class ListIterator: def __init__(self): self._index = self._start = self._count = 0 self._seq = self._have_value = None self._pagesize = 0 def __getstate__(self): return self._start, self._pagesize def __setstate__(self, tup): self._start, self._pagesize = tup self._index = self._start self._have_value = self._seq = None def __len__(self): if self._pagesize: return self._pagesize elif self._seq is not None: return len(self._seq) else: return 0 # ---------------------------------------------------- # Pagination functionality. def pagesize(self): return self._pagesize def set_pagesize(self, size): self._pagesize = size def has_prevpage(self): return self._start > 0 def has_nextpage(self): if not self._pagesize: raise ApplicationError('iterator not in page mode') return len(self._seq) > self._start + self._pagesize def get_backdoor(self, op): return 1 def set_backdoor(self, op, value): if value: if op == 'nextpage': self._start = self._start + self._pagesize self._index = self._start elif op == 'prevpage': self._start = self._start - self._pagesize if self._start < 0: self._start = 0 self._index = self._start # ---------------------------------------------------- # Sequence control. def has_sequence(self): return self._seq is not None def set_sequence(self, seq): self._seq = seq # ---------------------------------------------------- # Iteration. Make sure that each element is only retrieved from # the sequence once. This allows programs to make use of objects # which are not really sequences; popen().readline() for instance. def reset_index(self): self._index = self._start def reset_count(self): self._count = 0 def index(self): return self._index def count(self): return self._count def start(self): return self._start def value(self): return self._value def clear_value(self): self._have_value = None def has_value(self): if self._have_value is None: try: self._value = self._seq[self._index] self._have_value = 1 except IndexError: self._have_value = 0 return self._have_value def set_value(self, value): self._value = value self._have_value = 1 def next(self): self._index += 1 self._count += 1 self._have_value = None self.has_value() class ListIteratorLite: """ A simpler ListIterator for cases were we don't need pagination, etc This one does not index the sequence, using only the iterator protocol. """ def __init__(self): self._index = -1 self._iter = self._have_value = None def has_sequence(self): return self._iter is not None def set_sequence(self, seq): self._iter = iter(seq) # ---------------------------------------------------- # Iteration. Make sure that each element is only retrieved from # the sequence once. This allows programs to make use of objects # which are not really sequences; popen().readline() for instance. def reset_index(self): pass def reset_count(self): pass def pagesize(self): return 0 def index(self): return self._index def value(self): return self._value def clear_value(self): self._have_value = None def has_value(self): if not self._have_value: try: self._value = self._iter.next() self._have_value = True self._index += 1 except StopIteration: self._have_value = False return self._have_value def set_value(self, value): self._value = value self._have_value = True def next(self): self._have_value = False # EXPR:seq # Yields an object which implements sequence protocol. The result # is assigned to the ITER attribute. If not present, the ITER # attribute must already have a sequence in it. # PAGESIZE:int # Turns on pagination. If this attribute is used anywhere with the # named ITER then that page size becomes the default for all other # uses of the ITER. Places the ITER into the session. # CONTINUE:bool # Instructs the tag not to reset the index() to the start() - it is # continued on from the previous ITER use. # PREPARE:bool # Tells the tag to attach the sequence to the ITER and perform # pagination calculations. This makes the ITER available for tests # before the content is rendered. # COLS:int # Instructs the tag to render the content in multiple columns. # Items are arranged to flow down columns by default. # FLOW:across/down # Define the order in which multicolumn output is arranged. class For(EnclosingTag, InputNameValueMixin): name = 'al-for' def __init__(self, ctx, filename, line_num, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('iter', 'expr') if self.has_attrib('flow'): if self.get_attrib('flow').lower() not in ('across', 'down'): self.raise_error('"flow" must be "across" or "down"') def to_html(self, ctx): name = self.get_attrib('iter') iter = None # If PAGESIZE or CONTINUE defined then the iterator will not # be recreated if it already exists. if self.has_attrib('pagesize') or self.has_attrib('continue'): if not name: raise ApplicationError('iterator must be named if "pagesize" ' 'or "continue" are used') iter = ctx.get_value(name) if iter is None: if self.has_attrib('pagesize') or self.has_attrib('cols'): iter = ListIterator() else: iter = ListIteratorLite() if name: ctx.set_value(name, iter) # If iterator does not have a sequence yet, the EXPR attribute # must be present if self.has_attrib('expr'): seq = self.eval_attrib(ctx, 'expr') iter.set_sequence(seq) elif not iter.has_sequence(): self.raise_error('no sequence present; expected "expr" attribute') # Place iterator value into namespace? var_names = self.get_attrib('vars') if var_names: var_names = commasplit(var_names) # If PAGESIZE is defined then place the iterator into the # session. if self.has_attrib('pagesize'): ctx.add_session_vars(name) pagesize = int(self.get_attrib('pagesize')) iter.set_pagesize(pagesize) # If CONTINUE is defined then the contents will flow on from # the previous use of the iterator if not self.has_attrib('continue'): iter.reset_index() iter.reset_count() iter.has_value() # If PREPARE defined then no rendering performed if self.has_attrib('prepare'): return # Render the sequence contents if self.has_attrib('cols'): if self.get_attrib('flow', 'down').lower() == 'down': self.cols_down_html(ctx, iter) else: self.cols_across_html(ctx, iter) else: pagesize = iter.pagesize() while iter.has_value(): if pagesize and iter.count() >= pagesize: break if var_names: value = iter.value() if len(var_names) == 1: ctx.set_value(var_names[0], value) else: for a, v in zip(var_names, value): ctx.set_value(a, v) EnclosingTag.to_html(self, ctx) iter.next() iter.clear_value() def cols_down_html(self, ctx, iter): num_cols = int(self.get_attrib('cols')) num = len(iter) num_rows = num / num_cols num_extra = num % num_cols for row in range(num_rows + (num_extra > 0)): index = row seq = [] for col in range(num_cols): if row == num_rows and col == num_extra: break seq.append(iter._seq[index]) index = index + num_rows + (col < num_extra) if index >= num: break iter.set_value(seq) EnclosingTag.to_html(self, ctx) def cols_across_html(self, ctx, iter): num_cols = int(self.get_attrib('cols')) pagesize = iter.pagesize() num = len(iter) index = 0 while index < num: seq = iter._seq[index:index + num_cols] iter.set_value(seq) EnclosingTag.to_html(self, ctx) index = index + num_cols class TreeIterator: def __init__(self): self._stack = [] def __setstate__(self, *tup): self._stack = [] def tree_depth(self): return self._tree_depth def depth(self): return len(self._stack) def span(self): return self._tree_depth - len(self._stack) def line(self, depth): return self._line[depth] def set_line(self, line): self._line = line def value(self): return self._value def set_value(self, node): self._value = node def is_open(self): return hasattr(self._value, 'children') def node_use_ellipsis(self, ctx, node): return 0 def use_ellipsis(self): return 0 def node_type(self): return 0 def node_is_open(self, ctx, node): return hasattr(node, 'children') def is_selected(self): return 0 def has_children(self): return hasattr(self._value, 'children') def get_backdoor(self, op, key): return None def set_backdoor(self, op, key, value): pass class LazyTreeIterator(TreeIterator): def __init__(self, single_select = 0): TreeIterator.__init__(self) self._single_select = single_select self._key = None self._type = 0 self._selected_aliases = {} self._open_aliases = {} def __getstate__(self): return self._single_select, self._open_aliases, self._selected_aliases def __setstate__(self, tup): TreeIterator.__setstate__(self, tup) self._key = None self._single_select, self._open_aliases, self._selected_aliases = tup def set_value(self, node, type = 0): TreeIterator.set_value(self, node) if node: self._key = node.albatross_alias() self._type = type def is_open(self): if not hasattr(self._value, 'children'): return 0 return self._key in self._open_aliases def load_children(self, ctx, node): if not node.children_loaded: node.load_children(ctx) node.children_loaded = 1 def node_is_open(self, ctx, node): if not hasattr(node, 'children'): return 0 node_open = node.albatross_alias() in self._open_aliases if node_open: self.load_children(ctx, node) return node_open def is_selected(self): return self._key in self._selected_aliases def node_type(self): return self._type def close_all(self): self._open_aliases = {} def deselect_all(self): self._selected_aliases = {} def get_selected_aliases(self): aliases = self._selected_aliases.keys() aliases.sort() return aliases def select_alias(self, alias, value = 1): if value: self._selected_aliases[alias] = 1 elif alias in self._selected_aliases: del self._selected_aliases[alias] def set_selected_aliases(self, aliases): self._selected_aliases = {} for alias in aliases: self._selected_aliases[alias] = 1 def get_open_aliases(self): aliases = self._open_aliases.keys() aliases.sort() return aliases def open_alias(self, alias, open = 1): if open: self._open_aliases[alias] = 1 elif alias in self._open_aliases: del self._open_aliases[alias] def set_open_aliases(self, aliases): self._open_aliases = {} for alias in aliases: self._open_aliases[alias] = 1 def get_backdoor(self, op, key): if op == 'treeselect': return key in self._selected_aliases elif op == 'treefold': return 1 else: return None def set_backdoor(self, op, key, value): if op == 'treeselect': if value: if self._single_select: self._selected_aliases = { key: 1 } else: self._selected_aliases[key] = 1 elif key in self._selected_aliases: del self._selected_aliases[key] elif op == 'treefold': if value: if key in self._open_aliases: del self._open_aliases[key] else: self._open_aliases[key] = 1 class EllipsisTreeIterator(LazyTreeIterator): def __init__(self): LazyTreeIterator.__init__(self) self._noellipsis_alias = None def __setstate__(self, tup): LazyTreeIterator.__setstate__(self, tup) self._noellipsis_alias = None def node_use_ellipsis(self, ctx, node): if self._key == self._noellipsis_alias: return 0 else: subnodes_open = 0 for sub_node in self.value().children: if self.node_is_open(ctx, sub_node): subnodes_open = 1 return subnodes_open def get_backdoor(self, op, key): if op == 'treeellipsis': return 1 else: return LazyTreeIterator.get_backdoor(self, op, key) def set_backdoor(self, op, key, value): if op == 'treeellipsis': if value: self._noellipsis_alias = key else: LazyTreeIterator.set_backdoor(self, op, key, value) # Format tree structured data. class Tree(EnclosingTag, InputNameValueMixin): name = 'al-tree' def __init__(self, ctx, filename, line_num, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('expr') self.assert_any_attrib('iter') def to_html(self, ctx): # Get iterator out of context - allows tree state to be # restored from session name = self.get_attrib('iter') iter = ctx.get_value(name) if iter is None: if self.has_attrib('ellipsis'): iter = EllipsisTreeIterator() elif self.has_attrib('lazy'): iter = LazyTreeIterator(self.has_attrib('single')) else: iter = TreeIterator() ctx.set_value(name, iter) ctx.add_session_vars(name) tree = self.eval_attrib(ctx, 'expr') iter._tree_depth = self.find_tree_depth(ctx, iter, tree) self.node_to_html(ctx, iter, tree) def find_tree_depth(self, ctx, iter, tree): deepest = 0 iter.set_value(tree) if iter.node_is_open(ctx, tree): for node in tree.children: depth = self.find_tree_depth(ctx, iter, node) if depth > deepest: deepest = depth return deepest + 1 def node_to_html(self, ctx, iter, tree): parent = None line = [] for node in iter._stack: if parent: if node is parent.children[-1]: line.append(0) else: line.append(1) parent = node if parent: if tree is parent.children[-1]: line.append(2) else: line.append(1) iter.set_line(line) iter.set_value(tree) EnclosingTag.to_html(self, ctx) iter._stack.append(tree) if iter.node_is_open(ctx, tree): if iter.node_use_ellipsis(ctx, tree): skipped = 0 for sub_node in tree.children: if iter.node_is_open(ctx, sub_node): if skipped: iter.set_value(tree, 1) EnclosingTag.to_html(self, ctx) skipped = 0 self.node_to_html(ctx, iter, sub_node) else: skipped = 1 if skipped: iter.set_value(tree, 1) EnclosingTag.to_html(self, ctx) else: for node in tree.children: self.node_to_html(ctx, iter, node) del iter._stack[-1] # Include execution context values # # # # # # When present the DATE attribute specifies a format string which is # passed to time.strftime() to format the expression value. The # LOOKUP value uses the expression value as an index into the named # lookup table and renders the value as the corresponding table entry. class Value(EmptyTag, InputNameValueMixin): name = 'al-value' def __init__(self, ctx, filename, line_num, attribs): EmptyTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('expr') def to_html(self, ctx): value = self.eval_attrib(ctx, 'expr') format = self.get_attrib('date') if format: value = time.strftime(format, time.localtime(value)) ctx.write_content(value) return lookup_name = self.get_attrib('lookup') if lookup_name: lookup = ctx.get_lookup(lookup_name) if not lookup: self.raise_error('undefined lookup "%s"' % lookup_name) lookup.lookup_html(ctx, value) return if self.has_attrib('noescape'): ctx.write_content(str(value)) else: ctx.write_content(escape(value)) # Specify one entry in a lookup table # # # The enclosed content is evaluated when the table is # accessed, not when the table is contructed. The EXPR # attribute is evaluated when the table is evaluated. # This allows the application to establish a context in # which the item EXPR attributes can be resolved to a # constant. # class Item(EnclosingTag, InputNameValueMixin): name = 'al-item' def __init__(self, ctx, filename, line_num, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('expr') # Specify a lookup table for translating a value to arbitrary content # # # Content # Content # Content # class Lookup(EnclosingTag): name = 'al-lookup' def __init__(self, ctx, filename, line_num, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) self.assert_any_attrib('name', 'expr') if self.has_attrib('name'): ctx.register_lookup(self.get_attrib('name'), self) self.item_list = [] def append(self, tag): if isinstance(tag, Item): self.item_list.append(tag) else: EnclosingTag.append(self, tag) def _build_item_dict(self, ctx): if not hasattr(self, 'item_dict'): item_dict = {} for item in self.item_list: item_dict[item.eval_attrib(ctx, 'expr')] = item self.item_dict = item_dict def to_html(self, ctx): self._build_item_dict(ctx) if self.has_attrib('expr'): self.lookup_html(ctx, self.eval_attrib(ctx, 'expr')) def lookup_html(self, ctx, value): self._build_item_dict(ctx) item = self.item_dict.get(value, self.content) item.to_html(ctx) # ------------------------------------------------------------------ # Macro expansions # # Usearg and Macro do not produce output when evaluated - only when # expanded via class Setdefault(EnclosingTag): name = 'al-setdefault' def __init__(self, ctx, filename, line_num, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) self.assert_has_attrib('name') class Usearg(EmptyTag): name = 'al-usearg' def to_html(self, ctx): content = ctx.get_macro_arg(self.get_attrib('name')) # Peel one level of arguments off stack to evaluate enclosed # content then restore the stack afterwards. Nested macros # cause infinite recursion otherwise. args = ctx.pop_macro_args() content.to_html(ctx) ctx.push_macro_args(args) class Macro(EnclosingTag): name = 'al-macro' def __init__(self, ctx, filename, line_num, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) self.assert_has_attrib('name') self.arg_defaults = {} ctx.register_macro(self.get_attrib('name'), self) def append(self, tag): if isinstance(tag, Setdefault): self.arg_defaults[tag.get_attrib('name')] = tag else: EnclosingTag.append(self, tag) def to_html(self, ctx): pass def expand_html(self, ctx): EnclosingTag.to_html(self, ctx) class Setarg(EnclosingTag): name = 'al-setarg' class _MacroArgExpr: def __init__(self, expr): self.expr = expr self.co_expr = None def to_html(self, ctx): if self.co_expr is None: self.co_expr = compile(self.expr, '', 'eval') ctx.write_content(ctx.eval_expr(self.co_expr)) class Expand(EnclosingTag): name = 'al-expand' def __init__(self, ctx, filename, line_num, attribs): EnclosingTag.__init__(self, ctx, filename, line_num, attribs) self.arg_dict = {None: self.content} self.arg_expr = {} for name, value in self.attrib_items(): if name.endswith('arg'): self.arg_dict[name[:-3]] = Text(value) elif name.endswith('argexpr'): self.arg_dict[name[:-7]] = _MacroArgExpr(value) def append(self, tag): if isinstance(tag, Setarg): self.arg_dict[tag.get_attrib('name')] = tag else: EnclosingTag.append(self, tag) def to_html(self, ctx): macro = ctx.get_macro(self.get_attrib('name')) if not macro: self.raise_error('undefined macro "%s"' % self.get_attrib('name')) ctx.push_macro_args(self.arg_dict, macro.arg_defaults) macro.expand_html(ctx) ctx.pop_macro_args() class Require(EmptyTag): name = 'al-require' def __init__(self, ctx, filename, line_num, attribs): EmptyTag.__init__(self, ctx, filename, line_num, attribs) self.assert_only_attrib('version', 'feature') version = self.get_attrib('version') if version: try: version = int(version) except ValueError: self.raise_error('version attribute must be an integer') if version > template_version: self.raise_error('template requires features from templating version %d, this is version %d' % (version, template_version)) feature = self.get_attrib('feature') if feature: for feature in feature.split(','): feature = feature.strip() if feature not in template_features: self.raise_error('template requires feature %r - not available' % feature) tags = (Input, Href, Img, Form, Option, Select, TextArea, Flush, Comment, Include, Elif, Else, If, Exec, For, Tree, Value, Item, Lookup, Setdefault, Usearg, Macro, Setarg, Expand, Require) albatross-1.36/albatross/common.py0000644000175000017500000000560610575672025017165 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # Stuff shared by all albatross modules - must not import any other # albatross modules, or cycles will result. # Base exception class used by all Albatross exceptions class AlbatrossError(Exception): pass # Generic causes of exceptions: class UserError(AlbatrossError): pass class ApplicationError(AlbatrossError): pass class InternalError(AlbatrossError): pass class ServerError(AlbatrossError): pass # Specific exceptions: class SecurityError(UserError): pass class TemplateLoadError(ApplicationError): pass class SessionExpired(UserError): pass # HTTP/1.0 Status definitions from RFC1945 HTTP_OK = 200 HTTP_CREATED = 201 HTTP_ACCEPTED = 202 HTTP_NO_CONTENT = 204 HTTP_MULTIPLE_CHOICES = 300 HTTP_MOVED_PERMANENTLY = 301 HTTP_MOVED_TEMPORARILY = 302 HTTP_NOT_MODIFIED = 304 HTTP_BAD_REQUEST = 400 HTTP_UNAUTHORIZED = 401 HTTP_FORBIDDEN = 403 HTTP_NOT_FOUND = 404 HTTP_INTERNAL_SERVER_ERROR = 500 HTTP_NOT_IMPLEMENTED = 501 HTTP_BAD_GATEWAY = 502 HTTP_SERVICE_UNAVAILABLE = 503 def http_status_informational(status): return str(status).startswith('1') def http_status_successful(status): return str(status).startswith('2') def http_status_redirection(status): return str(status).startswith('3') def http_status_client_error(status): return str(status).startswith('4') def http_status_server_error(status): return str(status).startswith('5') http_status_phrases = { HTTP_OK : "Ok", HTTP_CREATED : "Created", HTTP_ACCEPTED : "Accepted", HTTP_NO_CONTENT : "No Content", HTTP_MULTIPLE_CHOICES : "Multiple Choices", HTTP_MOVED_PERMANENTLY : "Moved Permanently", HTTP_MOVED_TEMPORARILY : "Moved Temporarily", HTTP_NOT_MODIFIED : "Not Modified", HTTP_BAD_REQUEST : "Bad Request", HTTP_UNAUTHORIZED : "Unauthorized", HTTP_FORBIDDEN : "Forbidden", HTTP_NOT_FOUND : "Not Found", HTTP_INTERNAL_SERVER_ERROR : "Internal Server Error", HTTP_NOT_IMPLEMENTED : "Not Implemented", HTTP_BAD_GATEWAY : "Bad Gateway", HTTP_SERVICE_UNAVAILABLE : "Service Unavailable", } def http_status_string(status): try: return '%s %s' % (status, http_status_phrases[status]) except KeyError: return '%s' % status def commasplit(text): """ split a string into comma separated fields """ return [f.strip() for f in text.split(',')] albatross-1.36/albatross/app.py0000644000175000017500000005021410446670652016451 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # Application support which all application types inherit from import os import sys import imp import urlparse from albatross.context import NamespaceMixin, ExecuteMixin, ResourceMixin,\ NameRecorderMixin, HiddenFieldSessionMixin, PickleSignMixin,\ CachingTemplateLoaderMixin, _caller_globals from albatross.session import SessionServerContextMixin,\ SessionServerAppMixin from albatross import tags from albatross.common import * class Redirect: def __init__(self, loc): self.loc = loc class ResponseMixin: ''' Maintain an ordered, case-insensitive dictionary of headers. ''' def __init__(self): self.__sent_headers = False self.__seqnum = 0 self.__headers = {} self.set_header('Pragma', 'no-cache') self.set_header('Cache-Control', 'no-cache') self.set_header('Content-Type', 'text/html') def get_header(self, name): try: seqnum, name, value = self.__headers[name.lower()] except KeyError: pass else: return value def set_header(self, name, value): """ Sets a header to the given value, overriding any previous value. """ if self.__sent_headers: raise ApplicationError('headers have already been sent') name_lower = name.lower() try: seqnum, name, old_value = self.__headers[name_lower] except KeyError: self.__headers[name_lower] = (self.__seqnum, name, [value]) self.__seqnum += 1 else: self.__headers[name_lower] = (seqnum, name, [value]) def add_header(self, name, value): """ Sets the value of the name header to value in the internal store, appending the new header immediately after any existing headers of the same name. """ if self.__sent_headers: raise ApplicationError('headers have already been sent') name_lower = name.lower() try: seqnum, name, old_value = self.__headers[name_lower] except KeyError: self.__headers[name_lower] = (self.__seqnum, name, [value]) self.__seqnum += self.__seqnum else: old_value.append(value) self.__headers[name_lower] = (seqnum, name, old_value) def del_header(self, name): if self.__sent_headers: raise ApplicationError('headers have already been sent') try: del self.__headers[name.lower()] except KeyError: pass def write_headers(self): if self.__sent_headers: raise ApplicationError('headers have already been sent') headers = self.__headers.values() headers.sort() for seqnum, name, values in headers: for value in values: self.request.write_header(name, value) self.request.end_headers() self.__sent_headers = True def send_content(self, data): if not self.__sent_headers: self.write_headers() self.request.write_content(data) def send_redirect(self, loc): cookies = self.get_header('Set-Cookie') if cookies: for cookie in cookies: self.request.write_header('Set-Cookie', cookie) return self.request.redirect(loc) # With an application object the registration of resources is managed # by the application. class AppContext(NamespaceMixin, ResponseMixin, ExecuteMixin): def __init__(self, app): NamespaceMixin.__init__(self) ResponseMixin.__init__(self) ExecuteMixin.__init__(self) self.locals.__page__ = None self.locals.__pages__ = [] self.app = app self.__current_uri = None # ---------------------------------------------------- # Serve all requests for resources via the application def get_macro(self, name): return self.app.get_macro(name) def register_macro(self, name, macro): return self.app.register_macro(name, macro) def get_lookup(self, name): return self.app.get_lookup(name) def register_lookup(self, name, lookup): return self.app.register_lookup(name, lookup) def get_tagclass(self, name): return self.app.get_tagclass(name) # ---------------------------------------------------- # Serve all requests for templates via the application def load_template(self, name): return self.app.load_template(name) def load_template_once(self, name): return self.app.load_template_once(name) def run_template(self, name): templ = self.app.load_template(name) self.set_globals(_caller_globals('run_template')) templ.to_html(self) def run_template_once(self, name): templ = self.app.load_template_once(name) if templ: self.set_globals(_caller_globals('run_template_once')) templ.to_html(self) # ---------------------------------------------------- # Introduce some application specific methods def clear_locals(self): page, pages = self.locals.__page__, self.locals.__pages__ NamespaceMixin.clear_locals(self) self.locals.__page__, self.locals.__pages__ = page, pages def __enter_page(self, name, args): self.locals.__page__ = name self.app.load_page(self) self.app.page_enter(self, args) def __leave_page(self, new_page_name): if self.locals.__page__ \ and self.locals.__page__ != new_page_name: self.app.load_page(self) self.app.page_leave(self) def set_page(self, name, *args): self.__leave_page(name) self.__enter_page(name, args) def push_page(self, name, *args): if self.locals.__page__ \ and self.locals.__page__ != name: self.locals.__pages__.append(self.locals.__page__) self.__enter_page(name, args) def pop_page(self, target_page = None): while True: try: name = self.locals.__pages__.pop() except IndexError: raise ApplicationError('pop_page: page stack is empty') self.__leave_page(name) self.locals.__page__ = name self.app.load_page(self) if target_page is None or target_page == name: break def set_request(self, req): self.request = req def req_equals(self, name): try: if self.request.field_value(name): return True except KeyError: try: if self.request.field_value(name + '.x'): return True except KeyError: pass return False def base_url(self): return self.app.base_url() def parsed_request_uri(self): if self.__current_uri is None: self.__current_uri = urlparse.urlparse(self.request.get_uri())[:3] return self.__current_uri def current_url(self): return self.parsed_request_uri()[2] def absolute_base_url(self): this_path = self.current_url() base_path = urlparse.urlparse(self.base_url())[2] pos = this_path.find(base_path) if pos < 0: raise ApplicationError('base_url "%s" not found in request url "%s"' % (base_path, this_path)) return this_path[:pos + len(base_path)] def redirect_url(self, loc): scheme, netloc, path = urlparse.urlparse(loc)[:3] if scheme or netloc: return loc # Already absolute scheme, netloc, path = self.parsed_request_uri() if loc.startswith('/'): return urlparse.urlunparse((scheme, netloc, loc, '', '', '')) new_base = self.absolute_base_url() if not new_base.endswith('/'): new_base = new_base + '/' return urlparse.urlunparse((scheme, netloc, new_base + loc, '', '', '')) def redirect(self, loc): raise Redirect(self.redirect_url(loc)) # Basic application processing sequence is implemented in a class # which is then subclassed in cgiapp and modapp. class Application(ResourceMixin): def __init__(self, base_url): ResourceMixin.__init__(self) self.register_tagclasses(*tags.tags) # Use a base_url of '/' if not provided if base_url: self.__base_url = base_url else: self.__base_url = '/' def run(self, req): '''Process a single browser request ''' ctx = None try: ctx = self.create_context() ctx.set_request(req) self.load_session(ctx) self.load_page(ctx) if self.validate_request(ctx): self.merge_request(ctx) self.process_request(ctx) self.display_response(ctx) self.save_session(ctx) ctx.flush_content() except Redirect, e: self.save_session(ctx) return ctx.send_redirect(e.loc) except: self.handle_exception(ctx, req) return req.return_code() def format_exception(self): import traceback import linecache etype, value, tb = sys.exc_info() try: pyexc = ''.join(traceback.format_exception(etype, value, tb)) lines = [] lines.append('Template traceback (most recent call last):') for tup in self.template_traceback(tb): lines.append(' File "%s", line %s, in %s' % tup) line = linecache.getline(tup[0], tup[1]).rstrip() if line: lines.append(line) htmlexc = '\n'.join(lines) finally: del etype, value, tb return pyexc, htmlexc def handle_exception(self, ctx, req): pyexc, htmlexc = map(tags.escape, self.format_exception()) req.set_status(HTTP_INTERNAL_SERVER_ERROR) req.write_header('Content-Type', 'text/html') req.end_headers() try: tmp_ctx = self.create_context() tmp_ctx.set_request(req) tmp_ctx.locals.python_exc = pyexc tmp_ctx.locals.html_exc = htmlexc templ = self.load_template('traceback.html') templ.to_html(tmp_ctx) tmp_ctx.flush_content() except: req.write_content('
')
            req.write_content(htmlexc)
            req.write_content('\n\n')
            req.write_content(pyexc)
            req.write_content('
') if ctx: self.remove_session(ctx) def template_traceback(self, tb): tmpl_frames = [] prev = None while tb is not None: f = tb.tb_frame if 'self' in f.f_locals: obj = f.f_locals['self'] if hasattr(obj, 'line_num'): tag = (obj.filename, obj.line_num, obj.name) if tag != prev: tmpl_frames.append(tag) prev = tag tb = tb.tb_next return tmpl_frames def load_session(self, ctx): ctx.load_session() def save_session(self, ctx): ctx.save_session() def remove_session(self, ctx): ctx.remove_session() def validate_request(self, ctx): return True def base_url(self): return self.__base_url def merge_request(self, ctx): ctx.merge_request() def pickle_sign(self, text): return '' def pickle_unsign(self, text): return '' # ------------------------------------------------------------------ # Page processing handlers # ------------------------------------------------------------------ # A caching page module loader which only reloads the page module it # has been modified. class PageModuleMixin: '''Caching module loader ''' mod_holder_name = '__alpage__' def __init__(self, base_dir, start_page): self.__base_dir = base_dir self.__start_page = start_page def module_path(self): return self.__base_dir def start_page(self): return self.__start_page def load_page(self, ctx): if ctx.locals.__page__: self.load_page_module(ctx, ctx.locals.__page__) else: ctx.set_page(self.start_page()) # recursively calls load_page def is_page_module(self, name): return name.startswith(self.mod_holder_name) def load_page_module(self, ctx, name): mod_path = name.replace('/', '.').split('.') if mod_path[0] != self.mod_holder_name: mod_path.insert(0, self.mod_holder_name) # Create module path out of dummy modules, if needed for i in range(len(mod_path) - 1, 0, -1): mod_name = '.'.join(mod_path[:i]) try: sys.modules[mod_name] break except KeyError: sys.modules[mod_name] = imp.new_module(mod_name) # Now see if it's already loaded mod_name = '.'.join(mod_path) try: module = sys.modules[mod_name] except KeyError: # Nope pass else: # The name exists, but is it a dummy or the real thing? if hasattr(module, '__file__'): # Real? Then return it ctx.page = module return module # Dummy? but we want the real thing... so continue mod_dir = os.path.join(self.__base_dir, *mod_path[1:-1]) try: f, filepath, desc = imp.find_module(mod_path[-1], [mod_dir]) except ImportError, e: raise ApplicationError('%s (in %s)' % (e, mod_dir)) try: module = imp.load_module(mod_name, f, filepath, desc) finally: if f: f.close() if not (hasattr(module, 'page_enter') or hasattr(module, 'page_leave') or hasattr(module, 'page_process') or hasattr(module, 'page_display')): raise ApplicationError('module "%s" does not define one of page_enter, page_leave, page_process or page_display' % (name)) sys.modules[mod_name] = ctx.page = module return module def page_enter(self, ctx, args): func = getattr(ctx.page, 'page_enter', None) if func is not None: func(ctx, *args) def page_leave(self, ctx): func = getattr(ctx.page, 'page_leave', None) if func is not None: func(ctx) def process_request(self, ctx): func = getattr(ctx.page, 'page_process', None) if func is not None: func(ctx) def display_response(self, ctx): func = getattr(ctx.page, 'page_display', None) if func is not None: func(ctx) # A page loader in which pages are implemented in objects. class PageObjectMixin: def __init__(self, start_page): self.__start_page = start_page self.__page_objects = {} def is_page_module(self, name): return False def start_page(self): return self.__start_page def register_page(self, name, obj): self.__page_objects[name] = obj def load_page(self, ctx): if ctx.locals.__page__: ctx.page = ctx.locals.__page__ else: ctx.set_page(self.start_page()) # recursively calls load_page def page_enter(self, ctx, args): obj = self.__page_objects[ctx.page] func = getattr(obj, 'page_enter', None) if func is not None: func(ctx, *args) def page_leave(self, ctx): obj = self.__page_objects[ctx.page] func = getattr(obj, 'page_leave', None) if func is not None: func(ctx) def process_request(self, ctx): obj = self.__page_objects[ctx.page] func = getattr(obj, 'page_process', None) if func is not None: func(ctx) def display_response(self, ctx): obj = self.__page_objects[ctx.page] func = getattr(obj, 'page_display', None) if func is not None: func(ctx) # ------------------------------------------------------------------ # Prepackaged application contexts # ------------------------------------------------------------------ # Simple application context simply stores field names in form to # allow empty fields to be set to None. Also saves state in hidden # form fields. class SimpleAppContext(AppContext, NameRecorderMixin, HiddenFieldSessionMixin): def __init__(self, app): AppContext.__init__(self, app) NameRecorderMixin.__init__(self) HiddenFieldSessionMixin.__init__(self) def form_close(self): use_multipart_enc = NameRecorderMixin.form_close(self) HiddenFieldSessionMixin.form_close(self) return use_multipart_enc # Session state is stored server side class SessionAppContext(AppContext, NameRecorderMixin, SessionServerContextMixin): def __init__(self, app): AppContext.__init__(self, app) NameRecorderMixin.__init__(self) SessionServerContextMixin.__init__(self) # ------------------------------------------------------------------ # Prepackaged application objects # ------------------------------------------------------------------ # Page processing is implemented in methods of page objects class SimpleApp(PickleSignMixin, Application, CachingTemplateLoaderMixin, PageObjectMixin): def __init__(self, base_url, template_path, start_page, secret): Application.__init__(self, base_url) PickleSignMixin.__init__(self, secret) CachingTemplateLoaderMixin.__init__(self, template_path) PageObjectMixin.__init__(self, start_page) def create_context(self): return SimpleAppContext(self) # Same as SimpleApp with server-side session support class SimpleSessionApp(PickleSignMixin, Application, CachingTemplateLoaderMixin, PageObjectMixin, SessionServerAppMixin): def __init__(self, base_url, template_path, start_page, secret, session_appid, session_server = 'localhost', server_port = 34343, session_age = 1800): Application.__init__(self, base_url) PickleSignMixin.__init__(self, secret) CachingTemplateLoaderMixin.__init__(self, template_path) PageObjectMixin.__init__(self, start_page) SessionServerAppMixin.__init__(self, session_appid, session_server, server_port, session_age) def create_context(self): return SessionAppContext(self) # Page processing is implemented in functions of page modules class ModularApp(PickleSignMixin, Application, CachingTemplateLoaderMixin, PageModuleMixin): def __init__(self, base_url, module_path, template_path, start_page, secret): Application.__init__(self, base_url) PickleSignMixin.__init__(self, secret) CachingTemplateLoaderMixin.__init__(self, template_path) PageModuleMixin.__init__(self, module_path, start_page) def create_context(self): return SimpleAppContext(self) # Same as ModularApp with server-side session support class ModularSessionApp(PickleSignMixin, Application, CachingTemplateLoaderMixin, PageModuleMixin, SessionServerAppMixin): def __init__(self, base_url, module_path, template_path, start_page, secret, session_appid, session_server = 'localhost', server_port = 34343, session_age = 1800): Application.__init__(self, base_url) PickleSignMixin.__init__(self, secret) CachingTemplateLoaderMixin.__init__(self, template_path) PageModuleMixin.__init__(self, module_path, start_page) SessionServerAppMixin.__init__(self, session_appid, session_server, server_port, session_age) def create_context(self): return SessionAppContext(self) albatross-1.36/albatross/sessionfile.py0000644000175000017500000001552210374300002020172 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # Note: session_dir should not be a publically readable directory, or # local users will be able to steal sessions. import os import stat import sys import binascii import random import errno import struct import os.path from albatross.app import AppContext, Application, PageObjectMixin,\ PageModuleMixin from albatross.context import SessionBase, NameRecorderMixin,\ PickleSignMixin, CachingTemplateLoaderMixin from albatross.randompage import RandomPageModuleMixin from albatross.session import SessionCookieMixin from albatross.common import * # File based session recording class SessionFileContextMixin(SessionCookieMixin, SessionBase): def __init__(self): SessionBase.__init__(self) self.__sesid = None def sesid(self): return self.__sesid def load_session(self): sesid = self._get_sesid_from_cookie() text = None if sesid: sesid = os.path.basename(sesid) # Safety text = self.app.get_session(sesid) if not text: if self.request.get_method().upper() != 'GET': raise SessionExpired('Session expired or browser does not support cookies') sesid = self.app.new_session() self.__sesid = sesid if text: try: self.decode_session(text) except: self.app.del_session(sesid) raise self._set_sesid_cookie(self.__sesid) def save_session(self): if self.should_save_session() and self.__sesid is not None: text = self.encode_session() self.app.put_session(self.__sesid, text) def remove_session(self): SessionBase.remove_session(self) if self.__sesid is not None: self.app.del_session(self.__sesid) sesid = self.app.new_session() self.__sesid = sesid self._set_sesid_cookie(self.__sesid) class SessionFileAppMixin: def __init__(self, session_appid, session_dir, session_age = 1800): self.__appid = session_appid self.__session_dir = session_dir self.__age = session_age def ses_appid(self): return self.__appid def ses_age(self): return self.__age def get_session(self, sesid): text = None try: text = open(self._ses_filename(sesid), "rb").read() except IOError, (eno, estr): if eno != errno.ENOENT: self.del_session(sesid) raise ServerError("read session file: %s: %s" % (self._ses_filename(sesid), estr)) return text def new_session(self): while 1: try: text = open('/dev/urandom').read(8) except (OSError, IOError): text = struct.pack('d', random.random()) sesid = binascii.hexlify(text) try: fd = os.open(self._ses_filename(sesid), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0700) except OSError, (eno, estr): if eno == errno.ENOENT: try: os.makedirs(self.__session_dir, 0700) except OSError, (eno, estr): raise ServerError("create session dir: %s: %s" % (self.__session_dir, estr)) continue if eno != errno.EEXIST: raise ServerError("new_session: %s: %s" % (self._ses_filename(sesid), estr)) continue os.close(fd) return sesid def put_session(self, sesid, text): try: file = open(self._ses_filename(sesid), "wb") file.write(text) file.close() except IOError, (eno, estr): raise ServerError("write session file: %s: %s" % (self._ses_filename(sesid), estr)) def del_session(self, sesid): try: os.remove(self._ses_filename(sesid)) except OSError: pass def _ses_filename(self, sesid): if os.path.dirname(sesid) != '': raise SecurityError("bad session id: %s" % (sesid)) return os.path.join(self.__session_dir, sesid + '.als') class SessionFileAppContext(AppContext, NameRecorderMixin, SessionFileContextMixin): def __init__(self, app): AppContext.__init__(self, app) NameRecorderMixin.__init__(self) SessionFileContextMixin.__init__(self) class SimpleSessionFileApp(PickleSignMixin, Application, CachingTemplateLoaderMixin, PageObjectMixin, SessionFileAppMixin): def __init__(self, base_url, template_path, start_page, secret, session_appid, session_dir): Application.__init__(self, base_url) PickleSignMixin.__init__(self, secret) CachingTemplateLoaderMixin.__init__(self, template_path) PageObjectMixin.__init__(self, start_page) SessionFileAppMixin.__init__(self, session_appid, session_dir) def create_context(self): return SessionFileAppContext(self) class ModularSessionFileApp(PickleSignMixin, Application, CachingTemplateLoaderMixin, PageModuleMixin, SessionFileAppMixin): def __init__(self, base_url, module_path, template_path, start_page, secret, session_appid, session_dir): Application.__init__(self, base_url) PickleSignMixin.__init__(self, secret) CachingTemplateLoaderMixin.__init__(self, template_path) PageModuleMixin.__init__(self, module_path, start_page) SessionFileAppMixin.__init__(self, session_appid, session_dir) def create_context(self): return SessionFileAppContext(self) class RandomModularSessionFileApp(PickleSignMixin, Application, CachingTemplateLoaderMixin, RandomPageModuleMixin, SessionFileAppMixin): def __init__(self, base_url, page_path, start_page, secret, session_appid, session_dir): Application.__init__(self, base_url) PickleSignMixin.__init__(self, secret) CachingTemplateLoaderMixin.__init__(self, page_path) RandomPageModuleMixin.__init__(self, page_path, start_page) SessionFileAppMixin.__init__(self, session_appid, session_dir) def create_context(self): return SessionFileAppContext(self) albatross-1.36/albatross/branchingsession.py0000644000175000017500000000612610300030143021201 0ustar andrewmandrewm# # Copyright 2004 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # import base64 try: import zlib have_zlib = 1 except ImportError: have_zlib = 0 from albatross.context import SessionBase, NameRecorderMixin from albatross.app import AppContext from albatross.common import * class BranchingSessionMixin(SessionBase): def __init__(self): SessionBase.__init__(self) self.__sesid = None self.__txid = None def sesid(self): if self.__sesid is None: self.__sesid = self.app.new_session() return self.__sesid def txid(self): if self.__txid is None: self.__txid = self.app.new_session() return self.__txid def _get_id_field(self, field): if not self.request.has_field(field): return id = self.request.field_value(field) if len(id) < 8 or len(id) > 256 or not id.isalnum(): raise SecurityError('Hostile session id in %s field: %r'% field, id) return id def load_session(self): self.__txid = None sesid = self._get_id_field('__albsessid__') txid = self._get_id_field('__albtxid__') if sesid and txid: ses = self.app.get_session(sesid) text = self.app.get_session(txid) if not ses or not text: if self.request.get_method().upper() != 'GET': raise SessionExpired('Session expired') self.__sesid = sesid text = base64.decodestring(text) try: if have_zlib: text = zlib.decompress(text) self.decode_session(text) except: self.app.del_session(sesid) raise else: self.__sesid = self.sesid() def save_session(self): if self.should_save_session(): text = self.encode_session() if have_zlib: text = zlib.compress(text) text = base64.encodestring(text) self.app.put_session(self.txid(), text) self.app.put_session(self.sesid(), 'OK\n') def remove_session(self): SessionBase.remove_session(self) if self.__sesid is not None: self.app.del_session(self.__sesid) self.__sesid = None def form_close(self): if self.should_save_session(): self.write_content('
\n \n \n
\n' % (self.sesid(), self.txid())) class BranchingSessionContext(AppContext, NameRecorderMixin, BranchingSessionMixin): def __init__(self, app): AppContext.__init__(self, app) NameRecorderMixin.__init__(self) BranchingSessionMixin.__init__(self) def form_close(self): use_multipart_enc = NameRecorderMixin.form_close(self) BranchingSessionMixin.form_close(self) return use_multipart_enc albatross-1.36/albatross/fcgiapp.py0000644000175000017500000002772710576144061017311 0ustar andrewmandrewm# # Copyright 2006 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # XXX TODO # Handle SIGPIPE (client abort) and SIGUSR1 (graceful)? # Respond to FCGI_GET_VALUES (can't test) # should we be calling poll on every stdout or stderr write? # Handle FCGI_ABORT_REQUEST # SPEC - http://www.fastcgi.com/devkit/doc/fcgi-spec.html # Apache mod_fcgi - http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html # App - http://localhost/cgi-bin/fcgitest/test.py import sys import os import cgi import errno import socket import select import struct from cStringIO import StringIO from albatross import cgiapp class FCGIError(Exception): pass class NotFCGI(FCGIError): pass # I think the FastCGI spec made a mistake attempting to multiplex # stderr over the socket: if you have a problem while starting up, # error output goes into a void, making FastCGI very hard debug. Apache # allows us to continue to write to stderr - if your web server # doesn't, then change this variable to True multiplex_stderr = False # FastCGI Constants # http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S8 FCGI_LISTENSOCK_FILENO = 0 FCGI_HEADER_LEN = 8 FCGI_VERSION = 1 FCGI_MAX_DATA = (1<<16)-1 # 16 bit length field # FCGI message types: FCGI_BEGIN_REQUEST = 1 FCGI_ABORT_REQUEST = 2 FCGI_END_REQUEST = 3 FCGI_PARAMS = 4 FCGI_STDIN = 5 FCGI_STDOUT = 6 FCGI_STDERR = 7 FCGI_DATA = 8 FCGI_GET_VALUES = 9 FCGI_GET_VALUES_RESULT = 10 FCGI_UNKNOWN_TYPE = 11 FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE # BEGIN_REQUEST flags FCGI_KEEP_CONN = 1 # BEGIN_REQUEST roles FCGI_RESPONDER = 1 FCGI_AUTHORIZER = 2 FCGI_FILTER = 3 # END_REQUEST status FCGI_REQUEST_COMPLETE = 0 FCGI_CANT_MPX_CONN = 1 FCGI_OVERLOADED = 2 FCGI_UNKNOWN_ROLE = 3 # This is not used, although it's retained to aid future debugging - some # fastcgi environments do not have a usable fd 2 (stderr). def redir_stderr(): fd = os.open('/tmp/debug', os.O_WRONLY|os.O_APPEND|os.O_CREAT, 0666) if fd != sys.stderr.fileno(): os.dup2(fd, sys.stderr.fileno()) os.close(fd) # listen_sock = -1 is_fcgi = True # Until we know otherwise # If running via TCP, contains a list of acceptable source IP address. web_server_addrs = None def _is_listening_socket(sock): # The FCGI socket is bound and listening, but not connected - if we're # accidently started as CGI, then we get a connected socket or a pipe # instead. try: sock.getpeername() except socket.error, (eno, errmsg): return eno == errno.ENOTCONN else: return False def fcgi_init(): global listen_sock, web_server_addrs, is_fcgi if not is_fcgi: raise NotFCGI try: web_server_addrs = os.environ['FCGI_WEB_SERVER_ADDRS'].split(',') except KeyError: pass null = os.open('/dev/null', os.O_RDWR) try: # socket.fromfd dups the socket (sigh) try: listen_sock = socket.fromfd(FCGI_LISTENSOCK_FILENO, socket.AF_UNIX, socket.SOCK_STREAM) except socket.error: raise NotFCGI if not _is_listening_socket(listen_sock): listen_sock.close() listen_sock = -1 is_fcgi = False raise NotFCGI # the fcgi spec says we're to close stdin, stdout and stderr, but 3rd # party code often expects fd 0, 1 and 2 to be open, so we point 0 and # 1 (stdin and stdout) at /dev/null. Apache's mod_fcgi deviates from # the spec by forwarding writes to fd 2 (stderr) to the web server # error log - this is handy, so we leave fd 2 alone. os.dup2(null, 0) os.dup2(null, 1) finally: os.close(null) def is_fcgi(): if listen_sock < 0: try: fcgi_init() except NotFCGI: return False return True class FCGIFileOut: def __init__(self, protocol, f_type): self.__protocol = protocol self.__type = f_type def write(self, data): while data: self.__protocol.send(self.__type, data[:FCGI_MAX_DATA]) data = data[FCGI_MAX_DATA:] self.__protocol.poll() def flush(self): self.__protocol.flush() class FCGIProtocol: hdrfmt = '>BBHHBB' assert struct.calcsize(hdrfmt) == FCGI_HEADER_LEN def __init__(self): if listen_sock < 0: fcgi_init() self.sock, addr = listen_sock.accept() self.sock.setblocking(0) self.stdin = [] self.params = {} self.recv_buf = '' self.send_buf = '' self.in_fds = [self.sock] self.out_fds = [] self.current_requestId = 0 self.server_request_complete = False if web_server_addrs and addr not in web_server_addrs: raise FCGIError('Received request from bad IP address: %s ' '(expected %s)' % (addr, ', or '.join(web_server_addrs))) sys.stdout = FCGIFileOut(self, FCGI_STDOUT) if multiplex_stderr: sys.stderr = FCGIFileOut(self, FCGI_STDERR) while not self.server_request_complete: self.poll() def getFieldStorage(self): return cgi.FieldStorage(fp=StringIO(''.join(self.stdin)), environ=self.params, keep_blank_values=1) def flush(self): while self.send_buf: self.poll() def close(self): self.recv_buf = '' self.send_buf = '' self.in_fds = [] self.out_fds = [] if self.sock: self.sock.close() self.sock = None def end(self): self.send_end_request(FCGI_REQUEST_COMPLETE, 0) self.flush() self.close() def send(self, f_type, content, f_requestId=None): if self.sock: if f_requestId is None: f_requestId = self.current_requestId f_length = len(content) assert f_length <= FCGI_MAX_DATA hdr = struct.pack(self.hdrfmt, FCGI_VERSION, f_type, f_requestId, f_length, 0, 0) self.send_buf = ''.join((self.send_buf, hdr, content)) if not self.out_fds: self.out_fds = [self.sock] def send_unknown_type(self, f_type): self.send(FCGI_UNKNOWN_TYPE, struct.pack('>B7x', f_type), 0) def send_end_request(self, f_status, app_status, f_requestId=None): data = struct.pack('>LB3x', f_status, app_status) self.send(FCGI_END_REQUEST, data, f_requestId) def process_incoming(self): # Digest any complete packets that have been received while len(self.recv_buf) >= FCGI_HEADER_LEN: f_version, f_type, f_requestId, f_contentLength, \ f_paddingLength, f_reserved = \ struct.unpack(self.hdrfmt, self.recv_buf[:FCGI_HEADER_LEN]) if f_version != FCGI_VERSION: raise FCGIError('Received message version %d, expect %d' % (f_version, FCGI_VERSION)) content_end = FCGI_HEADER_LEN + f_contentLength padding_end = content_end + f_paddingLength if len(self.recv_buf) < padding_end: break content = self.recv_buf[FCGI_HEADER_LEN:content_end] self.recv_buf = self.recv_buf[padding_end:] try: type_handler = self.type_dispatch[f_type] except IndexError: type_handler = None if type_handler is None: self.send_unknown_type(f_type) else: if f_type == FCGI_BEGIN_REQUEST: if self.current_requestId: self.send_end_request(FCGI_CANT_MPX_CONN, 0, f_requestId=f_requestId) continue else: self.current_requestId = f_requestId elif f_requestId and f_requestId != self.current_requestId: raise FCGIError('unsupported concurrent request received') type_handler(self, content) # Poll the fcgi socket, process any incoming messages, and send any # outgoing data. def poll(self): if self.sock: r, w, e = select.select(self.in_fds, self.out_fds, []) # Read any new data if r: try: buf = self.sock.recv(16384) except socket.error, (eno, estr): if eno != errno.ECONNRESET: raise self.close() else: if not buf: self.close() else: self.recv_buf += buf self.process_incoming() # Send as much data as we can if w: if self.send_buf: try: l = self.sock.send(self.send_buf) except socket.error, (eno, estr): if eno != errno.ECONNRESET: raise self.close() else: self.send_buf = self.send_buf[l:] if not self.send_buf: self.out_fds = [] def nvpair(self, offs, buf): # FCGI encodes name-value pairs as two lengths, then two strings # of the specified lengths. The lengths are encoded either as a single # byte less than 127, or, if the high bit is set, as a 4 byte big # endian. name_len = ord(buf[offs]) if name_len & 0x80: name_len = struct.unpack('>L', buf[offs:offs+4])[0] & 0x7fffffff; offs += 4 else: offs += 1 value_len = ord(buf[offs]) if value_len & 0x80: value_len = struct.unpack('>L', buf[offs:offs+4])[0] & 0x7fffffff; offs += 4 else: offs += 1 return offs+name_len+value_len, \ buf[offs:offs+name_len], \ buf[offs+name_len:offs+name_len+value_len] type_dispatch = [None] * (FCGI_MAXTYPE+1) def fcgi_begin_request(self, content): role, flags = struct.unpack('>HB', content[:3]) if role != FCGI_RESPONDER: self.send_end_request(FCGI_UNKNOWN_ROLE,0) type_dispatch[FCGI_BEGIN_REQUEST] = fcgi_begin_request def fcgi_abort_request(self, content): pass type_dispatch[FCGI_ABORT_REQUEST] = fcgi_abort_request def fcgi_end_request(self, content): pass type_dispatch[FCGI_END_REQUEST] = fcgi_end_request def fcgi_params(self, content): offs = 0 while offs < len(content): offs, name, value = self.nvpair(offs, content) self.params[name] = value type_dispatch[FCGI_PARAMS] = fcgi_params def fcgi_stdin(self, content): if content: self.stdin.append(content) else: self.server_request_complete = True type_dispatch[FCGI_STDIN] = fcgi_stdin def fcgi_get_values(self, content): raise NotImplementedError type_dispatch[FCGI_GET_VALUES] = fcgi_get_values if is_fcgi(): class Request(cgiapp.Request): def __init__(self, fields = None): self.__protocol = FCGIProtocol() if fields is None: fields = self.__protocol.getFieldStorage() cgiapp.Request.__init__(self, fields) def get_param(self, name, default=None): return self.__protocol.params.get(name, default) def return_code(self): self.__protocol.end() def running(): return True else: Request = cgiapp.Request is_running = True def running(): global is_running try: return is_running finally: is_running = False albatross-1.36/albatross/httpdapp.py0000644000175000017500000001251310025220163017472 0ustar andrewmandrewm# # Copyright 2003 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # AUTHOR(S) # Matt Goodall import BaseHTTPServer import cgi import mimetypes from cStringIO import StringIO from urlparse import urlsplit import os.path import shutil import time import stat from albatross.request import RequestBase from albatross.common import * RFC822_FORMAT = '%a, %d %b %Y %H:%M:%S +0000' class Request(RequestBase): """ BaseHTTPServer request adaptor We set up an environment to look like we've been invoked via a web server, and let the standard cgi module parse the request. """ def __init__(self, handler): self.__handler = handler self.__headers = [] environ = { 'REQUEST_METHOD': self.__handler.command, 'QUERY_STRING': urlsplit(self.__handler.requestline.split()[1])[3], } self.__handler.headers.setdefault('content-type', 'application/x-www-form-urlencoded') field_storage = cgi.FieldStorage(fp = self.__handler.rfile, headers = self.__handler.headers, environ = environ) RequestBase.__init__(self, field_storage) def get_header(self, name): return self.__handler.headers.get(name) def write_header(self, name, value): self.__headers.append((name, value)) def end_headers(self): self.__handler.send_response(self.status()) for name, value in self.__headers: self.__handler.send_header(name, value) self.__handler.end_headers() def get_uri(self): return self.__handler.path def get_method(self): return self.__handler.command def get_path_info(self): return self.__handler.path def get_servername(self): server_name = self.__handler.server.server_name port = self.__handler.server.server_port return '%s:%d' % (server_name, port) def write_content(self, data): self.__handler.wfile.write(data) def redirect(self, loc): self.set_status(HTTP_MOVED_PERMANENTLY) self.write_header('Location', loc) self.end_headers() class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """ Handle HTTP request, routing them through to the Albatross application """ def do_GET(self): # Try the static resources first if self.server.static_resources: for uri_path, fs_path in self.server.static_resources: if self.path.startswith(uri_path): path = self.path.split(uri_path)[1] self.send_file('%s%s' % (fs_path, path)) return self.process_request() def do_POST(self): self.process_request() def process_request(self): '''Process a request to the Albatross app''' self.server.app.run(Request(self)) def send_file(self, path): '''Send a file directly from the filesystem''' # Stat the file try: stats = os.stat(path) except OSError, e: self.send_error(HTTP_NOT_FOUND) self.end_headers() return # Get the last modified date in RFC822 form last_modified = time.gmtime(stats[stat.ST_MTIME]) last_modified = time.strftime(RFC822_FORMAT, last_modified) # See if a 304 (not modified) is enough ts = self.headers.get('If-Modified-Since') if ts and ts == last_modified: self.send_response(HTTP_NOT_MODIFIED) self.end_headers() return # Need to send the whole file f = None try: try: f = file(path, 'rb') self.send_response(HTTP_OK) self.send_header('Content-Type', mimetypes.guess_type(path)[0]) self.send_header('Content-Length', os.path.getsize(path)) self.send_header('Last-Modified', last_modified) self.end_headers() shutil.copyfileobj(f, self.wfile) except IOError, e: self.send_error(HTTP_FORBIDDEN) self.end_headers() finally: if f: f.close() class HTTPServer(BaseHTTPServer.HTTPServer): """ Simple, standalone HTTP server for Albatross applications. HTTPServer is a simple web server dedicated to processing requests and mapping that request through to Albatross. It can also serve static resources such as images, stylesheets etc directly from the filesystem. """ def __init__(self, app, port, static_resources=None): ''' Create an HTTP server for the Albatross application, app, and listen for requests on the specified port. static_resources is an optional list that maps uri paths to a corresponding filesystem path. Static resources are served directly from the filesystem without involving the Albatross application. ''' BaseHTTPServer.HTTPServer.__init__( self, ('', port), RequestHandler) self.app = app try: self.static_resources = static_resources.items() except AttributeError: self.static_resources = static_resources albatross-1.36/albatross/cgiapp.py0000644000175000017500000000267010372614314017126 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # import cgi import sys import os from albatross.common import * from albatross.request import RequestBase class Request(RequestBase): def __init__(self, fields = None): if fields is None: fields = cgi.FieldStorage() RequestBase.__init__(self, fields) def get_param(self, name, default=None): return os.environ.get(name, default) def get_uri(self): return self.get_param('REQUEST_URI') def get_method(self): return self.get_param('REQUEST_METHOD') def get_path_info(self): return self.get_param('PATH_INFO') def get_servername(self): return self.get_param('HTTP_HOST') def get_header(self, name): env_name = 'HTTP_' + name.upper().replace('-', '_') return self.get_param(env_name) def write_header(self, name, value): sys.stdout.write('%s: %s\n' % (name, value)) def end_headers(self): status = self.status() if status != HTTP_OK: self.write_header('Status', http_status_string(status)) sys.stdout.write('\n') def redirect(self, loc): self.set_status(HTTP_MOVED_PERMANENTLY) self.write_header('Location', loc) self.end_headers() def write_content(self, data): sys.stdout.write(data) sys.stdout.flush() albatross-1.36/samples/0000755000175000017500000000000010577422307014764 5ustar andrewmandrewmalbatross-1.36/samples/paginate/0000755000175000017500000000000010577422307016554 5ustar andrewmandrewmalbatross-1.36/samples/paginate/install.py0000644000175000017500000000027607433444167020606 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/paginate', cgi = 1) i.add_exec('paginate.py') i.add_html('paginate.html') i.install() albatross-1.36/samples/paginate/paginate.html0000644000175000017500000000071507407630714021236 0ustar andrewmandrewm Pagination Example

This is a list with pagination...

prev , next albatross-1.36/samples/paginate/.cvsignore0000644000175000017500000000000607476577160020564 0ustar andrewmandrewm*.pyc albatross-1.36/samples/paginate/paginate.py0000644000175000017500000000104107733001170020701 0ustar andrewmandrewm#!/usr/bin/python from albatross import SimpleSessionApp from albatross.cgiapp import Request class ListPage: def page_display(self, ctx): ctx.locals.data = range(100) ctx.run_template('paginate.html') app = SimpleSessionApp(base_url='paginate.py', template_path='.', start_page='start', secret='-=-secret-=-', session_appid='paginate') app.register_page('start', ListPage()) if __name__ == '__main__': app.run(Request()) albatross-1.36/samples/install.py0000644000175000017500000001727107500061500016776 0ustar andrewmandrewmimport os import sys import string import formatter import imp import struct import random import binascii import re class Error(Exception): pass def random_secret(): try: text = open('/dev/urandom').read(16) except: text = struct.pack('dd', random.random(), random.random()) return binascii.hexlify(text) def make_directory(name): parent, child = os.path.split(name) if child: make_directory(parent) try: os.stat(name)[0] return except (IOError, OSError): pass print 'creating directory %s' % name os.mkdir(name) class File: def __init__(self, name, dest): self.name = name self.dest = dest def dest_pathname(self): if self.dest[-1] == os.sep: dirname, name = os.path.split(self.name) return os.path.join(self.dest, name) return self.dest def dest_dirname(self): dirname, name = os.path.split(self.dest_pathname()) return dirname def copyfile(self, installer): data = open(self.name).read() if re.search(r'-=-secret-=-', data): secret = installer.get_secret() data = re.sub(r'-=-secret-=-', str(secret), data) data = re.sub(r'-=-install_dir-=-', self.dest_dirname(), data) dest = self.dest_pathname() open(dest, 'w').write(data) return dest class ExecFile(File): def install(self, installer): dest = self.copyfile(installer) print 'copy exec %s to %s' % (self.name, dest) if os.name == 'posix': os.chmod(dest, 0755) class ModuleFile(File): def install(self, installer): dest = self.copyfile(installer) print 'copy module %s to %s' % (self.name, dest) class HtmlFile(File): def install(self, installer): dest = self.copyfile(installer) print 'copy html %s to %s' % (self.name, dest) class DataFile(File): def install(self, installer): dest = self.copyfile(installer) print 'copy data %s to %s' % (self.name, dest) class ImageFile(File): def install(self, installer): dest = self.copyfile(installer) print 'copy image %s to %s' % (self.name, dest) class Config: def __init__(self): self.__has_changed = 0 def __str__(self): parts = [] for name in dir(self): if type(getattr(self, name)) is not type(''): continue parts.append('%s = "%s"' % (name, getattr(self, name))) parts.append('') return string.join(parts, '\n') def set_value(self, attr, value): if not hasattr(self, attr) or getattr(self, attr) != value: self.__has_changed = 1 setattr(self, attr, value) def has_changed(self): return self.__has_changed class Installer: def __init__(self, name, cgi = 0, apache = 0, doc = 0): msg = 'Installing %s' % name print msg print '=' * len(msg) self._name = name self._files = [] self.load_config() if cgi: cgibin_dir = self.get_config('cgibin_dir') self.destdir = os.path.join(cgibin_dir, self._name) elif apache: modpython_dir = self.get_config('modpython_dir') self.destdir = os.path.join(modpython_dir, self._name) elif doc: doc_dir = self.get_config('doc_dir') self.destdir = os.path.join(doc_dir, self._name) else: raise Error('must specify either cgi = 1 or apache = 1 or doc = 1') def __del__(self): self.save_config() def get_config(self, name): if hasattr(self.config, name): return getattr(self.config, name) w = formatter.DumbWriter() f = formatter.AbstractFormatter(w) f.add_flowing_data('Please enter a value for ') f.add_flowing_data(name) f.add_flowing_data(' which is ') if name[-7:] == '_secret': f.add_flowing_data('the secret key used to sign pickles' \ ' sent to the browser from the ') f.add_flowing_data(self._name) f.add_flowing_data(' program.') default = '' elif name == 'cgibin_dir': f.add_flowing_data('the top level directory where CGI Albatross') f.add_flowing_data(' samples will be installed.') default = '/usr/lib/cgi-bin' elif name == 'modpython_dir': f.add_flowing_data('the top level directory where mod_python') f.add_flowing_data(' Albatross samples will be installed.') default = '/var/www' elif name == 'doc_dir': f.add_flowing_data('the top level directory where ') f.add_flowing_data(' Albatross html documents will be installed.') default = '/var/www' f.end_paragraph(1) value = raw_input('Enter %s [%s]: ' % (name, default)) if not value: value = default self.config.set_value(name, value) return value def load_config(self): pathname = sys.modules['install'].__file__ dirname, name = os.path.split(pathname) self.cfgname = os.path.join(dirname, 'install.cfg') self.config = Config() try: execfile(self.cfgname, globals(), self.config.__dict__) except IOError: pass def save_config(self): if self.config.has_changed(): open(self.cfgname, 'w').write('%s' % self.config) def get_secret(self): dirname, name = os.path.split(self._name) attr = '%s_secret' % name secret = self.get_config(attr) if secret == '': self.config.set_value(attr, random_secret()) return getattr(self.config, attr) def add_exec(self, name, dest = ''): dest = os.path.join(self.destdir, dest) self._files.append(ExecFile(name, dest)) def add_module(self, name, dest = ''): dest = os.path.join(self.destdir, dest) self._files.append(ModuleFile(name, dest)) def add_html(self, name, dest = ''): dest = os.path.join(self.destdir, dest) self._files.append(HtmlFile(name, dest)) def add_data(self, name, dest = ''): dest = os.path.join(self.destdir, dest) self._files.append(DataFile(name, dest)) def add_image(self, name, dest = ''): dest = os.path.join(self.destdir, dest) self._files.append(ImageFile(name, dest)) def install(self): # Make sure all directories exist dirnames = {} for file in self._files: dirnames[file.dest_dirname()] = 1 dirnames = dirnames.keys() dirnames.sort() for name in dirnames: make_directory(name) # Install files for file in self._files: file.install(self) print class Catalog: def __init__(self): self.dirnames = [] def append(self, name): self.dirnames.append(name) def install_all(self): self.dirnames.sort() for dirname in self.dirnames: if not self.install(dirname): break def install(self, dirname): cwd = os.getcwd() os.chdir(dirname) result = os.system('python install.py') os.chdir(cwd) return result == 0 # Walk the samples directory finding information about samples def visit_dir(catalog, dirname, names): if dirname != rootdir and 'install.py' in names: catalog.append(dirname) if __name__ == '__main__': # Find the root of the samples directory rootdir, basename = os.path.split(sys.argv[0]) if not rootdir: rootdir = '.' catalog = Catalog() os.path.walk(rootdir, visit_dir, catalog) catalog.install_all() albatross-1.36/samples/popview3/0000755000175000017500000000000010577422307016540 5ustar andrewmandrewmalbatross-1.36/samples/popview3/install.py0000644000175000017500000000041207433444167020562 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/popview3', cgi = 1) i.add_exec('popview.py') i.add_module('popviewlib.py') i.add_html('login.html') i.add_html('list.html') i.add_html('detail.html') i.install() albatross-1.36/samples/popview3/login.html0000644000175000017500000000072007335507441020536 0ustar andrewmandrewm Please log in

Please log in

Username
Password
albatross-1.36/samples/popview3/list.html0000644000175000017500000000155307362241323020400 0ustar andrewmandrewm Message List Prev | Refresh | Next
To From Subject

albatross-1.36/samples/popview3/.cvsignore0000644000175000017500000000000607476577160020550 0ustar andrewmandrewm*.pyc albatross-1.36/samples/popview3/detail.html0000644000175000017500000000072507362241323020667 0ustar andrewmandrewm Message Detail Back
:

albatross-1.36/samples/popview3/popviewlib.py0000644000175000017500000000344707701441023021271 0ustar andrewmandrewmimport string import poplib pophost = 'pop' class Headers: def __init__(self): self.hdrs = {} def append(self, header): if not header: return parts = string.split(header, ': ', 1) name = string.capitalize(parts[0]) if len(parts) > 1: value = parts[1] else: value = '' curr = self.hdrs.get(name) if not curr: self.hdrs[name] = value return if type(curr) is type(''): curr = self.hdrs[name] = [curr] curr.append(value) def __getitem__(self, name): return self.hdrs.get(string.capitalize(name), '') class Msg: def __init__(self, mbox, msgnum): self.mbox = mbox self.msgnum = msgnum self.read_headers() def read_headers(self): res = self.mbox.top(self.msgnum, 0) hdrs = Headers() hdr = None for line in res[1]: if line and line[0] in string.whitespace: hdr = hdr + '\n' + line else: hdrs.append(hdr) hdr = line hdrs.append(hdr) self.hdrs = hdrs return hdrs def read_body(self): res = self.mbox.retr(self.msgnum) lines = res[1] for i in range(len(lines)): if not lines[i]: break self.body = string.join(lines[i:], '\n') return self.body class Mbox: def __init__(self, name, passwd): self.mbox = poplib.POP3(pophost) self.mbox.user(name) self.mbox.pass_(passwd) def __getitem__(self, i): try: return Msg(self.mbox, i + 1) except poplib.error_proto: raise IndexError def __len__(self): len, size = self.mbox.stat() return len albatross-1.36/samples/popview3/popview.py0000644000175000017500000000412407733001170020573 0ustar andrewmandrewm#!/usr/bin/python from albatross import SimpleSessionApp, SessionAppContext from albatross.cgiapp import Request import popviewlib class LoginPage: name = 'login' def page_process(self, ctx): if ctx.req_equals('login'): if ctx.locals.username and ctx.locals.passwd: try: ctx.open_mbox() ctx.add_session_vars('username', 'passwd') except poplib.error_proto: return ctx.set_page('list') def page_display(self, ctx): ctx.run_template('login.html') class ListPage: name = 'list' def page_process(self, ctx): if ctx.req_equals('msgnum'): ctx.set_page('detail') def page_display(self, ctx): ctx.open_mbox() ctx.run_template('list.html') class DetailPage: name = 'detail' def page_process(self, ctx): if ctx.req_equals('list'): ctx.set_page('list') def page_display(self, ctx): ctx.open_mbox() ctx.read_msg() ctx.run_template('detail.html') class AppContext(SessionAppContext): def open_mbox(self): if hasattr(self.locals, 'mbox'): return self.locals.mbox = popviewlib.Mbox(self.locals.username, self.locals.passwd) def read_msg(self): if hasattr(self.locals, 'msg'): return self.locals.msg = self.locals.mbox[int(self.locals.msgnum) - 1] self.locals.msg.read_body() class App(SimpleSessionApp): def __init__(self): SimpleSessionApp.__init__(self, base_url='popview.py', template_path='.', start_page='login', secret='-=-secret-=-', session_appid='popview3') for page_class in (LoginPage, ListPage, DetailPage): self.register_page(page_class.name, page_class()) def create_context(self): return AppContext(self) app = App() if __name__ == '__main__': app.run(Request()) albatross-1.36/samples/tree1/0000755000175000017500000000000010577422307016004 5ustar andrewmandrewmalbatross-1.36/samples/tree1/install.py0000644000175000017500000000026307433444167020032 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/tree1', cgi = 1) i.add_exec('tree.py') i.add_html('tree.html') i.install() albatross-1.36/samples/tree1/.cvsignore0000644000175000017500000000000607476577160020014 0ustar andrewmandrewm*.pyc albatross-1.36/samples/tree1/tree.py0000644000175000017500000000251407733034012017307 0ustar andrewmandrewm#!/usr/bin/python from albatross.cgiapp import Request from albatross import SimpleSessionApp node_num = 0 class Node: def __init__(self, name, children = None): self.name = name if children is not None: self.children = children self.selected = 0 global node_num self.node_num = node_num node_num = node_num + 1 def albatross_alias(self): return 'node%d' % self.node_num class TreePage: def page_enter(self, ctx): ctx.locals.tree \ = Node('a', [Node('a', [Node('a'), Node('b')]), Node('b', [Node('a', [Node('a', [Node('a'), Node('b')])]), Node('b'), Node('c', [Node('a'), Node('b')])])]) ctx.add_session_vars('tree') def page_display(self, ctx): ctx.run_template('tree.html') app = SimpleSessionApp(base_url='tree.py', template_path='.', start_page='start', secret='-=-secret-=-', session_appid='tree') app.register_page('start', TreePage()) if __name__ == '__main__': app.run(Request()) albatross-1.36/samples/tree1/tree.html0000644000175000017500000000112407407101246017621 0ustar andrewmandrewm Here is a Tree | \

Here is a Tree


 
  
 
 -
  

albatross-1.36/samples/form4/0000755000175000017500000000000010577422307016013 5ustar andrewmandrewmalbatross-1.36/samples/form4/install.py0000644000175000017500000000026307527452147020042 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/form4', cgi = 1) i.add_exec('form.py') i.add_html('form.html') i.install() albatross-1.36/samples/form4/form.py0000644000175000017500000000123507733001170017320 0ustar andrewmandrewm#!/usr/bin/python from albatross import SimpleApp from albatross.cgiapp import Request class Form: def page_enter(self, ctx): ctx.locals.text = ctx.locals.singleton = ctx.locals.group = \ ctx.locals.radio = ctx.locals.select = None ctx.locals.num = 0 ctx.add_session_vars('num') def page_display(self, ctx): ctx.locals.num += 1 ctx.run_template('form.html') app = SimpleApp(base_url='form.py', template_path='.', start_page='form', secret='-=-secret-=-') app.register_page('form', Form()) if __name__ == '__main__': app.run(Request()) albatross-1.36/samples/form4/form.html0000644000175000017500000000203507527452147017652 0ustar andrewmandrewm Display Form Input Input some values to the following form and press the submit button. Text field:
Singleton checkbox:
Checkbox group:
Radio buttons:
Option menu: option1 option2 option3
number of requests:
text:
singleton:
group:
radio:
select:
albatross-1.36/samples/sybase/0000755000175000017500000000000010577422307016252 5ustar andrewmandrewmalbatross-1.36/samples/sybase/sybase.py0000755000175000017500000001007107733034012020104 0ustar andrewmandrewmimport Sybase from albatross import SimpleSessionApp from albatross.apacheapp import Request def get_table_list(ctx): db = ctx.app.db db.execute('use %s' % ctx.locals.database) c = db.cursor() c.execute('select name from sysobjects' ' where type in ("U", "S")' ' order by name') tables = map(lambda r: r[0], c.fetchall()) ctx.locals.tables = tables def get_database_list(ctx): c = ctx.app.db.cursor() c.execute('select name from master..sysdatabases') databases = map(lambda r: r[0], c.fetchall()) ctx.locals.databases = databases def get_table_desc(ctx): db = ctx.app.db db.execute('use %s' % ctx.locals.database) c = db.cursor() c.execute('select id from sysobjects' ' where type in ("U", "S") and name = "%s"' % ctx.locals.table) table_id, = c.fetchone() c.execute('select c.name, t.name, c.length, c.status' ' from syscolumns c, systypes t' ' where c.id = %d and c.usertype = t.usertype' ' order by c.colid' % table_id) ctx.locals.columns = c.fetchall() def get_table_view(ctx): db = ctx.app.db db.execute('use %s' % ctx.locals.database) c = db.cursor() c.execute('select u.name from sysusers u, sysobjects o' ' where o.type in ("U","S") and o.name = "%s"' ' and o.uid = u.uid' % ctx.locals.table) owner, = c.fetchone() table = '%s.%s' % (owner, ctx.locals.table) c = db.cursor() c.execute('select * from %s' % table) ctx.locals.desc = c.description ctx.locals.rows = c.fetchall() def request_field(ctx, name): if ctx.request.has_field(name): return ctx.request.field_value(name) class TableList: name = 'table_list' def page_display(self, ctx): get_database_list(ctx) get_table_list(ctx) ctx.run_template('table-list.html') def page_process(self, ctx): if ctx.req_equals('changedb'): ctx.locals.database = request_field(ctx, 'database') elif ctx.req_equals('desc'): ctx.locals.table = request_field(ctx, 'desc') ctx.set_page('table_desc') elif ctx.req_equals('view'): ctx.locals.table = request_field(ctx, 'view') ctx.set_page('table_view') class TableDesc: name = 'table_desc' def page_display(self, ctx): get_table_desc(ctx) ctx.run_template('table-desc.html') def page_process(self, ctx): if ctx.req_equals('changetable'): ctx.locals.table = request_field(ctx, 'table') elif ctx.req_equals('back'): ctx.set_page('table_list') class TableView: name = 'table_view' def page_display(self, ctx): get_table_view(ctx) ctx.run_template('table-view.html') def page_process(self, ctx): if ctx.req_equals('changetable'): try: del ctx.locals.r except AttributeError: pass ctx.locals.table = request_field(ctx, 'table') elif ctx.req_equals('back'): ctx.set_page('table_list') class App(SimpleSessionApp): def __init__(self): SimpleSessionApp.__init__(self, base_url='sybase.py', template_path='-=-install_dir-=-', start_page='table_list', secret='-=-secret-=-', session_appid='sybase') for page_class in (TableList, TableDesc, TableView): self.register_page(page_class.name, page_class()) self.db = Sybase.connect('rat', 'sa', '') def load_session(self, ctx): SimpleSessionApp.load_session(self, ctx) ctx.default_session_var('database', 'master') ctx.default_session_var('tables', []) ctx.default_session_var('table', None) def create_context(self): ctx = SimpleSessionApp.create_context(self) ctx.run_template_once('macros.html') return ctx app = App() def handler(req): return app.run(Request(req)) albatross-1.36/samples/sybase/htaccess0000644000175000017500000000011007361306675017770 0ustar andrewmandrewmDirectoryIndex sybase.py SetHandler python-program PythonHandler sybase albatross-1.36/samples/sybase/install.py0000644000175000017500000000052607433444167020302 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/sybase', apache = 1) i.add_exec('sybase.py') i.add_html('macros.html') i.add_html('table-desc.html') i.add_html('table-list.html') i.add_html('table-view.html') i.add_html('traceback.html') i.add_data('htaccess', '.htaccess') i.install() albatross-1.36/samples/sybase/.cvsignore0000644000175000017500000000000607476577160020262 0ustar andrewmandrewm*.pyc albatross-1.36/samples/sybase/traceback.html0000644000175000017500000000057407670324130021060 0ustar andrewmandrewm Caught Exception Caught Exception

HTML Traceback (innermost last):

Python Traceback (innermost last):

albatross-1.36/samples/sybase/table-view.html0000644000175000017500000000237407670272567021220 0ustar andrewmandrewm Table View Table: Table Contents There are no rows.
Rows ..: Prev  |  Next albatross-1.36/samples/sybase/macros.html0000644000175000017500000000040507347065147020431 0ustar andrewmandrewm
albatross-1.36/samples/sybase/table-list.html0000644000175000017500000000165307347065147021213 0ustar andrewmandrewm Tables Database: Tables There are no tables.
Actions Name
View Describe
albatross-1.36/samples/sybase/table-desc.html0000644000175000017500000000205207361306675021150 0ustar andrewmandrewm Table Description Table: Table Columns There are no columns.
Name Type Length Status
albatross-1.36/samples/.cvsignore0000644000175000017500000000001407476577160016773 0ustar andrewmandrewm*.cfg *.pyc albatross-1.36/samples/random/0000755000175000017500000000000010577422307016244 5ustar andrewmandrewmalbatross-1.36/samples/random/install.py0000644000175000017500000000053707733036713020273 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/random', cgi = 1) i.add_exec('randompage.py') i.add_exec('pages/tree.py', 'pages/') i.add_html('pages/tree.html', 'pages/') i.add_exec('pages/paginate.py', 'pages/') i.add_html('pages/paginate.html', 'pages/') i.add_module('utils.py') i.install() albatross-1.36/samples/random/pages/0000755000175000017500000000000010577422307017343 5ustar andrewmandrewmalbatross-1.36/samples/random/pages/paginate.html0000644000175000017500000000077307733036713022032 0ustar andrewmandrewm Pagination Example

This is a list with pagination...

prev , next

To Tree Page albatross-1.36/samples/random/pages/paginate.py0000644000175000017500000000013607733036713021507 0ustar andrewmandrewmdef page_display(ctx): ctx.locals.data = range(100) ctx.run_template('paginate.html') albatross-1.36/samples/random/pages/tree.py0000644000175000017500000000125307733036713020657 0ustar andrewmandrewmfrom utils import Node def page_display(ctx): ctx.run_template('tree.html') def page_process(ctx): if ctx.req_equals('paginate'): ctx.redirect('paginate') if not hasattr(ctx.locals, 'tree'): ctx.locals.tree \ = Node('a', [Node('a', [Node('a'), Node('b')]), Node('b', [Node('a', [Node('a', [Node('a'), Node('b')])]), Node('b'), Node('c', [Node('a'), Node('b')])])]) ctx.add_session_vars('tree') albatross-1.36/samples/random/pages/tree.html0000644000175000017500000000122207733036713021167 0ustar andrewmandrewm Here is a Tree | \

Here is a Tree


 
  
 
 -
  

albatross-1.36/samples/random/randompage.py0000644000175000017500000000064007733027771020741 0ustar andrewmandrewm#!/usr/bin/python from albatross import RandomModularSessionApp from albatross.cgiapp import Request app = RandomModularSessionApp(base_url='randompage.py', page_path='pages', start_page='tree', secret='-=-secret-=-', session_appid='random') if __name__ == '__main__': app.run(Request()) albatross-1.36/samples/random/.cvsignore0000644000175000017500000000000607476577160020254 0ustar andrewmandrewm*.pyc albatross-1.36/samples/random/utils.py0000644000175000017500000000054507733027771017770 0ustar andrewmandrewmnode_num = 0 class Node: def __init__(self, name, children = None): self.name = name if children is not None: self.children = children self.selected = 0 global node_num self.node_num = node_num node_num = node_num + 1 def albatross_alias(self): return 'node%d' % self.node_num albatross-1.36/samples/tree3/0000755000175000017500000000000010577422307016006 5ustar andrewmandrewmalbatross-1.36/samples/tree3/install.py0000644000175000017500000000026307500045760020023 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/tree3', cgi = 1) i.add_exec('tree.py') i.add_html('tree.html') i.install() albatross-1.36/samples/tree3/tree.py0000644000175000017500000000206307733034012017310 0ustar andrewmandrewm#!/usr/bin/python import os, stat from albatross.cgiapp import Request from albatross import SimpleApp class Node: def __init__(self, ctx, name, path): self.name = name self.path = path st_mode = os.stat(path)[0] if stat.S_ISDIR(st_mode): self.children = [] self.children_loaded = 0 def load_children(self, ctx): names = os.listdir(self.path) names.sort() self.children = [] for name in names: if name in ('.', '..'): continue self.children.append(Node(ctx, name, os.path.join(self.path, name))) def albatross_alias(self): return self.path class TreePage: def page_display(self, ctx): ctx.locals.tree = Node(ctx, '/', '/var/www') ctx.run_template('tree.html') app = SimpleApp(base_url='tree.py', template_path='.', start_page='start', secret='-=-secret-=-') app.register_page('start', TreePage()) if __name__ == '__main__': app.run(Request()) albatross-1.36/samples/tree3/tree.html0000644000175000017500000000212607500061500017616 0ustar andrewmandrewm Here is a Tree

Here is a Tree

" width="100%" nowrap>
albatross-1.36/samples/mpperf/0000755000175000017500000000000010577422307016255 5ustar andrewmandrewmalbatross-1.36/samples/mpperf/htaccess0000644000175000017500000000011007467176027017776 0ustar andrewmandrewmDirectoryIndex mpperf.py SetHandler python-program PythonHandler mpperf albatross-1.36/samples/mpperf/install.py0000644000175000017500000000036507467176027020312 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/mpperf', apache = 1) i.add_exec('mpperf.py') i.add_module('main.py') i.add_html('main.html') i.add_data('htaccess', '.htaccess') i.install() albatross-1.36/samples/mpperf/main.html0000644000175000017500000000036007467176027020077 0ustar andrewmandrewm mod_python Performance Test

mod_python Performance Test

This is a very simple application which is used simply to test the throughput of the framework. albatross-1.36/samples/mpperf/.cvsignore0000644000175000017500000000000607476577160020265 0ustar andrewmandrewm*.pyc albatross-1.36/samples/mpperf/main.py0000644000175000017500000000007107467176027017562 0ustar andrewmandrewmdef page_display(ctx): ctx.run_template('main.html') albatross-1.36/samples/mpperf/mpperf.py0000644000175000017500000000052607733034012020113 0ustar andrewmandrewmfrom albatross import ModularApp from albatross.apacheapp import Request app = ModularApp(base_url='mpperf', module_path='-=-install_dir-=-', template_path='-=-install_dir-=-', start_page='main', secret='-=-secret-=-') def handler(req): return app.run(Request(req)) albatross-1.36/samples/extension/0000755000175000017500000000000010577422307017000 5ustar andrewmandrewmalbatross-1.36/samples/extension/install.py0000644000175000017500000000025707433444167021031 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/cal', cgi = 1) i.add_exec('cal.py') i.add_html('cal.html') i.install() albatross-1.36/samples/extension/cal.html0000644000175000017500000000056407411627545020436 0ustar andrewmandrewm Calendar for <al-value expr="year">

Calendar for

albatross-1.36/samples/extension/cal.py0000644000175000017500000000350607733001170020104 0ustar andrewmandrewm#!/usr/bin/python import time import calendar import albatross from albatross import SimpleApp from albatross.cgiapp import Request class Calendar(albatross.EmptyTag): name = 'alx-calendar' def to_html(self, ctx): year = self.get_attrib('year') if year is not None: year = ctx.eval_expr(year) month = self.get_attrib('month') if month is not None: month = ctx.eval_expr(month) if month is None or year is None: now = time.localtime(time.time()) if year is None: year = now[0] if month is None: month = now[1] ctx.write_content('\n') ctx.write_content('\n' \ % (calendar.month_name[month], year)) ctx.write_content('') for i in range(7): ctx.write_content('' \ % calendar.day_abbr[(i + 6) % 7][:2]) ctx.write_content('\n') calendar.setfirstweekday(6) for r in calendar.monthcalendar(year, month): ctx.write_content('') for i in range(7): if r[i]: ctx.write_content('' % r[i]) else: ctx.write_content('') ctx.write_content('\n') ctx.write_content('
%s %s
%s
%s
\n') class CalendarPage: def page_display(self, ctx): ctx.locals.year = 2002 ctx.run_template('cal.html') app = SimpleApp(base_url='cal.py', template_path='.', start_page='start', secret='-=-secret-=-') app.register_tagclasses(Calendar) app.register_page('start', CalendarPage()) if __name__ == '__main__': app.run(Request()) albatross-1.36/samples/extension/.cvsignore0000644000175000017500000000000607476577160021010 0ustar andrewmandrewm*.pyc albatross-1.36/samples/images/0000755000175000017500000000000010577422307016231 5ustar andrewmandrewmalbatross-1.36/samples/images/install.py0000644000175000017500000000032307500061500020231 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/images', doc = 1) i.add_image('open.png') i.add_image('close.png') i.add_image('ellipsis.png') i.install() albatross-1.36/samples/images/ellipsis.png0000644000175000017500000000034707500061500020551 0ustar andrewmandrewmPNG  IHDR &gAMA abKGD pHYs  ~tIME;dIDATxcd@i1002b|CDC 0b?,FuLXl ~lZ@k|6?`4Ed,B;IENDB`albatross-1.36/samples/images/.cvsignore0000644000175000017500000000000607500061500020207 0ustar andrewmandrewm*.pyc albatross-1.36/samples/images/close.png0000644000175000017500000000031407500061500020024 0ustar andrewmandrewmPNG  IHDR r|gAMA abKGD pHYs  #utIME*foIIDATxc`@i4 c,"Ȫѕ0a5@X@P!;qbM ~a DzE$IENDB`albatross-1.36/samples/images/open.png0000644000175000017500000000027407500061500017665 0ustar andrewmandrewmPNG  IHDR r|gAMA abKGD pHYs  d_tIME, 9IDATxc`Ԁ!?H8n)12,`b9"q)fM 9S(4IENDB`albatross-1.36/samples/popview4/0000755000175000017500000000000010577422307016541 5ustar andrewmandrewmalbatross-1.36/samples/popview4/list.py0000644000175000017500000000024607701441023020057 0ustar andrewmandrewmdef page_process(ctx): if ctx.req_equals('msgnum'): ctx.set_page('detail') def page_display(ctx): ctx.open_mbox() ctx.run_template('list.html') albatross-1.36/samples/popview4/install.py0000644000175000017500000000052507433444167020570 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/popview4', cgi = 1) i.add_exec('popview.py') i.add_module('popviewlib.py') i.add_module('login.py') i.add_html('login.html') i.add_module('list.py') i.add_html('list.html') i.add_module('detail.py') i.add_html('detail.html') i.install() albatross-1.36/samples/popview4/login.html0000644000175000017500000000072007335507441020537 0ustar andrewmandrewm Please log in

Please log in

Username
Password
albatross-1.36/samples/popview4/list.html0000644000175000017500000000155307362241323020401 0ustar andrewmandrewm Message List Prev | Refresh | Next
To From Subject

albatross-1.36/samples/popview4/.cvsignore0000644000175000017500000000000607476577160020551 0ustar andrewmandrewm*.pyc albatross-1.36/samples/popview4/detail.py0000644000175000017500000000026707701441023020351 0ustar andrewmandrewmdef page_process(ctx): if ctx.req_equals('list'): ctx.set_page('list') def page_display(ctx): ctx.open_mbox() ctx.read_msg() ctx.run_template('detail.html') albatross-1.36/samples/popview4/login.py0000644000175000017500000000060507701441023020213 0ustar andrewmandrewmimport poplib def page_process(ctx): if ctx.req_equals('login'): if ctx.locals.username and ctx.locals.passwd: try: ctx.open_mbox() ctx.add_session_vars('username', 'passwd') except poplib.error_proto: return ctx.set_page('list') def page_display(ctx): ctx.run_template('login.html') albatross-1.36/samples/popview4/detail.html0000644000175000017500000000072507362241323020670 0ustar andrewmandrewm Message Detail Back
:

albatross-1.36/samples/popview4/popviewlib.py0000644000175000017500000000344707701441023021272 0ustar andrewmandrewmimport string import poplib pophost = 'pop' class Headers: def __init__(self): self.hdrs = {} def append(self, header): if not header: return parts = string.split(header, ': ', 1) name = string.capitalize(parts[0]) if len(parts) > 1: value = parts[1] else: value = '' curr = self.hdrs.get(name) if not curr: self.hdrs[name] = value return if type(curr) is type(''): curr = self.hdrs[name] = [curr] curr.append(value) def __getitem__(self, name): return self.hdrs.get(string.capitalize(name), '') class Msg: def __init__(self, mbox, msgnum): self.mbox = mbox self.msgnum = msgnum self.read_headers() def read_headers(self): res = self.mbox.top(self.msgnum, 0) hdrs = Headers() hdr = None for line in res[1]: if line and line[0] in string.whitespace: hdr = hdr + '\n' + line else: hdrs.append(hdr) hdr = line hdrs.append(hdr) self.hdrs = hdrs return hdrs def read_body(self): res = self.mbox.retr(self.msgnum) lines = res[1] for i in range(len(lines)): if not lines[i]: break self.body = string.join(lines[i:], '\n') return self.body class Mbox: def __init__(self, name, passwd): self.mbox = poplib.POP3(pophost) self.mbox.user(name) self.mbox.pass_(passwd) def __getitem__(self, i): try: return Msg(self.mbox, i + 1) except poplib.error_proto: raise IndexError def __len__(self): len, size = self.mbox.stat() return len albatross-1.36/samples/popview4/popview.py0000644000175000017500000000211307733001170020570 0ustar andrewmandrewm#!/usr/bin/python from albatross import ModularSessionApp, SessionAppContext from albatross.cgiapp import Request import popviewlib class AppContext(SessionAppContext): def open_mbox(self): if hasattr(self.locals, 'mbox'): return self.locals.mbox = popviewlib.Mbox(self.locals.username, self.locals.passwd) def read_msg(self): if hasattr(self.locals, 'msg'): return self.locals.msg = self.locals.mbox[int(self.locals.msgnum) - 1] self.locals.msg.read_body() class App(ModularSessionApp): def __init__(self): ModularSessionApp.__init__(self, base_url='popview.py', module_path='.', template_path='.', start_page='login', secret='-=-secret-=-', session_appid='popview4') def create_context(self): return AppContext(self) app = App() if __name__ == '__main__': app.run(Request()) albatross-1.36/samples/popview5/0000755000175000017500000000000010577422307016542 5ustar andrewmandrewmalbatross-1.36/samples/popview5/list.py0000644000175000017500000000024607701441023020060 0ustar andrewmandrewmdef page_process(ctx): if ctx.req_equals('msgnum'): ctx.set_page('detail') def page_display(ctx): ctx.open_mbox() ctx.run_template('list.html') albatross-1.36/samples/popview5/htaccess0000644000175000017500000000011207433376426020263 0ustar andrewmandrewmDirectoryIndex popview.py SetHandler python-program PythonHandler popview albatross-1.36/samples/popview5/install.py0000644000175000017500000000057407433444167020575 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/popview5', apache = 1) i.add_exec('popview.py') i.add_module('popviewlib.py') i.add_module('login.py') i.add_html('login.html') i.add_module('list.py') i.add_html('list.html') i.add_module('detail.py') i.add_html('detail.html') i.add_data('htaccess', '.htaccess') i.install() albatross-1.36/samples/popview5/login.html0000644000175000017500000000072007335507441020540 0ustar andrewmandrewm Please log in

Please log in

Username
Password
albatross-1.36/samples/popview5/list.html0000644000175000017500000000155307362241323020402 0ustar andrewmandrewm Message List Prev | Refresh | Next
To From Subject

albatross-1.36/samples/popview5/.cvsignore0000644000175000017500000000000607476577160020552 0ustar andrewmandrewm*.pyc albatross-1.36/samples/popview5/detail.py0000644000175000017500000000026707701441023020352 0ustar andrewmandrewmdef page_process(ctx): if ctx.req_equals('list'): ctx.set_page('list') def page_display(ctx): ctx.open_mbox() ctx.read_msg() ctx.run_template('detail.html') albatross-1.36/samples/popview5/login.py0000644000175000017500000000060507701441023020214 0ustar andrewmandrewmimport poplib def page_process(ctx): if ctx.req_equals('login'): if ctx.locals.username and ctx.locals.passwd: try: ctx.open_mbox() ctx.add_session_vars('username', 'passwd') except poplib.error_proto: return ctx.set_page('list') def page_display(ctx): ctx.run_template('login.html') albatross-1.36/samples/popview5/detail.html0000644000175000017500000000072507362241323020671 0ustar andrewmandrewm Message Detail Back
:

albatross-1.36/samples/popview5/popviewlib.py0000644000175000017500000000344707701441023021273 0ustar andrewmandrewmimport string import poplib pophost = 'pop' class Headers: def __init__(self): self.hdrs = {} def append(self, header): if not header: return parts = string.split(header, ': ', 1) name = string.capitalize(parts[0]) if len(parts) > 1: value = parts[1] else: value = '' curr = self.hdrs.get(name) if not curr: self.hdrs[name] = value return if type(curr) is type(''): curr = self.hdrs[name] = [curr] curr.append(value) def __getitem__(self, name): return self.hdrs.get(string.capitalize(name), '') class Msg: def __init__(self, mbox, msgnum): self.mbox = mbox self.msgnum = msgnum self.read_headers() def read_headers(self): res = self.mbox.top(self.msgnum, 0) hdrs = Headers() hdr = None for line in res[1]: if line and line[0] in string.whitespace: hdr = hdr + '\n' + line else: hdrs.append(hdr) hdr = line hdrs.append(hdr) self.hdrs = hdrs return hdrs def read_body(self): res = self.mbox.retr(self.msgnum) lines = res[1] for i in range(len(lines)): if not lines[i]: break self.body = string.join(lines[i:], '\n') return self.body class Mbox: def __init__(self, name, passwd): self.mbox = poplib.POP3(pophost) self.mbox.user(name) self.mbox.pass_(passwd) def __getitem__(self, i): try: return Msg(self.mbox, i + 1) except poplib.error_proto: raise IndexError def __len__(self): len, size = self.mbox.stat() return len albatross-1.36/samples/popview5/popview.py0000644000175000017500000000213507733001170020575 0ustar andrewmandrewmfrom albatross import ModularSessionApp, SessionAppContext from albatross.apacheapp import Request import popviewlib class AppContext(SessionAppContext): def open_mbox(self): if hasattr(self.locals, 'mbox'): return self.locals.mbox = popviewlib.Mbox(self.locals.username, self.locals.passwd) def read_msg(self): if hasattr(self.locals, 'msg'): return self.locals.msg = self.locals.mbox[int(self.locals.msgnum) - 1] self.locals.msg.read_body() class App(ModularSessionApp): def __init__(self): ModularSessionApp.__init__(self, base_url='popview.py', module_path='-=-install_dir-=-', template_path='-=-install_dir-=-', start_page='login', secret='-=-secret-=-', session_appid='popview5') def create_context(self): return AppContext(self) app = App() def handler(req): return app.run(Request(req)) albatross-1.36/samples/popview1/0000755000175000017500000000000010577422307016536 5ustar andrewmandrewmalbatross-1.36/samples/popview1/install.py0000644000175000017500000000041207433444167020560 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/popview1', cgi = 1) i.add_exec('popview.py') i.add_module('popviewlib.py') i.add_html('login.html') i.add_html('list.html') i.add_html('detail.html') i.install() albatross-1.36/samples/popview1/login.html0000644000175000017500000000072007335507441020534 0ustar andrewmandrewm Please log in

Please log in

Username
Password
albatross-1.36/samples/popview1/list.html0000644000175000017500000000154307335164151020400 0ustar andrewmandrewm Message List
View To From Subject
albatross-1.36/samples/popview1/.cvsignore0000644000175000017500000000000607476577160020546 0ustar andrewmandrewm*.pyc albatross-1.36/samples/popview1/detail.html0000644000175000017500000000101707335163344020666 0ustar andrewmandrewm Message Detail
:

albatross-1.36/samples/popview1/popviewlib.py0000644000175000017500000000344707701441023021267 0ustar andrewmandrewmimport string import poplib pophost = 'pop' class Headers: def __init__(self): self.hdrs = {} def append(self, header): if not header: return parts = string.split(header, ': ', 1) name = string.capitalize(parts[0]) if len(parts) > 1: value = parts[1] else: value = '' curr = self.hdrs.get(name) if not curr: self.hdrs[name] = value return if type(curr) is type(''): curr = self.hdrs[name] = [curr] curr.append(value) def __getitem__(self, name): return self.hdrs.get(string.capitalize(name), '') class Msg: def __init__(self, mbox, msgnum): self.mbox = mbox self.msgnum = msgnum self.read_headers() def read_headers(self): res = self.mbox.top(self.msgnum, 0) hdrs = Headers() hdr = None for line in res[1]: if line and line[0] in string.whitespace: hdr = hdr + '\n' + line else: hdrs.append(hdr) hdr = line hdrs.append(hdr) self.hdrs = hdrs return hdrs def read_body(self): res = self.mbox.retr(self.msgnum) lines = res[1] for i in range(len(lines)): if not lines[i]: break self.body = string.join(lines[i:], '\n') return self.body class Mbox: def __init__(self, name, passwd): self.mbox = poplib.POP3(pophost) self.mbox.user(name) self.mbox.pass_(passwd) def __getitem__(self, i): try: return Msg(self.mbox, i + 1) except poplib.error_proto: raise IndexError def __len__(self): len, size = self.mbox.stat() return len albatross-1.36/samples/popview1/popview.py0000644000175000017500000000376307777373207020626 0ustar andrewmandrewm#!/usr/bin/python from albatross import SimpleApp, SimpleAppContext from albatross.cgiapp import Request import poplib import popviewlib class LoginPage: name = 'login' def page_process(self, ctx): if ctx.req_equals('login'): if ctx.locals.username and ctx.locals.passwd: try: ctx.open_mbox() ctx.add_session_vars('username', 'passwd') except poplib.error_proto: return ctx.set_page('list') def page_display(self, ctx): ctx.run_template('login.html') class ListPage: name = 'list' def page_process(self, ctx): if ctx.req_equals('detail'): ctx.set_page('detail') def page_display(self, ctx): ctx.open_mbox() ctx.run_template('list.html') class DetailPage: name = 'detail' def page_process(self, ctx): if ctx.req_equals('list'): ctx.set_page('list') def page_display(self, ctx): ctx.open_mbox() ctx.read_msg() ctx.run_template('detail.html') class AppContext(SimpleAppContext): def open_mbox(self): if hasattr(self.locals, 'mbox'): return self.locals.mbox = popviewlib.Mbox(self.locals.username, self.locals.passwd) def read_msg(self): if hasattr(self.locals, 'msg'): return self.locals.msg = self.locals.mbox[int(self.locals.msgnum) - 1] self.locals.msg.read_body() class App(SimpleApp): def __init__(self): SimpleApp.__init__(self, base_url='popview.py', template_path='.', start_page='login', secret='-=-secret-=-') for page_class in (LoginPage, ListPage, DetailPage): self.register_page(page_class.name, page_class()) def create_context(self): return AppContext(self) app = App() if __name__ == '__main__': app.run(Request()) albatross-1.36/samples/templates/0000755000175000017500000000000010577422307016762 5ustar andrewmandrewmalbatross-1.36/samples/templates/form1/0000755000175000017500000000000010577422307020006 5ustar andrewmandrewmalbatross-1.36/samples/templates/form1/install.py0000644000175000017500000000032607433446734022036 0ustar andrewmandrewmimport sys sys.path.insert(0, '../..') import install from install import Installer i = Installer('alsamp/form1', cgi = 1) i.add_exec('form.py') i.add_html('form.html') i.add_html('form-display.html') i.install() albatross-1.36/samples/templates/form1/form.py0000644000175000017500000000036607433446734021337 0ustar andrewmandrewm#!/usr/bin/python import cgi from albatross import SimpleContext ctx = SimpleContext('.') ctx.locals.form = cgi.FieldStorage() templ = ctx.load_template('form.html') templ.to_html(ctx) print 'Content-Type: text/html' print ctx.flush_content() albatross-1.36/samples/templates/form1/.cvsignore0000644000175000017500000000000607476577160022016 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/form1/form.html0000644000175000017500000000135107433446734021646 0ustar andrewmandrewm Display Form Input Input some values to the following form and press the submit button.
Text field:
Singleton checkbox:
Checkbox group:
Radio buttons:
Option menu:
albatross-1.36/samples/templates/form1/form-display.html0000644000175000017500000000067507330017513023302 0ustar andrewmandrewm Field is list: , Field has a single value:
albatross-1.36/samples/templates/form2/0000755000175000017500000000000010577422307020007 5ustar andrewmandrewmalbatross-1.36/samples/templates/form2/install.py0000644000175000017500000000032607433446734022037 0ustar andrewmandrewmimport sys sys.path.insert(0, '../..') import install from install import Installer i = Installer('alsamp/form2', cgi = 1) i.add_exec('form.py') i.add_html('form.html') i.add_html('form-display.html') i.install() albatross-1.36/samples/templates/form2/form.py0000644000175000017500000000074707433446734021343 0ustar andrewmandrewm#!/usr/bin/python import cgi from albatross import SimpleContext form = cgi.FieldStorage() ctx = SimpleContext('.') ctx.locals.form = form for name in form.keys(): if type(form[name]) is type([]): value = [] for elem in form[name]: value.append(elem.value) else: value = form[name].value setattr(ctx.locals, name, value) templ = ctx.load_template('form.html') templ.to_html(ctx) print 'Content-Type: text/html' print ctx.flush_content() albatross-1.36/samples/templates/form2/.cvsignore0000644000175000017500000000000607476577160022017 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/form2/form.html0000644000175000017500000000152507726744441021653 0ustar andrewmandrewm Display Form Input Input some values to the following form and press the submit button.
Text field:
Singleton checkbox:
Checkbox group:
Radio buttons:
Option menu: option1 option2 option3 albatross-1.36/samples/templates/form2/form-display.html0000644000175000017500000000067507330017513023303 0ustar andrewmandrewm Field is list: , Field has a single value:
albatross-1.36/samples/templates/simple1/0000755000175000017500000000000010577422307020334 5ustar andrewmandrewmalbatross-1.36/samples/templates/simple1/install.py0000644000175000017500000000024207433435207022352 0ustar andrewmandrewmimport sys sys.path.insert(0, '../..') import install from install import Installer i = Installer('alsamp/simple1', cgi = 1) i.add_exec('simple.py') i.install() albatross-1.36/samples/templates/simple1/.cvsignore0000644000175000017500000000000607476577160022344 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/simple1/simple.py0000644000175000017500000000036607330017513022174 0ustar andrewmandrewm#!/usr/bin/python print 'Content-Type: text/html' print print '' print ' ' print ' My CGI application' print ' ' print ' ' print ' Hello from my simple CGI application!' print ' ' print '' albatross-1.36/samples/templates/form3/0000755000175000017500000000000010577422307020010 5ustar andrewmandrewmalbatross-1.36/samples/templates/form3/install.py0000644000175000017500000000032607433446734022040 0ustar andrewmandrewmimport sys sys.path.insert(0, '../..') import install from install import Installer i = Installer('alsamp/form3', cgi = 1) i.add_exec('form.py') i.add_html('form.html') i.add_html('form-display.html') i.install() albatross-1.36/samples/templates/form3/form.py0000644000175000017500000000116007433446734021332 0ustar andrewmandrewm#!/usr/bin/python import cgi from albatross import SimpleContext form = cgi.FieldStorage() ctx = SimpleContext('.') ctx.locals.option_list1 = ['option1', 'option2', 'option3'] ctx.locals.option_list2 = [(1, 'option1'), (2, 'option2'), (3, 'option3')] ctx.locals.form = form for name in form.keys(): if type(form[name]) is type([]): value = [] for elem in form[name]: value.append(elem.value) else: value = form[name].value setattr(ctx.locals, name, value) templ = ctx.load_template('form.html') templ.to_html(ctx) print 'Content-Type: text/html' print ctx.flush_content() albatross-1.36/samples/templates/form3/.cvsignore0000644000175000017500000000000607476577160022020 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/form3/form.html0000644000175000017500000000146107726744441021653 0ustar andrewmandrewm Display Form Input Input some values to the following form and press the submit button.
Text field:
Singleton checkbox:
Checkbox group:
Radio buttons:
Option menu: albatross-1.36/samples/templates/form3/form-display.html0000644000175000017500000000067507330017513023304 0ustar andrewmandrewm Field is list: , Field has a single value:
albatross-1.36/samples/templates/simple2/0000755000175000017500000000000010577422307020335 5ustar andrewmandrewmalbatross-1.36/samples/templates/simple2/install.py0000644000175000017500000000027407433435207022360 0ustar andrewmandrewmimport sys sys.path.insert(0, '../..') import install from install import Installer i = Installer('alsamp/simple2', cgi = 1) i.add_exec('simple.py') i.add_html('simple.html') i.install() albatross-1.36/samples/templates/simple2/simple.html0000644000175000017500000000020407330017513022500 0ustar andrewmandrewm My CGI application Hello from my second simple CGI application! albatross-1.36/samples/templates/simple2/.cvsignore0000644000175000017500000000000607476577160022345 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/simple2/simple.py0000644000175000017500000000030707433435207022200 0ustar andrewmandrewm#!/usr/bin/python from albatross import SimpleContext ctx = SimpleContext('.') templ = ctx.load_template('simple.html') templ.to_html(ctx) print 'Content-Type: text/html' print ctx.flush_content() albatross-1.36/samples/templates/tree/0000755000175000017500000000000010577422307017721 5ustar andrewmandrewmalbatross-1.36/samples/templates/tree/.cvsignore0000644000175000017500000000000607476577160021731 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/tree/tree.py0000644000175000017500000000133407701441023021222 0ustar andrewmandrewmfrom albatross import SimpleContext class Node: def __init__(self, name, children = None): self.name = name if children is not None: self.children = children ctx = SimpleContext('.') ctx.locals.tree = Node('a', [Node('b', [Node('c'), Node('d')]), Node('e', [Node('f', [Node('g', [Node('h'), Node('i')])]), Node('j'), Node('k', [Node('l'), Node('m')])])]) templ = ctx.load_template('tree.html') templ.to_html(ctx) ctx.flush_content() albatross-1.36/samples/templates/tree/tree.html0000644000175000017500000000052507345152235021547 0ustar andrewmandrewm | \ - albatross-1.36/samples/templates/simple3/0000755000175000017500000000000010577422307020336 5ustar andrewmandrewmalbatross-1.36/samples/templates/simple3/install.py0000644000175000017500000000027407433435207022361 0ustar andrewmandrewmimport sys sys.path.insert(0, '../..') import install from install import Installer i = Installer('alsamp/simple3', cgi = 1) i.add_exec('simple.py') i.add_html('simple.html') i.install() albatross-1.36/samples/templates/simple3/simple.html0000644000175000017500000000042107330017513022502 0ustar andrewmandrewm The CGI environment
albatross-1.36/samples/templates/simple3/.cvsignore0000644000175000017500000000000607476577160022346 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/simple3/simple.py0000644000175000017500000000045707433435207022207 0ustar andrewmandrewm#!/usr/bin/python import os from albatross import SimpleContext ctx = SimpleContext('.') templ = ctx.load_template('simple.html') keys = os.environ.keys() keys.sort() ctx.locals.keys = keys ctx.locals.environ = os.environ templ.to_html(ctx) print 'Content-Type: text/html' print ctx.flush_content() albatross-1.36/samples/templates/content1/0000755000175000017500000000000010577422307020515 5ustar andrewmandrewmalbatross-1.36/samples/templates/content1/install.py0000644000175000017500000000041307433441121022523 0ustar andrewmandrewmimport sys sys.path.insert(0, '../..') import install from install import Installer i = Installer('alsamp/content1', cgi = 1) i.add_exec('content.py') i.add_html('main.html', 'templ/') i.add_html('oops.html', 'templ/') i.add_html('other.html', 'templ/') i.install() albatross-1.36/samples/templates/content1/main.html0000644000175000017500000000030207330017513022312 0ustar andrewmandrewm Simple Content Management - main page.

Simple Content Management - main page


This is the main page. albatross-1.36/samples/templates/content1/oops.html0000644000175000017500000000054707433441121022361 0ustar andrewmandrewm Simple Content Management - error page.

Simple Content Management - error page


You actually requested the error page! Sorry, the page does not exist. albatross-1.36/samples/templates/content1/.cvsignore0000644000175000017500000000000607476577160022525 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/content1/content.py0000644000175000017500000000077510027750251022542 0ustar andrewmandrewm#!/usr/bin/python import os from albatross import SimpleContext, TemplateLoadError script_name = os.environ['SCRIPT_NAME'] request_uri = os.environ['REQUEST_URI'] page = request_uri[len(script_name) + 1:] if not page or os.path.dirname(page): page = 'main.html' ctx = SimpleContext('templ') ctx.locals.page = page try: templ = ctx.load_template(page) except TemplateLoadError: templ = ctx.load_template('oops.html') templ.to_html(ctx) print 'Content-Type: text/html' print ctx.flush_content() albatross-1.36/samples/templates/content1/other.html0000644000175000017500000000030507330017513022512 0ustar andrewmandrewm Simple Content Management - other page.

Simple Content Management - other page


This is the other page. albatross-1.36/samples/templates/simple4/0000755000175000017500000000000010577422307020337 5ustar andrewmandrewmalbatross-1.36/samples/templates/simple4/install.py0000644000175000017500000000027407433435207022362 0ustar andrewmandrewmimport sys sys.path.insert(0, '../..') import install from install import Installer i = Installer('alsamp/simple4', cgi = 1) i.add_exec('simple.py') i.add_html('simple.html') i.install() albatross-1.36/samples/templates/simple4/simple.html0000644000175000017500000000051007330017513022502 0ustar andrewmandrewm The CGI environment
albatross-1.36/samples/templates/simple4/.cvsignore0000644000175000017500000000000607476577160022347 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/simple4/simple.py0000644000175000017500000000036307433435207022204 0ustar andrewmandrewm#!/usr/bin/python import os from albatross import SimpleContext ctx = SimpleContext('.') templ = ctx.load_template('simple.html') ctx.locals.environ = os.environ templ.to_html(ctx) print 'Content-Type: text/html' print ctx.flush_content() albatross-1.36/samples/templates/stream/0000755000175000017500000000000010577422307020255 5ustar andrewmandrewmalbatross-1.36/samples/templates/stream/install.py0000644000175000017500000000027307433447650022304 0ustar andrewmandrewmimport sys sys.path.insert(0, '../..') import install from install import Installer i = Installer('alsamp/stream', cgi = 1) i.add_exec('stream.py') i.add_html('stream.html') i.install() albatross-1.36/samples/templates/stream/.cvsignore0000644000175000017500000000000607476577160022265 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/stream/stream.py0000644000175000017500000000057707433447650022140 0ustar andrewmandrewm#!/usr/bin/python import time from albatross import SimpleContext class SlowProcess: def __getitem__(self, i): time.sleep(1) if i < 10: return i raise IndexError ctx = SimpleContext('.') templ = ctx.load_template('stream.html') ctx.locals.process = SlowProcess() print 'Content-Type: text/html' print templ.to_html(ctx) ctx.flush_content() albatross-1.36/samples/templates/stream/stream.html0000644000175000017500000000041407411610574022433 0ustar andrewmandrewm I think I can, I think I can

Calculation is in progress, please stand by.

Stage:

All done! albatross-1.36/samples/templates/simple5/0000755000175000017500000000000010577422307020340 5ustar andrewmandrewmalbatross-1.36/samples/templates/simple5/install.py0000644000175000017500000000027407433435207022363 0ustar andrewmandrewmimport sys sys.path.insert(0, '../..') import install from install import Installer i = Installer('alsamp/simple5', cgi = 1) i.add_exec('simple.py') i.add_html('simple.html') i.install() albatross-1.36/samples/templates/simple5/simple.html0000644000175000017500000000053107330017513022506 0ustar andrewmandrewm The CGI environment
albatross-1.36/samples/templates/simple5/.cvsignore0000644000175000017500000000000607476577160022350 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/simple5/simple.py0000644000175000017500000000030707433435207022203 0ustar andrewmandrewm#!/usr/bin/python from albatross import SimpleContext ctx = SimpleContext('.') templ = ctx.load_template('simple.html') templ.to_html(ctx) print 'Content-Type: text/html' print ctx.flush_content() albatross-1.36/samples/templates/content2/0000755000175000017500000000000010577422307020516 5ustar andrewmandrewmalbatross-1.36/samples/templates/content2/install.py0000644000175000017500000000045707433441121022534 0ustar andrewmandrewmimport sys sys.path.insert(0, '../..') import install from install import Installer i = Installer('alsamp/content2', cgi = 1) i.add_exec('content.py') i.add_html('macros.html', 'templ/') i.add_html('main.html', 'templ/') i.add_html('oops.html', 'templ/') i.add_html('other.html', 'templ/') i.install() albatross-1.36/samples/templates/content2/main.html0000644000175000017500000000015507330017513022321 0ustar andrewmandrewm main page This is the main page. albatross-1.36/samples/templates/content2/oops.html0000644000175000017500000000042107433441121022351 0ustar andrewmandrewm error page You actually requested the error page! Sorry, the page does not exist. albatross-1.36/samples/templates/content2/.cvsignore0000644000175000017500000000000607476577160022526 0ustar andrewmandrewm*.pyc albatross-1.36/samples/templates/content2/content.py0000644000175000017500000000105310027750251022531 0ustar andrewmandrewm#!/usr/bin/python import os from albatross import SimpleContext, TemplateLoadError script_name = os.environ['SCRIPT_NAME'] request_uri = os.environ['REQUEST_URI'] page = request_uri[len(script_name) + 1:] if not page or os.path.dirname(page): page = 'main.html' ctx = SimpleContext('templ') ctx.load_template('macros.html').to_html(ctx) ctx.locals.page = page try: templ = ctx.load_template(page) except TemplateLoadError: templ = ctx.load_template('oops.html') templ.to_html(ctx) print 'Content-Type: text/html' print ctx.flush_content() albatross-1.36/samples/templates/content2/other.html0000644000175000017500000000015707330017513022520 0ustar andrewmandrewm other page This is the other page. albatross-1.36/samples/templates/content2/macros.html0000644000175000017500000000036607330017513022665 0ustar andrewmandrewm Simple Content Management - <al-usearg name="title">

Simple Content Management -


albatross-1.36/samples/tree2/0000755000175000017500000000000010577422307016005 5ustar andrewmandrewmalbatross-1.36/samples/tree2/install.py0000644000175000017500000000026307433444167020033 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/tree2', cgi = 1) i.add_exec('tree.py') i.add_html('tree.html') i.install() albatross-1.36/samples/tree2/.cvsignore0000644000175000017500000000000607476577160020015 0ustar andrewmandrewm*.pyc albatross-1.36/samples/tree2/tree.py0000644000175000017500000000206307733034012017307 0ustar andrewmandrewm#!/usr/bin/python import os, stat from albatross.cgiapp import Request from albatross import SimpleApp class Node: def __init__(self, ctx, name, path): self.name = name self.path = path st_mode = os.stat(path)[0] if stat.S_ISDIR(st_mode): self.children = [] self.children_loaded = 0 def load_children(self, ctx): names = os.listdir(self.path) names.sort() self.children = [] for name in names: if name in ('.', '..'): continue self.children.append(Node(ctx, name, os.path.join(self.path, name))) def albatross_alias(self): return self.path class TreePage: def page_display(self, ctx): ctx.locals.tree = Node(ctx, '/', '/var/www') ctx.run_template('tree.html') app = SimpleApp(base_url='tree.py', template_path='.', start_page='start', secret='-=-secret-=-') app.register_page('start', TreePage()) if __name__ == '__main__': app.run(Request()) albatross-1.36/samples/tree2/tree.html0000644000175000017500000000164207500061500017617 0ustar andrewmandrewm Here is a Tree

Here is a Tree

" width="100%" nowrap>
albatross-1.36/samples/popview2/0000755000175000017500000000000010577422307016537 5ustar andrewmandrewmalbatross-1.36/samples/popview2/install.py0000644000175000017500000000041207433444167020561 0ustar andrewmandrewmimport sys sys.path.insert(0, '..') import install from install import Installer i = Installer('alsamp/popview2', cgi = 1) i.add_exec('popview.py') i.add_module('popviewlib.py') i.add_html('login.html') i.add_html('list.html') i.add_html('detail.html') i.install() albatross-1.36/samples/popview2/login.html0000644000175000017500000000072007335507441020535 0ustar andrewmandrewm Please log in

Please log in

Username
Password
albatross-1.36/samples/popview2/list.html0000644000175000017500000000223507335507562020407 0ustar andrewmandrewm Message List
View To From Subject

albatross-1.36/samples/popview2/.cvsignore0000644000175000017500000000000607476577160020547 0ustar andrewmandrewm*.pyc albatross-1.36/samples/popview2/detail.html0000644000175000017500000000101707335163344020667 0ustar andrewmandrewm Message Detail
:

albatross-1.36/samples/popview2/popviewlib.py0000644000175000017500000000344707701441023021270 0ustar andrewmandrewmimport string import poplib pophost = 'pop' class Headers: def __init__(self): self.hdrs = {} def append(self, header): if not header: return parts = string.split(header, ': ', 1) name = string.capitalize(parts[0]) if len(parts) > 1: value = parts[1] else: value = '' curr = self.hdrs.get(name) if not curr: self.hdrs[name] = value return if type(curr) is type(''): curr = self.hdrs[name] = [curr] curr.append(value) def __getitem__(self, name): return self.hdrs.get(string.capitalize(name), '') class Msg: def __init__(self, mbox, msgnum): self.mbox = mbox self.msgnum = msgnum self.read_headers() def read_headers(self): res = self.mbox.top(self.msgnum, 0) hdrs = Headers() hdr = None for line in res[1]: if line and line[0] in string.whitespace: hdr = hdr + '\n' + line else: hdrs.append(hdr) hdr = line hdrs.append(hdr) self.hdrs = hdrs return hdrs def read_body(self): res = self.mbox.retr(self.msgnum) lines = res[1] for i in range(len(lines)): if not lines[i]: break self.body = string.join(lines[i:], '\n') return self.body class Mbox: def __init__(self, name, passwd): self.mbox = poplib.POP3(pophost) self.mbox.user(name) self.mbox.pass_(passwd) def __getitem__(self, i): try: return Msg(self.mbox, i + 1) except poplib.error_proto: raise IndexError def __len__(self): len, size = self.mbox.stat() return len albatross-1.36/samples/popview2/popview.py0000644000175000017500000000374507777373207020627 0ustar andrewmandrewm#!/usr/bin/python from albatross import SimpleApp, SimpleAppContext from albatross.cgiapp import Request import popviewlib class LoginPage: name = 'login' def page_process(self, ctx): if ctx.req_equals('login'): if ctx.locals.username and ctx.locals.passwd: try: ctx.open_mbox() ctx.add_session_vars('username', 'passwd') except poplib.error_proto: return ctx.set_page('list') def page_display(self, ctx): ctx.run_template('login.html') class ListPage: name = 'list' def page_process(self, ctx): if ctx.req_equals('detail'): ctx.set_page('detail') def page_display(self, ctx): ctx.open_mbox() ctx.run_template('list.html') class DetailPage: name = 'detail' def page_process(self, ctx): if ctx.req_equals('list'): ctx.set_page('list') def page_display(self, ctx): ctx.open_mbox() ctx.read_msg() ctx.run_template('detail.html') class AppContext(SimpleAppContext): def open_mbox(self): if hasattr(self.locals, 'mbox'): return self.locals.mbox = popviewlib.Mbox(self.locals.username, self.locals.passwd) def read_msg(self): if hasattr(self.locals, 'msg'): return self.locals.msg = self.locals.mbox[int(self.locals.msgnum) - 1] self.locals.msg.read_body() class App(SimpleApp): def __init__(self): SimpleApp.__init__(self, base_url='popview.py', template_path='.', start_page='login', secret='-=-secret-=-') for page_class in (LoginPage, ListPage, DetailPage): self.register_page(page_class.name, page_class()) def create_context(self): return AppContext(self) app = App() if __name__ == '__main__': app.run(Request()) albatross-1.36/session-server/0000755000175000017500000000000010577422307016307 5ustar andrewmandrewmalbatross-1.36/session-server/.cvsignore0000644000175000017500000000000607436646011020303 0ustar andrewmandrewm*.pyc albatross-1.36/session-server/rc.d-script0000644000175000017500000000154407501547174020371 0ustar andrewmandrewm#!/bin/sh # # SysV-style init.d script for the albatross session daemon # # $Id: rc.d-script 5897 2002-06-12 04:30:52Z andrewm $ # DAEMON=/usr/local/bin/al-session-daemon USER=albatross LOGFILE=/var/log/al-session-daemon.log PIDFILE=/var/run/albatross/session-daemon.pid test -x ${DAEMON} || exit 0 umask 077 case "$1" in start) echo "Starting albatross session daemon" piddir=`dirname ${PIDFILE}` touch ${LOGFILE} if [ ! -d ${piddir} ]; then mkdir ${piddir} fi chown ${USER} ${LOGFILE} ${piddir} su ${USER} -c "${DAEMON} -l ${LOGFILE} -k ${PIDFILE} start" ;; stop) echo "Stopping albatross session daemon" su ${USER} -c "${DAEMON} -l ${LOGFILE} -k ${PIDFILE} stop" ;; *) echo "Usage: $* {start|stop}" exit 1 ;; esac exit 0 albatross-1.36/session-server/TODO0000644000175000017500000000057607450247736017016 0ustar andrewmandrewm- Daemonize: - second fork after setsid()? - os.chdir('/) - umask? - environ? - "watcher" process - Persistent sessions. Ideas: - write each update to a file, periodically write ram resident sessions to a new file and zero "journal" file. On restart, read dump + "journal" to rebuild state. - libdb? - just write each session to a file. albatross-1.36/session-server/al-session-daemon0000755000175000017500000002035110530177557021557 0ustar andrewmandrewm#!/usr/bin/python # # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Run the session server as a daemon # # This won't work under Windows as the daemonisation process is specific # to unix. Hints on how to make it work gratefully received. # # $Id: al-session-daemon 8003 2006-11-20 01:05:51Z andrewm $ # # standard libraries import getopt import os import sys import signal from errno import * import time import traceback # our modules from albatross import simpleserver from albatross import pidfile ourname = "al-session-daemon" EXIT_OK, EXIT_ERROR, EXIT_USAGE = range(3) class Options: def __init__(self): # Defaults self.debug = 0 self.logfile_name = "/var/log/al-session-daemon.log" self.ourname = ourname self.pidfile_name = "/var/run/%s.pid" % ourname self.port = 34343 class Pipe: """ A simple wrapper around a unix pipe, intended to be used to communicate between a parent and a child process. One process holds the read end, the other process holds the write end. The first use decides which direction the pipe runs in, with the unused file descriptors being closed (so the other end can detect EOF). On destruction of the object, any open file descriptors are closed. """ def __init__(self): self.read_fd, self.write_fd = os.pipe() def write(self, buf): if self.read_fd >= 0: try: os.close(self.read_fd) except: pass self.read_fd = -1 return os.write(self.write_fd, buf) def read(self, count): if self.write_fd >= 0: try: os.close(self.write_fd) except: pass self.write_fd = -1 return os.read(self.read_fd, count) def __del__(self): if self.read_fd >= 0: try: os.close(self.read_fd) except: pass if self.write_fd >= 0: try: os.close(self.write_fd) except: pass class Daemon: def __init__(self, port, logfile_name, pidfile_name, debug = 0): self.port = port if logfile_name: self.logfile = open(logfile_name, "a", 1) else: self.logfile = None self.pidfile = pidfile.PidFile(pidfile_name) if debug: self.debugfile = self.logfile else: self.debugfile = None def start(self): # We want the parent to be able to return useful exit codes to # whoever called it, so we use a pipe to pass status from the # child back to the parent. pipe = Pipe() pid = os.fork() if pid: # Parent status = pipe.read(3) if status == str(EXIT_OK): print "session-server pid %d" % pid return EXIT_OK else: return EXIT_ERROR # Child del pid try: self.pidfile.start() except pidfile.PidFileError, msg: sys.stderr.write("pidfile: %s\n" % msg) sys.exit(EXIT_ERROR) try: self.server = simpleserver.Server(self.port, self.debugfile) # Divorce us from the parents controlling TTY (so we don't # receive their signals, etc). It's somewhat rude to change # these file descriptors from under the python libraries, but # it should be safe enough. devnull = os.open("/dev/null", os.O_RDWR) if not self.logfile: opfile = devnull else: opfile = self.logfile.fileno() os.dup2(devnull, 0) os.dup2(opfile, 1) os.dup2(opfile, 2) os.close(devnull) del opfile, devnull try: os.setsid() except: pass signal.signal(signal.SIGTERM, self.sig_stop) if self.logfile: self.logfile.write("\nServer starting at %s\n" % self.asctime()) self.logfile.flush() pipe.write(str(EXIT_OK)) # Signal parent that all is ok del pipe self.server.select_loop() except: traceback.print_exc() if self.logfile: self.logfile.write("Server stopped at %s\n" % self.asctime()) try: self.pidfile.stop() except pidfile.PidFileError, msg: sys.stderr.write("pidfile: %s\n" % msg) sys.exit(EXIT_OK) def sig_stop(self, sig, stack): self.server.stop() def stop(self): tries = 5 try: pid = self.pidfile.getpid() except pidfile.PidFileError, msg: sys.stderr.write("pidfile: %s\n" % msg) return EXIT_ERROR if pid: print "stopping %d" % pid, while self.pidfile.is_running() and tries: try: self.pidfile.kill() except pidfile.PidFileError, msg: sys.stderr.write("pidfile: %s\n" % msg) return EXIT_ERROR print ".", sys.stdout.flush() tries = tries - 1 time.sleep(1) if tries: print "done" return EXIT_OK else: print "failed to kill %s" % pid else: print "daemon not running" return EXIT_ERROR def status(self): pid = self.pidfile.getpid() if pid: print "Daemon pid is %d" % pid return EXIT_OK else: print "Daemon is not running" return EXIT_ERROR def command(self, cmd_name): cmds = { "start": self.start, "stop": self.stop, "status": self.status, } try: cmd = cmds[cmd_name] except KeyError: return EXIT_USAGE return cmd() def asctime(self): # Python 1.5.2's time.asctime() wasn't as enlightened as 2.0, so we # roll our own return time.asctime(time.localtime(time.time())) def usage(): sys.stderr.write("Usage: %s [OPTIONS]... \n" % ourname) sys.stderr.write("Try '%s --help' for more information\n" % ourname) sys.exit(EXIT_USAGE) def help(): print """\ Usage: %(ourname)s [options]... Where [options] are: -D, --debug Write debugging to log -h, --help Display this help and exit -k , Record server pid in , default is --pidfile= %(pidfile_name)s -p , --port= Listen on , default is %(port)s -l , Write log to , default is --log= %(logfile_name)s is one of: start start a new daemon stop kill the current daemon status request the daemon's status """ % Options().__dict__ sys.exit(EXIT_OK) def main(argv): options = Options() try: opts, args = getopt.getopt(argv, "Dhk:p:l:", ["debug", "help", "pidfile=", "port=", "log="]) except getopt.GetoptError: usage() for opt, arg in opts: if opt in ("-D", "--debug"): options.debug = not options.debug elif opt in ("-h", "--help"): help() elif opt in ("-k", "--pidfile"): options.pidfile_name = arg elif opt in ("-p", "--port"): try: options.port = int(arg) except ValueError: usage() elif opt in ("-l", "--log"): options.logfile_name = arg if not args: usage() daemon = Daemon(port = options.port, logfile_name = options.logfile_name, pidfile_name = options.pidfile_name, debug = options.debug) exit_code = daemon.command(args[0]) if exit_code == EXIT_USAGE: usage() sys.exit(exit_code) if __name__ == '__main__': main(sys.argv[1:]) albatross-1.36/PKG-INFO0000644000175000017500000000043110577422307014413 0ustar andrewmandrewmMetadata-Version: 1.0 Name: albatross Version: 1.36 Summary: Albatross Web Toolkit Home-page: http://www.object-craft.com.au/projects/albatross/ Author: Object Craft Author-email: albatross@object-craft.com.au License: BSD - see file LICENCE Description: UNKNOWN Platform: UNKNOWN albatross-1.36/test/0000755000175000017500000000000010577422307014277 5ustar andrewmandrewmalbatross-1.36/test/all.py0000644000175000017500000000147210001130000015365 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Build and test all listed modules. # # The module files must have a function "suite" which returns # an instance of unittest.TestSuite or unittest.TestCase. # # $Id: all.py 6161 2004-01-14 02:57:04Z andrewm $ import unittest class AllTestSuite(unittest.TestSuite): # add modules with tests here all_tests = [ "templates", "namespace", "sessions", "misc", ] def __init__(self): unittest.TestSuite.__init__(self) for module_name in self.all_tests: module = __import__(module_name, globals()) self.addTest(module.suite()) if __name__ == '__main__': unittest.main(defaultTest='AllTestSuite') albatross-1.36/test/misc/0000755000175000017500000000000010577422307015232 5ustar andrewmandrewmalbatross-1.36/test/misc/modules/0000755000175000017500000000000010577422307016702 5ustar andrewmandrewmalbatross-1.36/test/misc/modules/simple_module.py0000644000175000017500000000004010001130000022044 0ustar andrewmandrewmdef page_display(ctx): pass albatross-1.36/test/misc/modules/simple.html0000644000175000017500000000005210001130000021016 0ustar andrewmandrewmThe value of x is "" albatross-1.36/test/misc/modules/.cvsignore0000644000175000017500000000000610001130000020636 0ustar andrewmandrewm*.pyc albatross-1.36/test/misc/modules/missing_methods.py0000644000175000017500000000017710001130000022415 0ustar andrewmandrewm# This tests the case where none of the page module methods (page_enter, # page_display, page_process, page_leave) are defined albatross-1.36/test/misc/modules/submod/0000755000175000017500000000000010577422307020173 5ustar andrewmandrewmalbatross-1.36/test/misc/modules/submod/module_decode.py0000644000175000017500000000017510001130000023300 0ustar andrewmandrewmclass PageModuleObject: pass def page_display(ctx): ctx.locals.x = PageModuleObject() ctx.add_session_vars('x') albatross-1.36/test/misc/modules/submod/.cvsignore0000644000175000017500000000000610001130000022127 0ustar andrewmandrewm*.pyc albatross-1.36/test/misc/modules/submod/module_hierarchy.py0000644000175000017500000000012410001130000024025 0ustar andrewmandrewmdef page_display(ctx): ctx.locals.x = 'Bah' ctx.run_template('simple.html') albatross-1.36/test/misc/modules/submod.py0000644000175000017500000000013110001130000020500 0ustar andrewmandrewmdef page_display(ctx): ctx.locals.x = 'silkiest' ctx.run_template('simple.html') albatross-1.36/test/misc/modules/run_template.py0000644000175000017500000000012410001130000021710 0ustar andrewmandrewmdef page_display(ctx): ctx.locals.x = "Foo" ctx.run_template('simple.html') albatross-1.36/test/misc/__init__.py0000644000175000017500000000152110575142514017337 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Build and test all listed modules. # # The module files must have a function "suite" which returns # an instance of unittest.TestSuite or unittest.TestCase. # # $Id: __init__.py 8841 2007-03-12 03:13:16Z andrewm $ import unittest class AllTestSuite(unittest.TestSuite): # add modules with tests here all_tests = [ 'pagemodule', 'uriparse', 'flow', ] def __init__(self): unittest.TestSuite.__init__(self) for module_name in self.all_tests: module = __import__(module_name, globals()) self.addTest(module.suite()) def suite(): return AllTestSuite() if __name__ == '__main__': unittest.main(defaultTest='AllTestSuite') albatross-1.36/test/misc/.cvsignore0000644000175000017500000000000610001130000017166 0ustar andrewmandrewm*.pyc albatross-1.36/test/misc/dummyrequest.py0000644000175000017500000000127110575142514020346 0ustar andrewmandrewm# # Copyright 2006 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # class DummyRequest: """ Fakes client interactions for testing purposes. """ def __init__(self): self.__content = [] def has_field(self, name): pass def field_names(self): return [] def write_header(self, name, value): pass def end_headers(self): pass def write_content(self, data): self.__content.append(data) def get_content(self): return '\n'.join(self.__content) def set_status(self, status): pass def return_code(self): pass albatross-1.36/test/misc/flow.py0000644000175000017500000000671610575142514016562 0ustar andrewmandrewm# # Copyright 2006 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # page_enter, page_display, page_process, page_leave import unittest import albatross from dummyrequest import DummyRequest class Page: def __init__(self, name): self.name = name def page_enter(self, ctx): ctx.app.called.append('enter ' + self.name) def page_display(self, ctx): ctx.app.called.append('display ' + self.name) def page_process(self, ctx): ctx.app.called.append('process ' + self.name) if ctx.app.action: meth, args = ctx.app.action ctx.app.action = None getattr(ctx, meth)(*args) def page_leave(self, ctx): ctx.app.called.append('leave ' + self.name) class TestApp(albatross.SimpleApp): def __init__(self): albatross.SimpleApp.__init__(self, base_url='app.py', template_path='.', start_page='pageA', secret='-=-secret-=-') self.vars = {} self.ctx = None self.called = [] self.action = None def run_with_action(self, req, meth, *args): self.action = meth, args self.run(req) def get_called(self): called, self.called = self.called, [] return called def create_context(self): self.ctx = albatross.SimpleApp.create_context(self) return self.ctx def load_session(self, ctx): ctx.locals.__dict__.update(self.vars) def save_session(self, ctx): self.vars = ctx.locals.__dict__ class Case(unittest.TestCase): def assertActions(self, app, *actions): self.assertEqual(app.get_called(), list(actions)) def pageclass(self): app = TestApp() app.register_page('pageA', Page('pageA')) app.register_page('pageB', Page('pageB')) app.register_page('pageC', Page('pageC')) req = DummyRequest() app.run(req) content = req.get_content() self.failIf(content, content) self.assertEqual(app.vars['__page__'], 'pageA') self.assertEqual(app.get_called(), ['enter pageA', 'process pageA', 'display pageA']) app.run_with_action(req, 'set_page', 'pageB') self.assertActions(app, 'process pageA', 'leave pageA', 'enter pageB', 'display pageB') app.run_with_action(req, 'push_page', 'pageA') self.assertActions(app, 'process pageB', 'enter pageA', 'display pageA') app.run_with_action(req, 'pop_page') self.assertActions(app, 'process pageA', 'leave pageA', 'display pageB') app.run_with_action(req, 'push_page', 'pageA') app.run_with_action(req, 'push_page', 'pageC') self.assertActions(app, 'process pageB', 'enter pageA', 'display pageA', 'process pageA', 'enter pageC', 'display pageC') app.run_with_action(req, 'pop_page', 'pageB') self.assertActions(app, 'process pageC', 'leave pageC', 'leave pageA', 'display pageB') class Suite(unittest.TestSuite): test_list = ( 'pageclass', ) def __init__(self): unittest.TestSuite.__init__(self, map(Case, self.test_list)) def suite(): return Suite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/misc/pagemodule.py0000644000175000017500000000701310575142514017724 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Form functionality tests. # # $Id: pagemodule.py 8841 2007-03-12 03:13:16Z andrewm $ import sys import os import unittest import albatross import re from dummyrequest import DummyRequest class TestContext(albatross.AppContext, albatross.NameRecorderMixin, albatross.SessionBase): def __init__(self, app): albatross.AppContext.__init__(self, app) albatross.NameRecorderMixin.__init__(self) albatross.SessionBase.__init__(self) def load_session(self): pass def save_session(self): pass def remove_session(self): pass class TestApp(albatross.ModularApp): def __init__(self, mod_name): base_dir = os.path.dirname(sys.modules[__name__].__file__) module_dir = os.path.join(base_dir, 'modules') albatross.ModularApp.__init__(self, 'foo.py', module_dir, module_dir, mod_name, 'secret') def create_context(self): self.__ctx = TestContext(self) return self.__ctx def get_test_ctx(self): return self.__ctx class PageModuleCase(unittest.TestCase): def _test_mod(self, mod_name): app = TestApp(mod_name) req = DummyRequest() app.run(req) return req.get_content() def _mod_result(self, mod_name, want): result = self._test_mod(mod_name) self.assertEqual(result, want, 'wanted %r, got:\n%s' % (want, result)) def _mod_regexp(self, mod_name, pattern): result = self._test_mod(mod_name) self.failUnless(re.search(pattern, result), 'Did not find "%s" in:\n%s' % (pattern, result)) def missing_module(self): self._mod_regexp('nonexistent_module', 'ApplicationError: No module named') def simple_page_module(self): self._mod_result('simple_module', '') def missing_methods(self): self._mod_regexp('missing_methods', 'ApplicationError: .*does not define one of') def run_template(self): self._mod_result('run_template', 'The value of x is "Foo"\n') def module_hierarchy(self): self._mod_result('submod.module_hierarchy', 'The value of x is "Bah"\n') self._mod_result('submod', 'The value of x is "silkiest"\n') def module_decode(self): """ pickle and unpickle a session containing an instance of an object defined in the page module """ app = TestApp('submod.module_decode') req = DummyRequest() app.run(req) ctx = app.get_test_ctx() session = ctx.encode_session() # force re-import del sys.modules[app.mod_holder_name] del sys.modules[app.mod_holder_name + '.submod.module_decode'] app = TestApp('submod.module_decode') # to break # app._PageModuleMixin__base_dir = 'poo' ctx = app.create_context() ctx.decode_session(session) class PageModuleSuite(unittest.TestSuite): test_list = ( 'missing_module', 'simple_page_module', 'missing_methods', 'run_template', 'module_hierarchy', 'module_decode', ) def __init__(self): unittest.TestSuite.__init__(self, map(PageModuleCase, self.test_list)) def suite(): return PageModuleSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/misc/uriparse.py0000644000175000017500000000604310001422370017417 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # URI parsing and manipulation tests # # $Id: uriparse.py 6166 2004-01-15 05:28:24Z andrewm $ import unittest import albatross class DummyApp: def __init__(self, base_url): self.__base_url = base_url def base_url(self): return self.__base_url class DummyRequest: def __init__(self, request_uri): self.__request_uri = request_uri def get_uri(self): return self.__request_uri class URIparse(unittest.TestCase): def _get_ctx(self, base_url, request_uri): ctx = albatross.AppContext(DummyApp(base_url)) ctx.set_request(DummyRequest(request_uri)) return ctx def base_url(self): base_url = 'app.py' ctx = self._get_ctx(base_url, None) self.assertEqual(ctx.base_url(), base_url) def current_url(self): ctx = self._get_ctx(None, 'http://localhost/cgi-bin/app.py') self.assertEqual(ctx.current_url(), '/cgi-bin/app.py') def absolute_base_url(self): ctx = self._get_ctx('app.py', 'http://localhost/cgi-bin/app.py/poo') self.assertEqual(ctx.absolute_base_url(), '/cgi-bin/app.py') ctx = self._get_ctx('/cgi-bin/app.py', 'http://localhost/cgi-bin/app.py/poo') self.assertEqual(ctx.absolute_base_url(), '/cgi-bin/app.py') # Check base_url vs request url mismatch ctx = self._get_ctx('/cgi-bin/app.py', 'http://localhost/cgi-bin/whatever.py/poo') self.assertRaises(albatross.ApplicationError, ctx.absolute_base_url) def redirect_url(self): # Redirect within the app - make absolute ctx = self._get_ctx('app.py', 'http://localhost/cgi-bin/app.py/poo') self.assertEqual(ctx.redirect_url('foo'), 'http://localhost/cgi-bin/app.py/foo') # Redirect within the app - other access schemes (https) ctx = self._get_ctx('app.py', 'https://localhost/cgi-bin/app.py/poo') self.assertEqual(ctx.redirect_url('foo'), 'https://localhost/cgi-bin/app.py/foo') # Redirect outside the app, leave alone - absolute URI ctx = self._get_ctx('app.py', 'http://localhost/cgi-bin/app.py/poo') self.assertEqual(ctx.redirect_url('http://localhost/foo.html'), 'http://localhost/foo.html') # Redirect outside the app, absolute PATH ctx = self._get_ctx('app.py', 'http://localhost/cgi-bin/app.py/poo') self.assertEqual(ctx.redirect_url('/foo.html'), 'http://localhost/foo.html') class URIparseSuite(unittest.TestSuite): test_list = ( 'base_url', 'current_url', 'absolute_base_url', 'redirect_url', ) def __init__(self): unittest.TestSuite.__init__(self, map(URIparse, self.test_list)) def suite(): return URIparseSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/.cvsignore0000644000175000017500000000000607327242620016270 0ustar andrewmandrewm*.pyc albatross-1.36/test/sessions/0000755000175000017500000000000010577422307016145 5ustar andrewmandrewmalbatross-1.36/test/sessions/ses_file.py0000644000175000017500000000461310023463067020306 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # SessionFile functionality tests # # $Id: ses_file.py 6170 2004-03-10 00:49:59Z andrewm $ # import os, sys, errno import unittest import albatross import request class Vars: pass class SessionFileApp(albatross.SessionFileAppMixin): def module_path(self): pass class SessionFile(albatross.ResponseMixin, albatross.SessionFileContextMixin): """ This corresponds to the object albatross users know as the ctx, although since we aren't using the normal set of mixins, we have to supply our own locals and request members. """ def __init__(self, request, test_dir): self.app = SessionFileApp('test', test_dir) self.request = request self.locals = Vars() albatross.ResponseMixin.__init__(self) albatross.SessionFileContextMixin.__init__(self) def parsed_request_uri(self): return '', '', '' def absolute_base_url(self): return '' def test_save(self): self.write_headers() self.save_session() class Session: """ This represents a virtual "user" - essentially just a place to store their session id while the ctx objects come and go. """ def __init__(self, test_dir): self.test_dir = test_dir self.request = request.DummyCookieRequest() def new_context(self): return SessionFile(self.request, self.test_dir) class FileCase(unittest.TestCase): def setUp(self): # This is because we may not be imported from the current directory try: base_dir = os.path.dirname(sys.modules[__name__].__file__) except AttributeError: base_dir = '' self.test_dir = os.path.join(base_dir, "sessionfiletmp") try: os.mkdir(self.test_dir) except OSError, (eno, estr): if eno == errno.EEXIST: self.cleandir(self.test_dir) else: raise OSError, (eno, estr) def tearDown(self): self.cleandir(self.test_dir) os.rmdir(self.test_dir) def cleandir(self, test_dir): for file in os.listdir(test_dir): if file[-4:] == '.als': # Safety os.unlink(os.path.join(test_dir, file)) def new_session(self): return Session(self.test_dir) albatross-1.36/test/sessions/request.py0000644000175000017500000000256110446670652020216 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Dummy Request objects # # $Id: request.py 6618 2006-06-23 04:44:26Z andrewm $ # import string, re class DummyCookieRequest: """ A dummy Request-like class that serves to record the user's session id """ def __init__(self): self.cookie = None def get_method(self): return 'GET' def write_header(self, hdr, value): if hdr == 'Set-Cookie': self.cookie = value def get_header(self, hdr): if hdr == 'Cookie': return self.cookie def end_headers(self): pass class DummyFieldRequest: def __init__(self): self.fieldre = re.compile(r'', re.I|re.M|re.S) self.fields = {} self.__content_parts = [] def has_field(self, field): return field in self.fields def field_value(self, field): return self.fields[field] def write_content(self, data): self.__content_parts.append(data) def flush_content(self): data = string.join(self.__content_parts, '') match = self.fieldre.search(data) if match: key = match.group(1) value = match.group(2) self.fields[key] = value albatross-1.36/test/sessions/__init__.py0000644000175000017500000000150007521440537020252 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Build and test all listed modules. # # The module files must have a function "suite" which returns # an instance of unittest.TestSuite or unittest.TestCase. # # $Id: __init__.py 5912 2002-07-30 07:33:51Z andrewm $ import unittest class AllTestSuite(unittest.TestSuite): # add modules with tests here all_tests = [ "basic", "concurrent", ] def __init__(self): unittest.TestSuite.__init__(self) for module_name in self.all_tests: module = __import__(module_name, globals()) self.addTest(module.suite()) def suite(): return AllTestSuite() if __name__ == '__main__': unittest.main(defaultTest='AllTestSuite') albatross-1.36/test/sessions/ses_server.py0000644000175000017500000000423710445660052020700 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # SessionServer functionality tests # # $Id: ses_server.py 6562 2006-06-20 02:40:42Z andrewm $ # import os, sys try: base_dir = os.path.dirname(sys.modules[__name__].__file__) except AttributeError: base_dir = '' sys.path.insert(0, os.path.join(base_dir, '../../session-server')) import unittest import albatross import request class Vars: pass class SessionServerApp(albatross.SessionServerAppMixin): def module_path(self): pass class SessionServer(albatross.ResponseMixin, albatross.SessionServerContextMixin): """ This corresponds to the object albatross users know as the ctx, although since we aren't using the normal set of mixins, we have to supply our own locals and request members. """ def __init__(self, request): self.app = SessionServerApp('test') self.request = request self.locals = Vars() albatross.ResponseMixin.__init__(self) albatross.SessionServerContextMixin.__init__(self) def parsed_request_uri(self): return '', '', '' def absolute_base_url(self): return '' def test_save(self): self.write_headers() self.save_session() class Session: """ This represents a virtual "user" - essentially just a place to store their session id while the ctx objects come and go. """ def __init__(self): self.request = request.DummyCookieRequest() def new_context(self): return SessionServer(self.request) class ServerCase(unittest.TestCase): def setUp(self): # We assume the user has started the session server - we don't # have a portable way to do this ourselves. pass def tearDown(self): pass def new_session(self): return Session() def have_server(): import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: try: s.connect(('127.0.0.1', 34343)) except socket.error: return False else: return True finally: s.close() albatross-1.36/test/sessions/.cvsignore0000644000175000017500000000000607435122422020133 0ustar andrewmandrewm*.pyc albatross-1.36/test/sessions/concurrent.py0000644000175000017500000000147507521440537020710 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Basic functionality tests - save and restore a single context # # $Id: concurrent.py 5912 2002-07-30 07:33:51Z andrewm $ # import unittest class BasicCase(unittest.TestCase): def check_file(self): pass def check_server(self): pass def check_hiddenfield(self): pass class BasicSuite(unittest.TestSuite): # add modules with tests here test_list = [ "check_file", "check_server", "check_hiddenfield", ] def __init__(self): unittest.TestSuite.__init__(self, map(BasicCase, self.test_list)) def suite(): return BasicSuite() if __name__ == '__main__': unittest.main(defaultTest='BasicSuite') albatross-1.36/test/sessions/basic.py0000644000175000017500000000346410445660052017602 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Basic functionality tests - save and restore a single context # # $Id: basic.py 6562 2006-06-20 02:40:42Z andrewm $ # import unittest import ses_file import ses_server import ses_hiddenfield class BasicCase: def runTest(self): test_str = "Green Eggs and Ham" session = self.new_session() ctx = session.new_context() ctx.load_session() self.assertEqual(ctx.locals.__dict__, {}) ctx.locals.test_var = test_str ctx.add_session_vars('test_var') ctx.test_save() ctx = session.new_context() ctx.load_session() self.assertEqual(ctx.locals.__dict__.keys(), ['test_var']) self.assertEqual(ctx.locals.test_var, test_str) test_list = [] class FileBasicCase(ses_file.FileCase, BasicCase): pass test_list.append(FileBasicCase) if ses_server.have_server(): class ServerBasicCase(ses_server.ServerCase, BasicCase): pass test_list.append(ServerBasicCase) else: print 'SKIPPED sessions.basic.ServerBasicCase: no session server' class HiddenFieldBasicCase(ses_hiddenfield.HiddenFieldCase, BasicCase): pass test_list.append(HiddenFieldBasicCase) class BasicCase(unittest.TestCase): def check_file(self): self.test_saverestore(ses_file.Session()) def check_server(self): self.test_saverestore(ses_server.Session()) def check_hiddenfield(self): self.test_saverestore(ses_hiddenfield.Session()) class BasicSuite(unittest.TestSuite): def __init__(self): unittest.TestSuite.__init__(self, [cls() for cls in test_list]) def suite(): return BasicSuite() if __name__ == '__main__': unittest.main(defaultTest='BasicSuite') albatross-1.36/test/sessions/ses_hiddenfield.py0000644000175000017500000000301707521440537021631 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # HiddenFieldSession functionality tests # # $Id: ses_hiddenfield.py 5912 2002-07-30 07:33:51Z andrewm $ # import os, sys import unittest import albatross import request class Vars: pass class HiddenFieldSessionApp(albatross.PickleSignMixin): def module_path(self): pass class HiddenFieldSession(albatross.HiddenFieldSessionMixin): """ This corresponds to the object albatross users know as the ctx, although since we aren't using the normal set of mixins, we have to supply our own locals and request members. """ def __init__(self, request): self.app = HiddenFieldSessionApp(secret="test") self.request = request self.locals = Vars() albatross.HiddenFieldSessionMixin.__init__(self) def test_save(self): self.form_close() self.request.flush_content() def write_content(self, text): self.request.write_content(text) class Session: """ This represents a virtual "user" - essentially just a place to store their session id while the ctx objects come and go. """ def __init__(self): self.request = request.DummyFieldRequest() def new_context(self): return HiddenFieldSession(self.request) class HiddenFieldCase(unittest.TestCase): def setUp(self): pass def tearDown(self): pass def new_session(self): return Session() albatross-1.36/test/Makefile0000644000175000017500000000034110445650367015740 0ustar andrewmandrewm # Run test suite. # # $Id: Makefile 6561 2006-06-20 01:35:51Z andrewm $ PYTHON=python all: unittests docs unittests: PYTHONPATH=.. $(PYTHON) all.py docs: cd ../doc && make PYTHON="$(PYTHON)" test clean: rm -rf *.pyc albatross-1.36/test/namespace/0000755000175000017500000000000010577422307016233 5ustar andrewmandrewmalbatross-1.36/test/namespace/set_value.py0000644000175000017500000000547310575426527020613 0ustar andrewmandrewm# # Copyright 2005 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # Test the NamespaceMixin's set_value method # # $Id: set_value.py 8849 2007-03-13 04:49:27Z andrewm $ import unittest import albatross set_value = albatross.NamespaceMixin.set_value.im_func class NS: pass class SetValueTest(unittest.TestCase): def setUp(self): self.locals = NS() def _test(self, name, value): set_value(self, name, value) evaled = eval(name, {}, self.locals.__dict__) self.assertEqual(evaled, value) def set_test(self): self._test('a', 1) def errors_test(self): self.locals.b = 1 self.assertRaises(AttributeError, set_value, self, 'a.b', 1) self.assertRaises(ValueError, set_value, self, 'b[a]', 1) self.assertRaises(SyntaxError, set_value, self, 'b[.a]', 1) self.assertRaises(SyntaxError, set_value, self, 'b[1.0]', 1) self.assertRaises(SyntaxError, set_value, self, 'b[1', 1) self.assertRaises(SyntaxError, set_value, self, 'b]', 1) self.assertRaises(SyntaxError, set_value, self, '.b', 1) self.assertRaises(SyntaxError, set_value, self, 'b.', 1) self.assertRaises(SyntaxError, set_value, self, '[0]', 1) def set_array_test(self): self.locals.b = [0] * 4 self._test('b[0]', 1) self._test('b[2]', 2) self._test('b[-1]', 3) self.assertRaises(IndexError, set_value, self, 'b[4]', 1) self.assertEqual(self.locals.b, [1, 0, 2, 3]) def set_sub_test(self): self.locals.c = NS() self.locals.c.e = [0] * 4 f = NS() self.locals.c.e[3] = f self._test('c.d', 1) self._test('c.e[0]', 1) self._test('c.e[2]', 2) self._test('c.e[3].g', 3) self.assertRaises(IndexError, set_value, self, 'c.e[4]', 1) self.assertEqual(self.locals.c.e, [1, 0, 2, f]) def iter_backdoor_test(self): class Iter: def set_backdoor(self, op, value): self.op = op self.value = value self.locals.f = Iter() self.locals.g = None set_value(self, 'x,f', 1) self.assertEqual(self.locals.f.op, 'x') self.assertEqual(self.locals.f.value, 1) self.assertRaises(AttributeError, set_value, self, 'x,g', 1) def array_array_test(self): self.locals.g = [[0] * 3] * 3 self._test('g[1][2]', 2) class Suite(unittest.TestSuite): def __init__(self): tests = [SetValueTest(meth) for meth in dir(SetValueTest) if not meth.startswith('_') and meth.endswith('_test')] unittest.TestSuite.__init__(self, tests) def suite(): return Suite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/namespace/request.py0000644000175000017500000001744510576462345020315 0ustar andrewmandrewm# # Copyright 2002 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Test CGI Request functionality # # $Id: request.py 8887 2007-03-16 09:35:33Z andrewm $ import unittest import cgi import re import urllib import StringIO import albatross import albatross.cgiapp class Context(albatross.NameRecorderMixin, albatross.NamespaceMixin, albatross.ExecuteMixin): def __init__(self): albatross.NameRecorderMixin.__init__(self) albatross.NamespaceMixin.__init__(self) albatross.ExecuteMixin.__init__(self) class App(albatross.PickleSignMixin): def __init__(self): albatross.PickleSignMixin.__init__(self, '') def create_context(self): self.ctx = Context() self.ctx.app = self return self.ctx def run(self, req): ctx = self.create_context() ctx.request = req ctx.app = self if ctx.request is not None: ctx.merge_request() def build_query(*fields): return '&'.join(['%s=%s' % (a, urllib.quote(v)) for a, v in fields]) def make_albform(*inputs): # trick albatross into generating an __albform__ value for us app = App() ctx = app.create_context() ctx.push_content_trap() ctx.form_open() for itype, iname, imulti in inputs: ctx.input_add(itype, iname, '', imulti) ctx.form_close() result = ctx.pop_content_trap() match = re.search('name="__albform__" value="([^"]+)"', result) if match: return match.group(1) return "" class SimpleRequestTest(unittest.TestCase): def make_albform(self): return make_albform(('text', 'aaa', False), ('text', 'bbb', True)) def test_request(self, request): app = App() app.run(request) self.failUnless(app.ctx.locals.aaa, 'bah') self.failUnless(app.ctx.locals.bbb, ['a','b']) class CGIGetRequestTestCase(SimpleRequestTest): def runTest(self): albform = self.make_albform() query_string = build_query(('aaa', 'bah'), ('bbb', 'a'), ('bbb', 'b'), ('__albform__', albform)) environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': query_string, } fields = cgi.FieldStorage(environ=environ) self.test_request(albatross.cgiapp.Request(fields=fields)) class CGIGetRequestNoAlbformTestCase(SimpleRequestTest): def runTest(self): query_string = build_query(('aaa', 'bah'), ('bbb', 'a'), ('bbb', 'b'), ('ccc.ddd', 'c')) environ = { 'REQUEST_METHOD': 'GET', 'QUERY_STRING': query_string, } fields = cgi.FieldStorage(environ=environ) req = albatross.cgiapp.Request(fields=fields) app = App() app.run(req) app.ctx.locals.ccc = albatross.Vars() self.failIf(hasattr(app.ctx.locals, 'aaa')) self.failIf(hasattr(app.ctx.locals, 'bbb')) self.failIf(hasattr(app.ctx.locals.ccc, 'ddd')) app.ctx.merge_vars('aa', 'aaaa') self.failIf(hasattr(app.ctx.locals, 'aaa')) self.failIf(hasattr(app.ctx.locals, 'bbb')) self.failIf(hasattr(app.ctx.locals.ccc, 'ddd')) app.ctx.merge_vars('aaa', 'bbb') self.failUnless(app.ctx.locals.aaa, 'bah') self.failUnless(app.ctx.locals.bbb, ['a','b']) self.failIf(hasattr(app.ctx.locals.ccc, 'ddd')) app.ctx.merge_vars('ccc') self.failUnless(app.ctx.locals.ccc.ddd, 'c') class CGIPostRequestTestCase(SimpleRequestTest): def runTest(self): albform = self.make_albform() query_string = build_query(('aaa', 'bah'), ('bbb', 'a'), ('bbb', 'b'), ('__albform__', albform)) environ = { 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'application/x-www-form-urlencoded', } input_file = StringIO.StringIO(query_string) fields = cgi.FieldStorage(fp=input_file, environ=environ) self.test_request(albatross.cgiapp.Request(fields=fields)) class CGIFormRequestTestCase(SimpleRequestTest): input = """\ ----------AXBYCZ Content-Disposition: form-data; name="aaa" bah ----------AXBYCZ Content-Disposition: form-data; name="bbb" a ----------AXBYCZ Content-Disposition: form-data; name="bbb" b ----------AXBYCZ Content-Disposition: form-data; name="__albform__" %s ----------AXBYCZ-- """ def runTest(self): environ = { 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'multipart/form-data; boundary=--------AXBYCZ', } input_file = StringIO.StringIO(self.input % self.make_albform()) fields = cgi.FieldStorage(fp=input_file, environ=environ) self.test_request(albatross.cgiapp.Request(fields=fields)) class FileRequestTest(unittest.TestCase): def test_request(self, input_str): boundary = '--------AXBYCZ' environ = { 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'multipart/form-data; boundary=--------AXBYCZ', } albform = make_albform(('file', 'ccc', False)) app = App() input_file = StringIO.StringIO(input_str % albform) fields = cgi.FieldStorage(fp=input_file, environ=environ) req = albatross.cgiapp.Request(fields=fields) app.run(req) return app.ctx.locals.ccc class CGISingleFileRequestTestCase(FileRequestTest): input_str = """\ ----------AXBYCZ Content-Disposition: form-data; name="ccc"; filename="baz" bah ----------AXBYCZ Content-Disposition: form-data; name="__albform__" %s ----------AXBYCZ-- """ def runTest(self): ccc = self.test_request(self.input_str) self.failUnless(len(ccc), 1) self.failUnless(ccc[0].file.read(), 'bah') self.failUnless(ccc[0].filename, 'baz') class CGIListFileRequestTestCase(FileRequestTest): input_str = """\ ----------AXBYCZ Content-Disposition: form-data; name="ccc"; filename="baz" bah ----------AXBYCZ Content-Disposition: form-data; name="ccc"; filename="blot" bling ----------AXBYCZ Content-Disposition: form-data; name="__albform__" %s ----------AXBYCZ-- """ def runTest(self): ccc = self.test_request(self.input_str) self.failUnless(len(ccc), 2) self.failUnless(ccc[0].file.read(), 'bah') self.failUnless(ccc[0].filename, 'baz') self.failUnless(ccc[1].file.read(), 'bling') self.failUnless(ccc[1].filename, 'blot') class CGIMultiFileRequestTestCase(FileRequestTest): input_str = """\ ----------AXBYCZ Content-Disposition: form-data; name="ccc" Content-Type: multipart/mixed; boundary=--------ZCYBXA ----------ZCYBXA Content-Disposition: attachment; filename="baz" Content-Type: text/plain bah ----------ZCYBXA Content-Disposition: attachment; filename="blot" bling ----------ZCYBXA-- ----------AXBYCZ Content-Disposition: form-data; name="__albform__" %s ----------AXBYCZ-- """ def runTest(self): ccc = self.test_request(self.input_str) self.failUnless(len(ccc), 2) self.failUnless(ccc[0].file.read(), 'bah') self.failUnless(ccc[0].filename, 'baz') self.failUnless(ccc[1].file.read(), 'bling') self.failUnless(ccc[1].filename, 'blot') """ Driver for the above tests """ class RequestSuite(unittest.TestSuite): def __init__(self): unittest.TestSuite.__init__(self) self.addTest(CGIGetRequestTestCase()) self.addTest(CGIGetRequestNoAlbformTestCase()) self.addTest(CGIPostRequestTestCase()) self.addTest(CGIFormRequestTestCase()) self.addTest(CGISingleFileRequestTestCase()) self.addTest(CGIListFileRequestTestCase()) self.addTest(CGIMultiFileRequestTestCase()) def suite(): return RequestSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/namespace/__init__.py0000644000175000017500000000152510350447144020342 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Build and test all listed modules. # # The module files must have a function "suite" which returns # an instance of unittest.TestSuite or unittest.TestCase. # # $Id: __init__.py 6245 2005-12-16 05:19:32Z andrewm $ import unittest class AllTestSuite(unittest.TestSuite): # add modules with tests here all_tests = [ 'recorder', 'request', 'set_value', ] def __init__(self): unittest.TestSuite.__init__(self) for module_name in self.all_tests: module = __import__(module_name, globals()) self.addTest(module.suite()) def suite(): return AllTestSuite() if __name__ == '__main__': unittest.main(defaultTest='AllTestSuite') albatross-1.36/test/namespace/.cvsignore0000644000175000017500000000000607477533101020227 0ustar andrewmandrewm*.pyc albatross-1.36/test/namespace/recorder.py0000644000175000017500000001151310576462345020420 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Test NameRecorderMixin functionality # # Unfortunately, we need to have intimate knowledge of the encoding scheme used # by the NameRecorderMixin - changes there will need to be reflected here. # # $Id: recorder.py 8887 2007-03-16 09:35:33Z andrewm $ import re, os, sys import unittest import base64, zlib, cPickle import albatross """ Test field recording action Set up all the appropriate infrastructure, then run a template that contains an . Check that the resulting html contains a __albform__ hidden field with an appropriate value. """ albform_re = re.compile(r'', re.I|re.M|re.S) class RecorderTestApp(albatross.TemplateLoaderMixin, albatross.Application): def __init__(self, base_url): albatross.TemplateLoaderMixin.__init__(self, base_url) albatross.Application.__init__(self, base_url) def pickle_sign(self, text): # albatross.Application discards text, we need a pass-thru return text def pickle_unsign(self, text): return text class RecorderTestContext(albatross.AppContext, albatross.NameRecorderMixin, albatross.StubSessionMixin): def __init__(self, template_dir): app = RecorderTestApp(template_dir) albatross.AppContext.__init__(self, app) albatross.NameRecorderMixin.__init__(self) def current_url(self): return 'here' class RecorderTestCase(unittest.TestCase): template_dir = "recorder" def runTest(self): base_dir = os.path.dirname(sys.modules[__name__].__file__) template_dir = os.path.join(base_dir, self.template_dir) self.ctx = RecorderTestContext(template_dir) self.ctx.push_content_trap() tmpl = self.ctx.load_template("recorder.html") tmpl.to_html(self.ctx) text = self.ctx.pop_content_trap() expect = {'this': 0, 'that': 1} result = None match = albform_re.search(text) self.failUnless(match, "__albform__ regular expression didn't find anything") text = base64.decodestring(match.group(1)) text = zlib.decompress(text) result = cPickle.loads(text) self.assertEqual(result, expect) """ Test request merging action """ merge_data = [ # var list? input expect ('this', 0, 'one', 'one'), ('that', 1, 'two', ['two']), ('another', 1, ['three'], ['three']), ('whatever', 1, None, []), ] class MergeTestRequest: def __init__(self): self.expect = {} self.fields = {} self.islist = {} for var, islist, input, output in merge_data: self.islist[var] = islist self.fields[var] = input self.expect[var] = output text = cPickle.dumps(self.islist, 1) text = zlib.compress(text) text = base64.encodestring(text) self.fields['__albform__'] = text self.expect['__page__'] = None self.expect['__pages__'] = [] def has_field(self, field): return field in self.fields def field_value(self, field): return self.fields[field] class MergeTestCase(unittest.TestCase): def runTest(self): ctx = RecorderTestContext('.') ctx.set_request(MergeTestRequest()) ctx.merge_request() got = ctx.locals.__dict__ self.assertEqual(got, ctx.request.expect) """ Test namespace "protection" """ class UnderscoreTestContext(albatross.AppContext, albatross.StubRecorderMixin, albatross.StubSessionMixin): def __init__(self, template_dir): app = RecorderTestApp(template_dir) albatross.AppContext.__init__(self, app) # def current_url(self): # return 'here' class UnderscoreRequest: def __init__(self): self.__field = '_protected' def has_field(self, name): return name == self.__field def field_value(self, name): if name == self.__field: return self.__field else: raise KeyError def field_names(self): return [self.__field] class UnderscoreTestCase(unittest.TestCase): def runTest(self): ctx = UnderscoreTestContext('.') ctx.set_request(UnderscoreRequest()) self.failUnlessRaises(albatross.SecurityError, ctx.merge_request) """ Driver for the above tests """ class RecorderSuite(unittest.TestSuite): def __init__(self): unittest.TestSuite.__init__(self) self.addTest(RecorderTestCase()) self.addTest(MergeTestCase()) self.addTest(UnderscoreTestCase()) def suite(): return RecorderSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/namespace/recorder/0000755000175000017500000000000010577422307020040 5ustar andrewmandrewmalbatross-1.36/test/namespace/recorder/recorder.html0000644000175000017500000000041207525650103022524 0ustar andrewmandrewm albatross-1.36/test/templates/0000755000175000017500000000000010577422307016275 5ustar andrewmandrewmalbatross-1.36/test/templates/lookup.py0000644000175000017500000000430210575705072020160 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Test template expansion. # # $Id: lookup.py 8878 2007-03-14 05:38:02Z andrewm $ import unittest import albatross import albatross_test class TemplateTestCase(albatross_test.AlbatrossTestCase): template_dir = "lookup" lookup_cases = ( (0, "Zero"), (1, "One"), (2, "Two"), (3, "Many"), (4, "Many"), (-1, "Many"), (20, "Many") ) def test_cases(self, template, cases): for i, result in cases: self.ctx.locals.i = i self.html_test(template, result) class InlineCase(TemplateTestCase): def check_enum(self): self.test_cases('inline.html', self.lookup_cases) class FileTemplateCase(TemplateTestCase): def check_enum(self): self.test_cases('lookup.html', self.lookup_cases) def check_no_default(self): no_default_cases = ( (0, "Zero"), (1, "One"), (2, ""), (20, ""), ) self.test_cases('no-default.html', no_default_cases) def check_no_close(self): self.test_raise("no-close.html") # This differs from the previous class because it generates the html output by # feeding in a string to the templating code. class StringTemplateCase(TemplateTestCase): template_string = ''' Zero One Two Many ''' def check_enum(self): for i, result in self.lookup_cases: self.ctx.locals.i = i self.test_str_html(self.template_string, result) class TemplateSuite(unittest.TestSuite): test_list = ( "check_enum", "check_no_default", "check_no_close", ) def __init__(self): unittest.TestSuite.__init__(self, map(FileTemplateCase, self.test_list)) self.addTest(InlineCase("check_enum")) self.addTest(StringTemplateCase("check_enum")) def suite(): return TemplateSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/templates/iterator.py0000644000175000017500000000565610575705072020515 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Test iterator constructs. # # $Id: iterator.py 8878 2007-03-14 05:38:02Z andrewm $ import unittest import albatross import albatross_test class IteratorCase(albatross_test.AlbatrossTestCase): template_dir = "iterator" def check_iterator(self): self.html_test("iterator.html", "01234") def check_var(self): self.html_test("var.html", "01234") def check_vars(self): self.html_test("vars.html", "0516273849") def check_anonymous(self): self.html_test("anonymous.html", "aaaaa") def check_nested(self): self.html_test("nested.html", "000102101112") def check_precreate(self): self.ctx.locals.i = albatross.ListIterator() self.ctx.locals.i.set_sequence(range(5)) self.html_test("precreate.html", "01234") def check_unclosed(self): self.test_raise("unclosed.html") def check_missingname(self): self.test_raise("missingname.html") def check_noseq(self): self.test_raise("noseq.html") def check_colsdown(self): self.html_test("down.html", "+035+146+2+") def check_colsacross(self): self.html_test("across.html", "+012+345+6+") def check_paging(self): # Fields are: # i.pagesize() # i.has_prevpage() # i.has_nextpage() # i.index() # i.start() # i.count() # i.value() self.html_test("paging.html", "3 False True 0 0 0 0\n" "3 False True 1 0 1 1\n" "3 False True 2 0 2 2\n") self.ctx.locals.i.set_backdoor('nextpage', 'nextpage,i') self.expect_html( "3 True True 3 3 0 3\n" "3 True True 4 3 1 4\n" "3 True True 5 3 2 5\n") self.ctx.locals.i.set_backdoor('nextpage', 'nextpage,i') self.expect_html( "3 True False 6 6 0 6\n" "3 True False 7 6 1 7\n" "3 True False 8 6 2 8\n") self.ctx.locals.i.set_backdoor('prevpage', 'prevpage,i') self.expect_html( "3 True True 3 3 0 3\n" "3 True True 4 3 1 4\n" "3 True True 5 3 2 5\n") class IteratorSuite(unittest.TestSuite): test_list = ( "check_iterator", "check_var", "check_vars", "check_anonymous", "check_nested", "check_precreate", "check_unclosed", "check_missingname", "check_noseq", "check_colsdown", "check_colsacross", "check_paging", ) def __init__(self): unittest.TestSuite.__init__(self, map(IteratorCase, self.test_list)) def suite(): return IteratorSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/templates/form.py0000644000175000017500000002322010575641005017605 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Form functionality tests. # # $Id: form.py 8871 2007-03-14 00:29:57Z andrewm $ import unittest import albatross import albatross_test import time class InputCase(albatross_test.AlbatrossTestCase): template_dir = "form" def check_input_text(self): self.ctx.locals.spam = "canned meat product" self.html_test("input-text.html", '') def check_input_text_escaping(self): self.ctx.locals.spam = '&<>"' self.html_test("input-text.html", '') def check_input_text_zero(self): self.ctx.locals.spam = 0 self.html_test("input-text.html", '') def check_input_text_expr(self): self.ctx.locals.str = "canned meat product" self.html_test("input-text-expr.html", '') def check_input_text_expr_escaping(self): self.ctx.locals.str = '&<>"\'' self.html_test("input-text-expr.html", '') def check_input_nameexpr_text(self): self.ctx.locals.str = 'spam' self.ctx.locals.num = 69 self.ctx.locals.spam_69 = 'canned meat product' self.html_test("input-nameexpr-text.html", '') def check_input_password(self): self.html_test("input-password.html", '') def check_input_radio(self): self.ctx.locals.swallow = 'African' self.html_test("input-radio.html", """\ """) def check_input_radio_expr(self): self.ctx.locals.str = 'African' self.html_test("input-radio-expr.html", """\ """) def check_input_radio_valueexpr(self): self.ctx.locals.str = 'African' self.html_test("input-radio-valueexpr.html", """\ """) def check_input_checkbox(self): self.ctx.locals.selected = ['two','four'] self.ctx.locals.vexpr = 'four' self.html_test("input-checkbox.html", """\ """) def check_missing_multiple(self): self.test_raise("missing-multiple.html") def check_illegal_multiple(self): self.test_raise("illegal-multiple.html") def check_defined_single(self): self.test_raise("defined-single.html") def check_defined_multiple(self): self.test_raise("defined-multiple.html") def check_multiple_mixed_implicit(self): self.test_raise("multiple-mixed-implicit.html") def check_missing_name(self): self.test_raise("missing-name.html") def check_null_name(self): self.test_raise("null-name.html") class InputSuite(unittest.TestSuite): test_list = ( "check_input_text", "check_input_text_escaping", "check_input_text_zero", "check_input_text_expr", "check_input_text_expr_escaping", "check_input_nameexpr_text", "check_input_password", "check_input_radio", "check_input_radio_expr", "check_input_radio_valueexpr", "check_input_checkbox", "check_missing_multiple", "check_illegal_multiple", "check_defined_single", "check_defined_multiple", "check_multiple_mixed_implicit", "check_missing_name", "check_null_name", ) def __init__(self): unittest.TestSuite.__init__(self, map(InputCase, self.test_list)) class SelectCase(albatross_test.AlbatrossTestCase): template_dir = "form" def check_select(self): self.ctx.locals.sel = 'spam' self.html_test("select.html", '') def check_select_evaluate(self): self.ctx.locals.sel = 'spam' self.ctx.locals.val = 'spam' self.html_test("select-evaluate.html", '') def check_select_value(self): self.ctx.locals.sel = 'spam' self.html_test("select-value.html", '') def check_select_for(self): self.ctx.locals.sel = 'eggs' self.ctx.locals.values = 'spam', 'eggs' self.html_test("select-for.html", '') def check_select_for_pair(self): self.ctx.locals.sel = 'eggs' self.ctx.locals.values = ('spam', 'canned meat product'), 'eggs' self.html_test("select-for.html", '') def check_select_for_label(self): self.ctx.locals.sel = 'eggs' self.ctx.locals.values = ('spam', 'canned meat product'), ('eggs', None) self.html_test("select-for-label.html", '') def check_select_for_content(self): self.ctx.locals.sel = 'eggs' self.ctx.locals.values = ('spam', 'canned meat product'), ('eggs', '') self.html_test("select-for-content.html", '') def check_select_select(self): self.test_raise('select-select.html') def check_bare_option(self): self.test_raise('bare-option.html') def check_select_name_expr(self): self.ctx.locals.val = 'spam' self.html_test("select-name-expr.html", '') def check_select_optionexpr(self): self.ctx.locals.sel = '3' self.ctx.locals.opt = range(5) self.html_test("select-optionexpr.html", """\ """) def check_select_optionexpr_pairs(self): self.ctx.locals.sel = '2' self.ctx.locals.pairs = [(1, 'Spam'), (2, 'Eggs'), (3, 'Bacon')] self.html_test("select-optionexpr-pairs.html", """\ """) def check_select_optionexpr_escaping(self): self.ctx.locals.sel = '<' self.ctx.locals.opt = ['&', '<', '>', '"'] self.html_test("select-optionexpr.html", """\ """) def check_select_optionexpr_pair_escaping(self): self.ctx.locals.sel = '<' self.ctx.locals.pairs = [('&', '&'), ('<', '<'), ('>', '>'), ('"', '"')] self.html_test("select-optionexpr-pairs.html", """\ """) class SelectSuite(unittest.TestSuite): test_list = ( "check_select", "check_select_evaluate", "check_select_value", "check_select_for", "check_select_for_pair", "check_select_for_label", "check_select_for_content", "check_select_select", "check_bare_option", "check_select_name_expr", "check_select_optionexpr", "check_select_optionexpr_pairs", "check_select_optionexpr_escaping", "check_select_optionexpr_pair_escaping", ) def __init__(self): unittest.TestSuite.__init__(self, map(SelectCase, self.test_list)) class TextareaCase(albatross_test.AlbatrossTestCase): template_dir = "form" def check_textarea(self): self.html_test("textarea.html", '') def check_textarea_fromapp(self): self.ctx.locals.msg = "content\n" self.html_test("textarea-fromapp.html", '') def check_textarea_fromapp_escaping(self): self.ctx.locals.msg = '&<>"' self.html_test("textarea-fromapp.html", '') class TextareaSuite(unittest.TestSuite): test_list = ( "check_textarea", "check_textarea_fromapp", "check_textarea_fromapp_escaping", ) def __init__(self): unittest.TestSuite.__init__(self, map(TextareaCase, self.test_list)) class FormCase(albatross_test.AlbatrossTestCase): template_dir = "form" def check_nested_form(self): self.test_raise("nested-form.html") class FormSuite(unittest.TestSuite): test_list = ( 'check_nested_form', ) def __init__(self): unittest.TestSuite.__init__(self, map(FormCase, self.test_list)) self.addTest(InputSuite()) self.addTest(SelectSuite()) self.addTest(TextareaSuite()) def suite(): return FormSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/templates/__init__.py0000644000175000017500000000173710210756621020407 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Build and test all listed modules. # # The module files must have a function "suite" which returns # an instance of unittest.TestSuite or unittest.TestCase. # # $Id: __init__.py 6204 2005-03-01 03:12:17Z andrewm $ import unittest class AllTestSuite(unittest.TestSuite): # add modules with tests here all_tests = [ "basic", "control_flow", "env", "form", "include", "iterator", "lookup", "macro", "tree", "whitespace", "require", ] def __init__(self): unittest.TestSuite.__init__(self) for module_name in self.all_tests: module = __import__(module_name, globals()) self.addTest(module.suite()) def suite(): return AllTestSuite() if __name__ == '__main__': unittest.main(defaultTest='AllTestSuite') albatross-1.36/test/templates/all.py0000644000175000017500000000155007521440537017420 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Build and test all listed modules. # # The module files must have a function "suite" which returns # an instance of unittest.TestSuite or unittest.TestCase. # # $Id: all.py 5912 2002-07-30 07:33:51Z andrewm $ import unittest class AllTestSuite(unittest.TestSuite): # add modules with tests here all_tests = [ "basic", "control_flow", "env", "form", "include", "iterator", "lookup", "macro", "tree", "whitespace" ] def __init__(self): unittest.TestSuite.__init__(self) for m in map(__import__, self.all_tests): self.addTest(m.suite()) if __name__ == '__main__': unittest.main(defaultTest='AllTestSuite') albatross-1.36/test/templates/include.py0000644000175000017500000000206410575433235020274 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Include functionality tests. # # $Id: include.py 8851 2007-03-13 05:29:01Z andrewm $ import unittest import albatross import albatross_test class IncludeCase(albatross_test.AlbatrossTestCase): template_dir = "include" def check_name(self): self.html_test("name.html", "plain\n") def check_expr(self): self.ctx.locals.filename = "name.html" self.html_test("expr.html", "plain\n") def check_nested(self): self.html_test("nested.html", "plain\n") def check_nxfile(self): self.test_raise("incnxfile.html") class IncludeSuite(unittest.TestSuite): test_list = ( "check_name", "check_expr", "check_nested", "check_nxfile", ) def __init__(self): unittest.TestSuite.__init__(self, map(IncludeCase, self.test_list)) def suite(): return IncludeSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/templates/.cvsignore0000644000175000017500000000000607327242620020266 0ustar andrewmandrewm*.pyc albatross-1.36/test/templates/whitespace.py0000644000175000017500000000234510575433235021007 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Test whitespace. # # $Id: whitespace.py 8851 2007-03-13 05:29:01Z andrewm $ import unittest import albatross import albatross_test class WhitespaceCase(albatross_test.AlbatrossTestCase): template_dir = "whitespace" def check_default_ws(self): self.html_test("default-ws.html", "123") def check_strip(self): self.html_test("strip.html", "123") def check_indent(self): self.html_test("indent.html", "1 2 3") def check_newline(self): self.html_test("newline.html", "1\n2\n3") def check_all(self): self.html_test("all.html", "1\n 2\n \t3") def check_bogus(self): self.test_raise("bogus.html") class WhitespaceSuite(unittest.TestSuite): test_list = ( "check_default_ws", "check_strip", "check_indent", "check_newline", "check_all", "check_bogus", ) def __init__(self): unittest.TestSuite.__init__(self, map(WhitespaceCase, self.test_list)) def suite(): return WhitespaceSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/templates/macro/0000755000175000017500000000000010577422307017376 5ustar andrewmandrewmalbatross-1.36/test/templates/macro/0arg.html0000644000175000017500000000011307327462107021111 0ustar andrewmandrewm content albatross-1.36/test/templates/macro/argexpr.html0000644000175000017500000000024610447653355021743 0ustar andrewmandrewm xxx xxx albatross-1.36/test/templates/macro/missingarg.html0000644000175000017500000000023507327471067022435 0ustar andrewmandrewm xxx xxx argument albatross-1.36/test/templates/macro/2arg.html0000644000175000017500000000033407327471067021125 0ustar andrewmandrewm xxx xxx first second albatross-1.36/test/templates/macro/1arg.html0000644000175000017500000000023207327471067021121 0ustar andrewmandrewm xxx xxx argument albatross-1.36/test/templates/macro/unnamedarg.html0000644000175000017500000000020610446724413022401 0ustar andrewmandrewm xxx argument body albatross-1.36/test/templates/macro/default.html0000644000175000017500000000035710447672641021721 0ustar andrewmandrewm first aaa bbb catflap albatross-1.36/test/templates/macro/nested.html0000644000175000017500000000051010446724413021540 0ustar andrewmandrewm bbb ccc ddd aaa first second albatross-1.36/test/templates/macro/unclosed.html0000644000175000017500000000011207327471067022100 0ustar andrewmandrewm content albatross-1.36/test/templates/macro/defaultnested.html0000644000175000017500000000072110447672641023117 0ustar andrewmandrewm first third bbb ccc ddd eee second aaa catflap albatross-1.36/test/templates/macro/0arg-dup.html0000644000175000017500000000014707327462107021706 0ustar andrewmandrewm content albatross-1.36/test/templates/macro/unclosedarg.html0000644000175000017500000000021607327471067022577 0ustar andrewmandrewm xxx xxx argument albatross-1.36/test/templates/macro/simplearg.html0000644000175000017500000000023610447653355022255 0ustar andrewmandrewm xxx xxx albatross-1.36/test/templates/macro/unnamed.html0000644000175000017500000000015610360637210021704 0ustar andrewmandrewm xxx xxx unnamed body albatross-1.36/test/templates/whitespace/0000755000175000017500000000000010577422307020431 5ustar andrewmandrewmalbatross-1.36/test/templates/whitespace/strip.html0000644000175000017500000000014507327751263022465 0ustar andrewmandrewm albatross-1.36/test/templates/whitespace/indent.html0000644000175000017500000000014707327751263022607 0ustar andrewmandrewm albatross-1.36/test/templates/whitespace/all.html0000644000175000017500000000015207416540442022064 0ustar andrewmandrewm albatross-1.36/test/templates/whitespace/bogus.html0000644000175000017500000000007407327751263022444 0ustar andrewmandrewm albatross-1.36/test/templates/whitespace/default-ws.html0000644000175000017500000000007707327751263023403 0ustar andrewmandrewm albatross-1.36/test/templates/whitespace/newline.html0000644000175000017500000000015107327751263022762 0ustar andrewmandrewm albatross-1.36/test/templates/basic/0000755000175000017500000000000010577422307017356 5ustar andrewmandrewmalbatross-1.36/test/templates/basic/var-value-noescape.html0000644000175000017500000000003507416540442023736 0ustar andrewmandrewm albatross-1.36/test/templates/basic/var-value.html0000644000175000017500000000002407327677331022152 0ustar andrewmandrewm albatross-1.36/test/templates/basic/date.html0000644000175000017500000000010307327677331021163 0ustar andrewmandrewm albatross-1.36/test/templates/basic/comment.html0000644000175000017500000000010207416540442021676 0ustar andrewmandrewmone two four albatross-1.36/test/templates/basic/value-no-expr.html0000644000175000017500000000001307330150204022723 0ustar andrewmandrewm albatross-1.36/test/templates/basic/exec.html0000644000175000017500000000030707416540442021167 0ustar andrewmandrewm albatross-1.36/test/templates/basic/const-value.html0000644000175000017500000000002507327677331022511 0ustar andrewmandrewm albatross-1.36/test/templates/basic/attr-quote.html0000644000175000017500000000014407426515241022347 0ustar andrewmandrewm albatross-1.36/test/templates/basic/file.html0000644000175000017500000000000607327510051021150 0ustar andrewmandrewmplain albatross-1.36/test/templates/basic/value-no-expr-sp.html0000644000175000017500000000001407330150204023344 0ustar andrewmandrewm albatross-1.36/test/templates/basic/null-attr.html0000644000175000017500000000004207547233134022163 0ustar andrewmandrewm albatross-1.36/test/templates/env.py0000644000175000017500000000170707521440537017444 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Test environment settings. # # $Id: env.py 5912 2002-07-30 07:33:51Z andrewm $ import unittest import albatross import albatross_test import os class EnvCase(albatross_test.AlbatrossTestCase): template_dir = "env" # overload html_test() to drop environment into local context def html_test(self, template_file, expect): self.ctx.locals.env = os.environ albatross_test.AlbatrossTestCase.html_test(self, template_file, expect) def check_home(self): self.html_test("home.html", os.environ['HOME']) class EnvSuite(unittest.TestSuite): test_list = ( "check_home", ) def __init__(self): unittest.TestSuite.__init__(self, map(EnvCase, self.test_list)) def suite(): return EnvSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/templates/env/0000755000175000017500000000000010577422307017065 5ustar andrewmandrewmalbatross-1.36/test/templates/env/home.html0000644000175000017500000000003607330152770020677 0ustar andrewmandrewm albatross-1.36/test/templates/lookup/0000755000175000017500000000000010577422307017606 5ustar andrewmandrewmalbatross-1.36/test/templates/lookup/inline.html0000644000175000017500000000021310236352725021744 0ustar andrewmandrewm Zero One Two Many albatross-1.36/test/templates/lookup/no-close.html0000644000175000017500000000017510236352725022214 0ustar andrewmandrewm Zero One albatross-1.36/test/templates/lookup/lookup.html0000644000175000017500000000026010236352725022001 0ustar andrewmandrewm Zero One Two Many albatross-1.36/test/templates/lookup/no-default.html0000644000175000017500000000021210236352725022523 0ustar andrewmandrewm Zero One albatross-1.36/test/templates/control_flow/0000755000175000017500000000000010577422307021004 5ustar andrewmandrewmalbatross-1.36/test/templates/control_flow/if-elif-else.html0000644000175000017500000000013207327242620024124 0ustar andrewmandrewm zero positive negative albatross-1.36/test/templates/control_flow/if-elif-fail.html0000644000175000017500000000015107327501203024103 0ustar andrewmandrewm zero positive negative albatross-1.36/test/templates/tree.py0000644000175000017500000001516510231152466017607 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Test tree constructs. # # $Id: tree.py 6207 2005-04-19 09:53:58Z andrewm $ import unittest import albatross import albatross_test one_we_prepared_earlier="""\ -a |-b | |-c | \-d \-e |-f | \-g | |-h | \-i |-j \-k |-l \-m """ tree_input_folded="""\
a
""" tree_input_unfolded="""\
a
b
e
f
j
k
""" tree_input_unfolded_selected="""\
a
b
e
f
j
k
""" class SimpleNode: def __init__(self, name, children = None): self.name = name if children is not None: self.children = children class LazyNode: def __init__(self, name, children = None): self.name = name if children is not None: self.unloaded_children = children self.children = [] self.children_loaded = 0 def load_children(self,ctx): self.children = self.unloaded_children def albatross_alias(self): return self.name def build_tree(Ctor): return Ctor('a', [Ctor('b', [Ctor('c'), Ctor('d')]), Ctor('e', [Ctor('f', [Ctor('g', [Ctor('h'), Ctor('i')])]), Ctor('j'), Ctor('k', [Ctor('l'), Ctor('m')])])]) class TreeCase(albatross_test.AlbatrossTestCase): template_dir = "tree" def check_tree(self): self.ctx.locals.tree = build_tree(SimpleNode) self.html_test("tree.html", one_we_prepared_earlier) def check_tree_lazy(self): self.ctx.locals.tree = build_tree(LazyNode) self.html_test("tree-lazy.html", "-a\n") self.ctx.locals.n.set_backdoor('treefold','a',1) self.html_test("tree-lazy.html", "-a\n |-b\n \-e\n") def check_tree_input(self): self.ctx.locals.tree = build_tree(LazyNode) self.html_test("tree-input.html", tree_input_folded) self.ctx.locals.n.set_backdoor('treefold','a',1) self.ctx.locals.n.set_backdoor('treefold','e',1) self.html_test("tree-input.html", tree_input_unfolded) self.ctx.locals.n.set_backdoor('treeselect','b',1) self.ctx.locals.n.set_backdoor('treeselect','j',1) self.html_test("tree-input.html", tree_input_unfolded_selected) class TreeSuite(unittest.TestSuite): test_list = ( "check_tree", "check_tree_lazy", "check_tree_input", ) def __init__(self): unittest.TestSuite.__init__(self, map(TreeCase, self.test_list)) def suite(): return TreeSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/templates/basic.py0000644000175000017500000000522210575433235017731 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Basic functionality tests. # # $Id: basic.py 8851 2007-03-13 05:29:01Z andrewm $ import unittest import albatross import albatross_test import time class BasicCase(albatross_test.AlbatrossTestCase): template_dir = "basic" def check_file(self): self.html_test("file.html", "plain\n") def check_nxfile(self): self.test_raise("nxfile.html") def check_const_value(self): self.html_test("const-value.html", "27") def check_null_attr(self): self.html_test("null-attr.html", '') def check_attr_quote(self): self.html_test("attr-quote.html", "abcdefghijkl") def check_value_no_expr(self): self.test_raise("value-no-expr.html") def check_value_no_expr_sp(self): self.test_raise("value-no-expr-sp.html") def check_var_value(self): self.ctx.locals.x = 17 self.html_test("var-value.html", "17") def check_var_float(self): self.ctx.locals.x = 2.1 self.html_test("var-value.html", "2.1") def check_var_string(self): self.ctx.locals.x = "abc" self.html_test("var-value.html", "abc") def check_var_escape(self): self.ctx.locals.x = '' self.html_test("var-value.html", "<img src="http://rude.pictures.r.us/">") def check_var_noescape(self): self.ctx.locals.x = '' self.html_test("var-value-noescape.html", '') def check_date(self): t = time.strftime("%A, %B %d, %Y, %I:%M%p", time.localtime(996113203.67031395)) self.html_test("date.html", t) def check_comment(self): self.html_test("comment.html", "one\nfour\n") def check_exec(self): self.html_test("exec.html", " 2 3 5 7") class BasicSuite(unittest.TestSuite): test_list = ( "check_file", "check_nxfile", "check_const_value", "check_null_attr", "check_attr_quote", "check_value_no_expr", "check_value_no_expr_sp", "check_var_value", "check_var_float", "check_var_string", "check_var_escape", "check_var_noescape", "check_date", "check_comment", "check_exec", ) def __init__(self): unittest.TestSuite.__init__(self, map(BasicCase, self.test_list)) def suite(): return BasicSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/templates/tree/0000755000175000017500000000000010577422307017234 5ustar andrewmandrewmalbatross-1.36/test/templates/tree/tree-lazy.html0000644000175000017500000000053207417216413022034 0ustar andrewmandrewm | \ - albatross-1.36/test/templates/tree/tree-input.html0000644000175000017500000000135507475435342022230 0ustar andrewmandrewm
" nowrap>
albatross-1.36/test/templates/tree/tree.html0000644000175000017500000000052507416540442021062 0ustar andrewmandrewm | \ - albatross-1.36/test/templates/macro.py0000644000175000017500000000471410575166531017760 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Test macros. # # $Id: macro.py 8842 2007-03-12 06:04:09Z andrewm $ import unittest import albatross import albatross_test class MacroCase(albatross_test.AlbatrossTestCase): template_dir = "macro" def check_macro_0arg(self): self.html_test("0arg.html", "content\n") def check_macro_0arg_dup(self): self.html_test("0arg-dup.html", "content\ncontent\n") def check_macro_1arg(self): self.html_test("1arg.html", "xxx argument xxx\n") def check_macro_2arg(self): self.html_test("2arg.html", "xxx first second xxx\n") def check_macro_simplearg(self): self.html_test("simplearg.html", "xxx aaa bbb xxx\n") def check_macro_argexpr(self): self.ctx.locals.aaa = 'first' self.ctx.locals.bbb = 'second' self.html_test("argexpr.html", "xxx first second xxx\n") def check_macro_unnamed(self): self.html_test("unnamed.html", "xxx unnamed body xxx\n") def check_macro_unnamedarg(self): self.html_test("unnamedarg.html", "xxx argument") def check_macro_nested(self): self.html_test("nested.html", "aaa second bbb first ccc second ddd\n") def check_macro_default(self): self.html_test("default.html", "aaa first bbb\naaa catflap bbb\n") def check_macro_defaultnested(self): self.html_test("defaultnested.html", "aaa second bbb first ccc second ddd catflap eee\n") def check_macro_unclosed(self): self.test_raise("unclosed.html") def check_macro_unclosedarg(self): self.test_raise("unclosedarg.html") def check_macro_missingarg(self): self.test_raise("missingarg.html") class MacroSuite(unittest.TestSuite): test_list = ( "check_macro_0arg", "check_macro_0arg_dup", "check_macro_1arg", "check_macro_2arg", "check_macro_simplearg", "check_macro_argexpr", "check_macro_unnamed", "check_macro_nested", "check_macro_default", "check_macro_defaultnested", "check_macro_unclosed", "check_macro_unclosedarg", "check_macro_missingarg", "check_macro_unnamedarg", ) def __init__(self): unittest.TestSuite.__init__(self, map(MacroCase, self.test_list)) def suite(): return MacroSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/templates/albatross_test.py0000644000175000017500000000747510575705072021716 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Base class for testing Albatross functionality. # # Sub-classes are expected to define a class variable "template_dir" which # tells albatross which directory to search for the html template files. # # There is also a class variable, self.ctx, available to classes derived # from this which contains the current Albatross execution context. # # $Id: albatross_test.py 8878 2007-03-14 05:38:02Z andrewm $ import unittest import albatross import sys import os class AlbatrossTestError(Exception): pass class TestContext(albatross.AppContext, albatross.NameRecorderMixin, albatross.StubSessionMixin): def __init__(self, app): albatross.AppContext.__init__(self, app) albatross.NameRecorderMixin.__init__(self) def current_url(self): return 'here' class TestApp(albatross.TemplateLoaderMixin, albatross.Application): def __init__(self, base_url): albatross.TemplateLoaderMixin.__init__(self, base_url) albatross.Application.__init__(self, base_url) class AlbatrossTestCase(unittest.TestCase): def setUp(self): if not hasattr(self, "template_dir"): raise AlbatrossTestError("%s: template_dir not defined in class" % self.__class__) # This is because we may not be imported from the current directory base_dir = os.path.dirname(sys.modules[__name__].__file__) template_dir = os.path.join(base_dir, self.template_dir) # We create a new app instance for each test because macros and lookups # are cached on the app object. app = TestApp(template_dir) self.ctx = TestContext(app) def tearDown(self): pass def load_template_file(self, template): """ Template from file """ self.tmpl = self.ctx.load_template(template) def load_template_str(self, template): """ Template from string """ self.tmpl = albatross.Template(self.ctx, '', template) def gen_html(self): """ Run the template and capture the generated HTML """ self.ctx.push_content_trap() self.tmpl.to_html(self.ctx) return self.ctx.pop_content_trap() def expect_html(self, expect): result = self.gen_html() self.assertEqual(result, expect, 'expected "%s", got "%s"' % (expect, result)) def html_test(self, template_file, expect): """ Given a template file name and a string of expected results, compare the rendered template with the results """ self.load_template_file(template_file) self.expect_html(expect) def test_str_html(self, template, expect): """ Given a template in a string, and a string of expected results, compare the rendered template with the results """ self.load_template_str(template) self.expect_html(expect) def expect_raise(self, loader, template, exception=None): if exception is None: exception = albatross.ApplicationError try: loader(template) result = self.gen_html() except exception: pass else: if hasattr(exception, '__name__'): name = exception.__name__ else: name = str(exception) raise self.failureException('%s not raised, got "%s"' % (name, result)) def test_raise(self, template_file, exception=None): self.expect_raise(self.load_template_file, template_file, exception) def test_str_raise(self, template, exception=None): self.expect_raise(self.load_template_str, template, exception) albatross-1.36/test/templates/form/0000755000175000017500000000000010577422307017240 5ustar andrewmandrewmalbatross-1.36/test/templates/form/select-evaluate.html0000644000175000017500000000015707416540442023213 0ustar andrewmandrewm eggs albatross-1.36/test/templates/form/select.html0000644000175000017500000000013607416540442021404 0ustar andrewmandrewm spam eggs albatross-1.36/test/templates/form/input-password.html0000644000175000017500000000006507416743345023134 0ustar andrewmandrewm albatross-1.36/test/templates/form/null-name.html0000644000175000017500000000007007674332700022014 0ustar andrewmandrewm albatross-1.36/test/templates/form/missing-multiple.html0000644000175000017500000000027507477324336023444 0ustar andrewmandrewm albatross-1.36/test/templates/form/input-radio.html0000644000175000017500000000020507477324336022366 0ustar andrewmandrewm albatross-1.36/test/templates/form/multiple-mixed-implicit.html0000644000175000017500000000013610575641005024671 0ustar andrewmandrewm albatross-1.36/test/templates/form/defined-multiple.html0000644000175000017500000000014207525650103023346 0ustar andrewmandrewm albatross-1.36/test/templates/form/select-optionexpr.html0000644000175000017500000000005107416540442023605 0ustar andrewmandrewm albatross-1.36/test/templates/form/nested-form.html0000644000175000017500000000007107674271135022354 0ustar andrewmandrewm

albatross-1.36/test/templates/form/input-nameexpr.html0000644000175000017500000000005307416743345023106 0ustar andrewmandrewm albatross-1.36/test/templates/form/input-radio-valueexpr.html0000644000175000017500000000011507416743345024375 0ustar andrewmandrewm albatross-1.36/test/templates/form/textarea.html0000644000175000017500000000006107416743345021746 0ustar andrewmandrewm content albatross-1.36/test/templates/form/bare-option.html0000644000175000017500000000003310575166531022343 0ustar andrewmandrewmxxx albatross-1.36/test/templates/form/select-name-expr.html0000644000175000017500000000015110231152466023265 0ustar andrewmandrewm spam eggs albatross-1.36/test/templates/form/illegal-multiple.html0000644000175000017500000000021707525650103023364 0ustar andrewmandrewm albatross-1.36/test/templates/form/input-nameexpr-text.html0000644000175000017500000000006707416743345024075 0ustar andrewmandrewm albatross-1.36/test/templates/form/input-text.html0000644000175000017500000000002707416743345022254 0ustar andrewmandrewm albatross-1.36/test/templates/form/select-for-content.html0000644000175000017500000000025210575372616023645 0ustar andrewmandrewm albatross-1.36/test/templates/form/textarea-fromapp.html0000644000175000017500000000003207416743345023406 0ustar andrewmandrewm albatross-1.36/test/templates/form/select-optionexpr-pairs.html0000644000175000017500000000005307416540442024723 0ustar andrewmandrewm albatross-1.36/test/templates/form/defined-single.html0000644000175000017500000000014207525650103022774 0ustar andrewmandrewm albatross-1.36/test/templates/form/select-select.html0000644000175000017500000000011010575166531022654 0ustar andrewmandrewm albatross-1.36/test/templates/form/input-checkbox.html0000644000175000017500000000052407525650103023046 0ustar andrewmandrewm albatross-1.36/test/templates/form/select-value.html0000644000175000017500000000017207416540442022516 0ustar andrewmandrewm canned meat product eggs albatross-1.36/test/templates/form/select-for.html0000644000175000017500000000016610575166531022176 0ustar andrewmandrewm albatross-1.36/test/templates/form/select-for-label.html0000644000175000017500000000022210575372616023247 0ustar andrewmandrewm albatross-1.36/test/templates/form/missing-name.html0000644000175000017500000000005207674332700022513 0ustar andrewmandrewm albatross-1.36/test/templates/form/input-radio-expr.html0000644000175000017500000000023307477324336023343 0ustar andrewmandrewm albatross-1.36/test/templates/form/input-text-expr.html0000644000175000017500000000004207416743345023225 0ustar andrewmandrewm albatross-1.36/test/templates/require.py0000644000175000017500000000244510575705072020331 0ustar andrewmandrewm# # Copyright 2004 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Test "require" tag # # $Id: require.py 8878 2007-03-14 05:38:02Z andrewm $ import unittest import albatross import albatross_test class RequireCase(albatross_test.AlbatrossTestCase): template_dir = "require" def check_require(self): self.test_str_html("", "") self.test_str_raise("") def check_require_version(self): fmt = "" self.test_str_html(fmt % albatross.template_version, "") self.test_str_html(fmt % (albatross.template_version - 1), "") self.test_str_raise(fmt % (albatross.template_version + 1)) def check_require_feature(self): self.test_str_html("", "") self.test_str_raise("") class RequireSuite(unittest.TestSuite): test_list = ( "check_require", "check_require_version", "check_require_feature", ) def __init__(self): unittest.TestSuite.__init__(self, map(RequireCase, self.test_list)) def suite(): return RequireSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/templates/include/0000755000175000017500000000000010577422307017720 5ustar andrewmandrewmalbatross-1.36/test/templates/include/incnxfile.html0000644000175000017500000000004007416540442022556 0ustar andrewmandrewm albatross-1.36/test/templates/include/nested.html0000644000175000017500000000003607416540442022066 0ustar andrewmandrewm albatross-1.36/test/templates/include/plain.html0000644000175000017500000000000607416540442021704 0ustar andrewmandrewmplain albatross-1.36/test/templates/include/name.html0000644000175000017500000000003707416540442021525 0ustar andrewmandrewm albatross-1.36/test/templates/include/expr.html0000644000175000017500000000003507416540442021561 0ustar andrewmandrewm albatross-1.36/test/templates/control_flow.py0000644000175000017500000000235710575433235021365 0ustar andrewmandrewm# # Copyright 2001 by Object Craft P/L, Melbourne, Australia. # # LICENCE - see LICENCE file distributed with this software for details. # # # Test if-elif-else flow constructs. # # $Id: control_flow.py 8851 2007-03-13 05:29:01Z andrewm $ import unittest import albatross import albatross_test class IfThenElifElseCase(albatross_test.AlbatrossTestCase): template_dir = "control_flow" def html_test(self, template, value, expect): self.ctx.locals.x = value albatross_test.AlbatrossTestCase.html_test(self, template, expect) def check_if(self): self.html_test("if-elif-else.html", 0, "zero\n") def check_elif(self): self.html_test("if-elif-else.html", 1, "positive\n") def check_else(self): self.html_test("if-elif-else.html", -1, "negative\n") def check_unclosed(self): self.test_raise("if-elif-fail.html") class ControlFlowSuite(unittest.TestSuite): test_list = ( "check_if", "check_elif", "check_else", "check_unclosed" ) def __init__(self): unittest.TestSuite.__init__(self, map(IfThenElifElseCase, self.test_list)) def suite(): return ControlFlowSuite() if __name__ == '__main__': unittest.main(defaultTest='suite') albatross-1.36/test/templates/iterator/0000755000175000017500000000000010577422307020126 5ustar andrewmandrewmalbatross-1.36/test/templates/iterator/vars.html0000644000175000017500000000015410575672025021771 0ustar andrewmandrewm albatross-1.36/test/templates/iterator/down.html0000644000175000017500000000020407335507260021757 0ustar andrewmandrewm+ + albatross-1.36/test/templates/iterator/iterator.html0000644000175000017500000000011710575725155022651 0ustar andrewmandrewm albatross-1.36/test/templates/iterator/var.html0000644000175000017500000000010110575672025021576 0ustar andrewmandrewm albatross-1.36/test/templates/iterator/nested.html0000644000175000017500000000022307327500256022272 0ustar andrewmandrewm albatross-1.36/test/templates/iterator/unclosed.html0000644000175000017500000000007707327500256022633 0ustar andrewmandrewm albatross-1.36/test/templates/iterator/paging.html0000644000175000017500000000043110575705072022260 0ustar andrewmandrewm albatross-1.36/test/templates/iterator/noseq.html0000644000175000017500000000007110575672025022141 0ustar andrewmandrewm albatross-1.36/test/templates/iterator/anonymous.html0000644000175000017500000000004310575672025023043 0ustar andrewmandrewma albatross-1.36/test/templates/iterator/missingname.html0000644000175000017500000000010510575672025023324 0ustar andrewmandrewm albatross-1.36/test/templates/iterator/precreate.html0000644000175000017500000000010210575672025022761 0ustar andrewmandrewm albatross-1.36/test/templates/iterator/across.html0000644000175000017500000000022207335507260022302 0ustar andrewmandrewm+ +