[Web-SIG] PasteDeploy 0.1

Ian Bicking ianb at colorstudy.com
Tue Aug 23 06:30:19 CEST 2005

Phillip J. Eby wrote:
> 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.

Definitely; files are the essence of configuration, file references 
should be a core concept.  (Of course ZODB objects can work much like 
files, or wherever else you put the configuration is likely to have a 
filesystem-like feel)

>> 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]

I'm fine getting rid of "use", maybe like:

   [filter-app:main config:filebrowser.ini#auth]

Well... upon writing it, it doesn't look that nice.  But in theory... or 
maybe something like:


Where the first (non-comment) line was interpreted as the thing being 
loaded.  That makes it look very different.  Or whatever; I don't feel 
that strongly about it.

>> 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.

It's important to me, and it's not intuitive to me what you envision. 
So I feel a need to services in action, replacing global configuration.

I'll admit, it felt a little funny to me when I converted middleware to 
have a global configuration parameter that they simply ignored.  But 
*not* having that access would bother me more.

>>> * 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.

In paste.deploy I allow for future groups that ultimately return the 
same kind of object, so the group adds important information. 
paste.composit_factory1 and paste.app_factory1 both return the same kind 
of object, for instance.

>>> * 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.

I like the properties more than the composition.

>>> * 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.

In paste.deploy the syntax (filter:, etc) and the group are redundant. 
So if you accidentally treat an application like a filter it'll be 
caught before you call the object with the wrong parameters.

I'm also realizing that positional parameters are bad; I think I'll be 
changing to calling with purely keyword parameters.  It's too easy to 
mix up positional parameters, and pass in the wrong object in the wrong 
location, and then it only gets caught later when you try to use the 
wrong object in a way it doesn't support.  That was one of the more 
common errors I produced when converting my code.

>>> * "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()').

But the wrapper there is called with one argument, and the app with 
zero; but you say the wrapper has two and the app one...?

>> * 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

That's not any different to me, I guess.  This would be a better use of 

main = pipeline:
   login wrapper from Paste:
   urlmap from Paste:
       "/" = static
       # for some reason this feels a lot better than
       # auth(filebrowser_app())) to me:
       "/cms" = pipeline(auth, filebrowser_app)
       "/blog" = blog

config = vars:
   admin_email = "me at example.com"
   document_root = "/home/me/htdocs"

static = static from Paste:
   document_root = config.document_root

# gotta admit I still really prefer "filter" to "wrapper"
auth = auth wrapper from Paste:
     require_role = "admin"
     admin_email = config.admin_email

filebrowser_app = filebrowser from FileBrowser:
     # better use of properties than config.document_root, really:
     document_root = static.document_root
     admin_email = config.admin_email

# or you could do the whole thing like:

filebrowser = pipeline:
     auth wrapper from Paste:
         require_role = "admin"
         admin_email = config.admin_email
     filebrowser from FileBrowser:
         document_root = static.document_root

# maybe you could specialize/clone like:

auth2 = auth:
     require_role = "editor"

That configuration feels way better to me.  The named applications are 
full peers with the unnamed applications/objects (and I think there just 
shouldn't be top-level unnamed applications).

>> * 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?)

I don't like the function calling at all, but even moreso because it's 
inconsistent (it isn't used for the main app).

>> * "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.  :)

In paste.deploy config files are full peers to Eggs, and can be used 
anywhere that an egg: reference can be used.  I think that's a neat 
feature.  I don't want to tack on referencing other config files, like a 
special loader factory or textual inclusion hacks or anything like that.

Config files describe applications.  Egg entry points describe 
applications.  They should be peers.

> 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.

egg: is an access method, just like http: or whatever.  It doesn't say 
what the URI describes, just how to find it.

Ian Bicking  /  ianb at colorstudy.com  / http://blog.ianbicking.org

More information about the Web-SIG mailing list