[PEAK] Making PyProtocols work for aeve

Phillip J. Eby pje at telecommunity.com
Tue Nov 25 14:16:46 EST 2003


At 12:58 PM 11/25/03 -0500, Bob Ippolito wrote:

>There are two APIs.. one API lets you do this:
>
>(convert to/from python as soon as possible)
>listOfNames = [x.name for x in app.someElements]
>
>the other API lets you do this:
>
>(convert to/from python as late as possible)
>listOfNames = app.someElements.all.name.get()
>
>The reason for this is that the first API will make more than 
>2*len(app.someElements) dispatches and the second case will happen in a 
>single dispatch (of a more complicated AEDesc).  There are huge 
>performance and convenience differences between both of these APIs.

Okay.  But all the AEDescs have 4-character type codes, right?  Even the 
"more complicated" ones?

It seems to me that there are two levels to conversion here.  One is to 
convert unambiguous atomic types and containers to Python objects.  (I'm 
assuming that some things will clearly be lists, ints, strings, unicode 
etc.)  I'd assume that for such items, it suffices to have a simple, 
single-dictionary mapping from type codes to constructors, with a default 
constructor that makes a generic "AEObject".

Similarly, I'd assume that for Python types that have an unambiguous, 
lossless conversion to AE types, there would be an adapter from the builtin 
type to IAE_Base (or whatever you call it).

I also assume that there's some sort of pickling/unpickling process to/from 
the AE encoding, yes?  Or is this something you're doing as a C module?

If it's an encoding/decoding process, this is a lot cleaner to 
explain.  You don't really adapt to IAE_anything, you adapt to 
'IAE_Encoder', which has methods to write the data structures.

If it's more of an API that manipulates structures or calls over to the 
other applications (like COM on Windows), then it needs to go the other 
way, and actually convert to AE object things.   Hm.



>The second reason is that some people like to do things like 
>app.window.first.document.paragraphs[2].words.first.get() to get the first 
>word of the third paragraph, where via the first API you would've ended up 
>with a python string at app.window.first.document and you would have to 
>use Python APIs to split paragraphs and words.

Makes sense.


>>For example, do you want to let people declare adaptations from 
>>AEDesc.types?  Or is that going to be hidden behind some other interface 
>>you're creating?
>
>Hidden

This is beginning to sound a bit like OLE Automation (aka COM 
IDispatch).  That is, it's a scripting interface to another application, 
not really anything that requires adaptation.  But maybe I'm confused.


>This definitely helps.  I know all the types when I create the application 
>bindings, so I won't be making stuff up at protocolForDesc time.  If it's 
>not in the application's context dictionary, it will get looked up in the 
>global dictionary.



>>>3)  I need to be able to weight certain adaptations.  Some conversions 
>>>are lossy (i.e. double -> float), and shouldn't be used unless that's 
>>>what's asked for.
>>
>>You can do weighting using the depth indicators, but I'm a little 
>>suspicious of your question here because it seems to be implying that 
>>you're doing something different than what I'm thinking.  Clarifying what 
>>you mean would be very helpful here.
>
>Python has one float type, which is 64bits (on this platform).  Apple 
>Events has a whole bunch of floats (32bit, 64bit, 128bit off the top of my 
>head).  Here's the paths I have:
>
>float -> IAEDesc_32bitfloat
>float -> IAEDesc_64bitfloat
>
>Technically I could do this:
>
>float -> IAEDesc_64bitfloat
>float -> IAEDesc_64bitfoat -> IAEDesc_32bitfloat
>
>but that's a nastier conversion because it struct.pack's into an AEDesc, 
>and then it converts that AEDesc to another AEDesc.
>
>I'd prefer to just take the direct IAEDesc_32bitfloat route (a different 
>struct.pack), but only if I say adapt(float, IAEDesc_32bitfloat), not 
>adapt(float, IAEDesc).  Basically, float -> IAEDesc_32bitfloat should have 
>a cost > 1 (since it's lossy) and IAEDesc_64bitfloat should have a cost == 
>1.  The cost of adapting an AEDesc_FOO to AEDesc_BAR should be pretty 
>high, because I don't know if it works until I try it.  But maybe I have 
>to use a fallback factory to make that happen anyways(?)

What's beginning to bother me about all this is that I'm not clear on why 
you need to have these adaptations in the first place.

If this is a scripting interface, it seems that it should be possible to 
handle these things at method boundaries.  If it's a data encoding format, 
it seems you should encode what you have.


>>>4)  If you can't find an explicit adaptation path, you can ask the Apple 
>>>Event subsystem to (attempt to) coerce it for you.  This has the problem 
>>>of making way too many adapter declarations.
>>
>>Coerce it to *what*?  I'm a little lost here.  Why wouldn't you just let 
>>the user of the bridge define what they want to do for their app's purposes?
>
>When you're taking something from "IPython" and converting to "IAEDesc" 
>for purposes of putting it into a list or record, any sort of AEDesc will 
>work, as long as it's an AEDesc.  However, when you're doing something 
>like {set the name of the first track to 'hello'} (yes, AppleScript is 
>backwards.. python equivalent is app.track[0].name = 'hello') then the 
>application probably expects a certain kind of AEDesc for the name (text, 
>maybe) so you will want to give it an "IAEDesc_text" .. the adapter path 
>for that is probably python str -> python unicode -> AEDesc 'utxt' ... 
>AEDesc 'text' where -> are explicit adapters (python string to AEDesc utxt 
>should be the least cost path to an IAEDesc) then the ... means it will 
>just ask the Apple Events runtime to convert 'utxt' to 'text' because it 
>doesn't know what else to do.  I don't want to have adapters from 
>IAEDesc_utxt -> IAEDesc_text, IAEDesc_text -> IAEDesc_utxt, etc.. because 
>there are too many of them and who knows which ones are valid and which aren't.

I think part of what's confusing me is that I don't see what the use of 
adapting between different IAE types is.  Based on what I know so far, if I 
were doing this, I would *only* adapt to or from Python types, not between 
IAE types.  And I'd explicitly define whatever my app needed.

Further, I don't think I'd even bother with adapting anything more coarse 
grained than required for the scripting.  For example, in your example 
above of 'app.track[0].name="hello"', I'd assume that the AEObject stored 
in app.track[0] would have a 'setattr' or property that adapts the set 
value to IAEDesc_text, and that there'd be a str->IAEDesc_text 
adapter.  So, for all practical purposes I'd have no adapters except 
to/from atomic types and generic containers, and everything else would be 
an AEObject or a proxy type generated from metadata.

IOW, there'd be very little adaptation, unless an application wanted to 
define converters from certain URIs to application-defined interfaces that 
the application wanted to convert AE objects to.

So, I guess I'm saying that either you're making things too complicated, or 
that I don't understand yet why the complication is necessary.  :)

OTOH, my approach has the complication of needing Pythonic wrapper types 
for atomic types that can't be perfectly represented by Python built-in 
types.  For example, I'd probably create a wrapper for the 
IAEDesc_128bitfloat that implemented __add__, __mul__, etc. by calling down 
to AE API's.


>>Why wouldn't you have your wrapper system just convert 'null' to None in 
>>the first place?  (I.e., why go to the trouble of adapting it?)
>>Indeed, I'd assume that for types where there is no ambiguity or data 
>>loss, you might as well convert them to Python types in the first 
>>place.  (Why bother allocating a structure you're just going to convert 
>>anyway?)
>
>That's what I do now.. I guess the best way would be for me to use some of 
>my existing adaptation infrastructure for these unambiguous cases and call 
>out to adapt when it's less obvious what needs to be done.

Why adapt at all?  Why not just leave them as proxy objects that implement 
getattr/setattr/getitem/etc.?


>That just means I will have to have my own version of the "adapt" 
>function, since it needs to be used when picking apart AEDesc containers 
>or a raw AEDesc.

This sounds like you're trying to do it "eagerly" instead of "lazily".  But 
you haven't actually given a use case for eagerly adapting non-atomic 
constructs.

If you look at peak.model, there are two fundamentally different kinds of 
types - primitives, and structured types.  Primitives can't be subdivided 
any further: strings, ints, floats, etc.  These are reasonable candidates 
for eager conversion, even if it's to an extension type or proxy type that 
tries to "look like" a Python string or number or whatever.

But structured types are more appropriate for lazy conversion.  Instead of 
converting their contents, they just reference child AE objects.  And, they 
allow setting or getting them as AE objects.  When setting, adapt to the 
least-specific interface allowed by the type.  When getting, simply return 
objects as-is.  If the client wants a different type, *they* should adapt 
it.  However, for primitive types, the objects they receive on getting 
attributes should be quite usable.

I think that this can be so, even for your second API.


>I'll let you know when I fully understand it (don't expect to hear that 
>any time soon) ;)  I think I do have a somewhat clearer idea about how I 
>should be using PyProtocols now.

I'm guessing that this system also resembles CORBA typecodes in a 
way.  (See peak.model.datatypes).  CORBA typecodes have a bunch of 
primitive type possibilities, but they can also be a list or other 
structure of other typecodes, recursively.  So, I guess your second API 
example just returns a type meaning 'list of IAEDesc_text', and so that's 
the "more complicated" type you refer to.  If so, then I guess that 
requires a bit of thinking as to how to adapt/dispatch that cleanly.  Of 
course, that may simply mean you have a parameterized list type that 
already knows what adapter to use.  E.g.:

IAEDesc_list[x] -(AEList(x))-> IPythonic

If my guess about similarity to CORBA typecodes is correct, then I do see 
why things could get more complex than I've been thinking.  You might need 
to define parameterized protocol types, sort of like 
'protocols.sequenceOf()' does.  I've been assuming that all the type data 
is in a single 4-character code, and defines either an object type or a 
primitive type, where containers are generic types rather than 
parameterized ones.




More information about the PEAK mailing list