[Web-SIG] PasteDeploy 0.1

Phillip J. Eby pje at telecommunity.com
Tue Aug 23 05:12:44 CEST 2005


At 09:03 PM 8/22/2005 -0500, Ian Bicking wrote:
>One aspect of paste.deploy that wasn't shown in that example is that it's 
>easy to refer to other configuration files.  It would actually be more 
>realistic to do:
>
>   [composit:app]
>   use = egg:Paste#urlmap
>   / = config:static_root.ini
>   /cms = config:filebrowser.ini
>   /blog = config:blog.ini

In the minimum case, one could do that without any change to the syntax I 
proposed:

     [static = file]
     filename = "static_root.ini"

But I think it would be nicer to just provide a "builtin function" to load 
a component from a file, since it's common enough to deserve a primitive.


>And if filebrowser.ini defined an authentication filter named "auth", you 
>could add this to blog.ini to reuse that configuration:
>
>   [filter-app:main]
>   use = config:filebrowser.ini#auth
>   next = blog

One of the things I really dislike about the PasteDeploy syntax is that it 
mingles factory arguments with chaining, which seems like mixing metalevels 
to me; I never know whether an argument is intended for the parser (e.g. 
use, next) or for the factory.  My blog.ini would look like this (in its 
entirety):

    [file]
    # Borrow the filebrowser 'auth' wrapper
    filename = "filebrowser.ini"
    factory = "auth"

    [myglue.apps:blog_app]


>However, in paste.deploy there does remain real global configuration, so 
>you wouldn't have to manually copy in values from the globals.  While 
>admittedly it makes the interface slightly less elegant from the Python 
>side, I think it's an important feature.

That's easily emulated if you need it; just create a configuration service 
or services that can be acquired via the parent_component links.  Actually, 
the format I propose allows numerous other ways to emulate that feature on 
varying scales, but doesn't force all factories to understand any one 
specific configuration protocol.


>>* Argument names can be either an identifier or a quoted string
>
>I tried to avoid anything fancy; if I was going to do something fancy I'd 
>feel a need to look at all the configuration formats currently for Python, 
>and if not reuse them at least steal from them.
>
>But it's clear that plain ConfigParser parsing is pretty lame.

The only reason for allowing string literals is to avoid coming up with a 
lame new escaping scheme for use cases like the URL map.


>>* You can use factories from a default group (e.g. 'vars' above might 
>>effectively be short for 'vars from WSGIUtils')
>
>How is that default group determined?  What is a "group"?

Er, sorry, I meant entry point group, like "wsgi.factories" or whatever.  I 
was just pointing out that the meaning of a non-import string could be 
loaded from an entry point group, and that the group might vary depending 
on the application loading the configuration file.


>>* named sections ("[name = ...]") have to come after the unnamed 
>>sections, and they are turned into "curried" factory objects that are 
>>available in the eval() namespace used for all expressions.  When called 
>>in an expression, they can accept keyword arguments to override the 
>>defaults in the named section.  They have properties with the same names 
>>as the values defined in that section.
>
>The properties are fine; I can't say the calling syntax appeals to me 
>particularly.

I thought about *not* calling them (except for wrappers), but then the 
properties would have to go.


>>* The first part of a section (after the "name=", if any) is an import 
>>spec for a factory, or if it's followed by "from" or "wrapper from", then 
>>it's the name of an entry point that advertises a factory.
>
>How do you determine the entry point type?  Or is there one entry point 
>type for anything available in a configuration file?  paste.deploy defines 
>an entry point type for each kind of object.

I'm thinking that the loader gets passed some arguments to determine what 
entry point group to use.  This format, by the way, only requires one group 
for all the "normal" entry points, because the "wrapper" keyword 
distinguishes between the two factory signatures -- which are the only 
signatures you get.


>>* "wrapper" means that the factory will be called with two positional 
>>arguments; non-wrappers are called with one argument.  Named wrappers can 
>>be passed a positional argument if used in an another factory argument 
>>expression - this will be the object they should wrap.
>
>This part is unclear to me.

See the urlmap in the example, where "/blog" = auth(blog()).  'auth' is a 
"wrapper", so it can be called with something to wrap (e.g. 'blog()').


>* Using ordering in a syntax that doesn't feel ordered or nested.

Fair enough.  However, I'm used to ordered .ini files (they do exist), so 
I'm not sure that's enough on its own to rule out the syntax.  Also, we 
could nix the '[]' for section headings and come up with something else, e.g.:

      login wrapper from Paste:
        database = "mysql://localhost/userdb"
        table    = "users"

      urlmap from Paste:
        "/"     = static()
        "/cms"  = auth(filebrowser_app())
        "/blog" = blog()

      def config() as vars:
          admin_email = "me at example.com"
          document_root = "/home/me/htdocs"

      def static() as static from Paste:
          document_root = config.document_root

      def auth() as auth wrapper from Paste:
          require_role = "admin"
          admin_email = config.admin_email

      def filebrowser_app() as filebrowser from FileBrowser:
          document_root = config.document_root
          admin_email = config.admin_email

      def blog() as myglue.apps:blog_app:
          admin_email = config.admin_email


This probably wouldn't be any harder to parse than my initial proposal, as 
I was thinking of using the "tokenize" module, and in this case a DEDENT 
token would indicate the end of a section.  I'm not sure I like the 'def 
x()' bit, makes it look a little too much like Python, at the same time as 
it seems good to have it be like Python.


>* Using function composition to represent application/filter 
>composition.  But only sometimes.

Only sometimes you don't like it?  :)  Or do you mean that the format I 
gave only uses it sometimes, and that's what you dislike?  (i.e., you'd be 
fine if it was always done that way?)


>* "name from egg_spec" reads nice on one level, but is vague on another 
>level.  Even if "egg:egg_spec#name" doesn't read well, I think it is 
>nicely self-describing.

Um, wha???  The only difference between the two is that one of them has 
"egg:" in front of it, which seems a bit redundant to me.  That's probably 
because I assume that in the long run eggs will be so ubiquitous that it 
really will be redundant to explicitly refer to them as such.  :)

Conversely, if I assume that some further description is required, I would 
want to say "pypi:" or "project:" or something else of that sort, because 
"egg" isn't the essential nature of the thing; the name is a *project* 
name, while eggs are an implementation detail.


>* eval() scares me a bit; if I used eval() I would feel a need to keep 
>sufficient information around to do proper tracebacks that include the 
>source configuration file.

Sure, that can be done, especially if one does a couple of tricks with 
compile(), new.code(), and co_firstlineno.


>But all-strings isn't great either. Evaluation without conditionals seems 
>like it goes only half-way; OTOH conditionals get to something too complex 
>for configuration.  So however it goes, configuration should be somewhere 
>in the middle of completely dumb (ConfigParser, unevaluated values), and 
>completely general (Python code).  Where in the middle I'm unsure.

eval() isn't full Python code, and you can set a restricted set of builtins 
if you really want.  I don't see much point to dumbing it down any further 
than that, though.



More information about the Web-SIG mailing list