FAQ
Hello everyone,


my name is Michael, I'm the lead developer of a Python GUI automation library for Windows called Automa: http://www.getautoma.com. We want to add some features to our library but are unsure how to best expose them via our API. It would be extremely helpful for us if you could let us know which API design feels "right" to you.


Our API already offers very simple commands for automating the GUI of a Windows computer. For example:


from automa.api import *
start("Notepad")
write("Hello World!")
press(CTRL + 's')
write("test.txt", into="File name")
click("Save")
click("Close")


When you execute this script, Automa starts Notepad and simulates key strokes, mouse movements and clicks to perform the required commands. At the moment, each action is performed in the currently active window.


We do not (yet) have a functionality that allows you to explicitly switch to a specific window. Such a functionality would for instance make it possible to open two Notepad windows using the start(...) command, and copy text between them.


One API design would be to have our start(...) function return a "Window" (say) object, whose methods allow you to do the same operations as the global functions write(...), press(...), click(...) etc., but in the respective window. In this design, the example of operating two Notepad windows could be written as


notepad_1 = start("Notepad")
notepad_2 = start("Notepad")
notepad_1.write("Hello World!")
notepad_1.press(CTRL + 'a', CTRL + 'c')
notepad_2.press(CTRL + 'v')


The problem with this design is that it effectively duplicates our API: We want to keep our "global" functions because they are so easy to read. If we add methods to a new "Window" class that do more or less the same, we feel that we are violating Python's principle that "There should be one - and preferably only one - obvious way to do it."


An alternative design would be to make the window switching an explicit action. One way of doing this would be to add a new global function, say "switch_to" or "activate", that takes a single parameter that identifies the window to be switched to. We could still have start(...) return a Window object, that could then be passed to our function:


notepad_1 = start("Notepad")
notepad_2 = start("Notepad")
switch_to(notepad_1)
write("Hello World!")
press(CTRL + 'a', CTRL + 'c')
switch_to(notepad_2)
press(CTRL + 'v')


Maybe our Window objects could also be used as context managers:


notepad_1 = start("Notepad")
notepad_2 = start("Notepad")
with notepad_1:
write("Hello World!")
press(CTRL + 'a', CTRL + 'c')
with notepad_2:
press(CTRL + 'v')


As a final idea, switching could also be done as a method of the Window class:


notepad_1 = start("Notepad")
notepad_2 = start("Notepad")
notepad_1.activate()
write("Hello World!")
press(CTRL + 'a', CTRL + 'c')
notepad_2.activate()
press(CTRL + 'v')


It would be extremely helpful for us if you could let me know which way of using the API you would prefer. If you opt for an explicit version, how would you call the respective method? "activate" / "switch_to" / "focus" or something else?


Thank you so much!


Best wishes,
Michael

Search Discussions

  • Kwpolska at Mar 25, 2013 at 7:42 pm

    On Mon, Mar 25, 2013 at 8:29 PM, Michael Herrmann wrote:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.write("Hello World!")
    notepad_1.press(CTRL + 'a', CTRL + 'c')
    notepad_2.press(CTRL + 'v')

    Explicit is better than implicit. Changing windows should be explicit
    and not implified by your library.

    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v')

    Much better.

    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    with notepad_1:
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    with notepad_2:
    press(CTRL + 'v')

    That?s ugly, and don?t forget that your users aren?t Pythonistas most
    of the time.

    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.activate()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.activate()
    press(CTRL + 'v')

    That is nice and makes sense, because a global function feels wrong,
    at least for me.

    It would be extremely helpful for us if you could let me know which way of using the API you would prefer. If you opt for an explicit version, how would you call the respective method? "activate" / "switch_to" / "focus" or something else?

    Window().focus() is the best IMO.


    PS. do you plan a version for non-Windows OSes? Also, ?99 is too expensive.


    --
    Kwpolska <http://kwpolska.tk> | GPG KEY: 5EAAEA16
    stop html mail | always bottom-post
    http://asciiribbon.org | http://caliburn.nl/topposting.html
  • Michael Herrmann at Mar 25, 2013 at 8:48 pm
    Hi Kwpolska,


    thanks for your reply (as last time I posted here!).

    On Monday, March 25, 2013 8:42:25 PM UTC+1, Kwpolska wrote:
    ...
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    with notepad_1:
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    with notepad_2:
    press(CTRL + 'v')
    That?s ugly, and don?t forget that your users aren?t Pythonistas most
    of the time.

    I kind of like the context manager solution because the indentation makes it very obvious what happens in which window. You are right about our target group though. Also, the "with" is not as explicit as it probably should be.

    ...
    PS. do you plan a version for non-Windows OSes? Also, ?99 is too expensive.

    We'd of course love to support other platforms but don't currently have the resources to do this. We actually just wrote a blog entry about this and some related questions: http://www.getautoma.com/blog/automa-faq If we have something wrong, do let us know in the comments over there!


    It's very hard work building such an automation tool and also we have to live off something. Unfortunately, we can't make the price any lower.


    P.S.: First-time bottom-posting!
  • Chris Angelico at Mar 25, 2013 at 9:08 pm

    On Tue, Mar 26, 2013 at 7:48 AM, Michael Herrmann wrote:
    On Monday, March 25, 2013 8:42:25 PM UTC+1, Kwpolska wrote:
    ...
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    with notepad_1:
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    with notepad_2:
    press(CTRL + 'v')
    That?s ugly, and don?t forget that your users aren?t Pythonistas most
    of the time.
    I kind of like the context manager solution because the indentation makes it very obvious what happens in which window. You are right about our target group though. Also, the "with" is not as explicit as it probably should be.

    What happens at the __exit__ of the context manager? What happens if
    context managers are nested? I'd be inclined to the simpler option of
    an explicit switch (since focus doesn't really "stack" and it'd feel
    weird for focus to *sometimes* switch away when you're done working
    with one window), though the context manager syntax does have its
    advantages too.

    PS. do you plan a version for non-Windows OSes? Also, ?99 is too expensive.
    We'd of course love to support other platforms but don't currently have the resources to do this. We actually just wrote a blog entry about this and some related questions: http://www.getautoma.com/blog/automa-faq If we have something wrong, do let us know in the comments over there!

    Make the API clean enough and someone else might well write a Linux
    equivalent. Then it'll be as simple as a try/import/except/import at
    the top and multiple platforms will work.

    P.S.: First-time bottom-posting!

    Congrats! Feels good, doesn't it :)


    ChrisA
  • Michael Herrmann at Mar 26, 2013 at 8:53 am

    On Monday, March 25, 2013 10:08:53 PM UTC+1, Chris Angelico wrote:
    ...
    I kind of like the context manager solution because the indentation makes it very obvious what happens in which window. You are right about our target group though. Also, the "with" is not as explicit as it probably should be.
    What happens at the __exit__ of the context manager? What happens if
    context managers are nested? I'd be inclined to the simpler option of
    an explicit switch (since focus doesn't really "stack" and it'd feel
    weird for focus to *sometimes* switch away when you're done working
    with one window), though the context manager syntax does have its
    advantages too.

    You are right, an __exit__ for a window doesn't really make sense and neither does stacking. There's also the problem that the focus window may change - for instance when closing it. What happens if you're still inside the "with ..." then? At first glance, I think the context manager solution looks nice syntactically, but maybe it isn't the way to go here.

    ...
    We'd of course love to support other platforms but don't currently have the resources to do this. We actually just wrote a blog entry about this and some related questions: http://www.getautoma.com/blog/automa-faq If we have something wrong, do let us know in the comments over there!

    Make the API clean enough and someone else might well write a Linux
    equivalent. Then it'll be as simple as a try/import/except/import at
    the top and multiple platforms will work.

    Yes, that's a good point. A clean API is very important to us (hence my posting here).


    Thanks for your answer!
    Michael
  • Chris Angelico at Mar 26, 2013 at 11:38 am

    On Tue, Mar 26, 2013 at 7:53 PM, Michael Herrmann wrote:
    On Monday, March 25, 2013 10:08:53 PM UTC+1, Chris Angelico wrote:
    ...
    I kind of like the context manager solution because the indentation makes it very obvious what happens in which window. You are right about our target group though. Also, the "with" is not as explicit as it probably should be.
    What happens at the __exit__ of the context manager? What happens if
    context managers are nested? I'd be inclined to the simpler option of
    an explicit switch (since focus doesn't really "stack" and it'd feel
    weird for focus to *sometimes* switch away when you're done working
    with one window), though the context manager syntax does have its
    advantages too.
    You are right, an __exit__ for a window doesn't really make sense and neither does stacking. There's also the problem that the focus window may change - for instance when closing it. What happens if you're still inside the "with ..." then? At first glance, I think the context manager solution looks nice syntactically, but maybe it isn't the way to go here.

    Fundamental point: As I understand the API, it doesn't *actually* tie
    to a window. You don't locate the Notepad window and send it keys -
    you switch focus to Notepad and then send keys to the whole system. Is
    this correct? I'm basing my understanding on this paragraph from your
    original post:
    We do not (yet) have a functionality that allows you to explicitly switch to a
    specific window. Such a functionality would for instance make it possible to
    open two Notepad windows using the start(...) command, and copy text
    between them.

    If so, then all of the method-based options are effectively lying,
    because they imply a binding that's not there. The actual sequence of
    actions includes imperatives of "switch to some other window", so I
    think that's what the API should reflect. Otherwise, there's risk that
    something will get horribly horribly confused between the programmer's
    brain and the end result (which could happen on either side of your
    code).


    But if you can unambiguously identify a running instance of something
    and switch to it, then a method on the object that start() returns
    would be absolutely correct. So I'd be looking at either your second
    or fourth options from the original post.


    ChrisA
  • Michael Herrmann at Mar 26, 2013 at 12:13 pm

    On Tuesday, March 26, 2013 12:38:35 PM UTC+1, Chris Angelico wrote:
    ...
    Fundamental point: As I understand the API, it doesn't *actually* tie
    to a window. You don't locate the Notepad window and send it keys -
    you switch focus to Notepad and then send keys to the whole system. Is
    this correct? I'm basing my understanding on this paragraph from your
    original post:
    We do not (yet) have a functionality that allows you to explicitly switch to a
    specific window. Such a functionality would for instance make it possible to
    open two Notepad windows using the start(...) command, and copy text
    between them.
    If so, then all of the method-based options are effectively lying,
    because they imply a binding that's not there. The actual sequence of
    actions includes imperatives of "switch to some other window", so I
    think that's what the API should reflect. Otherwise, there's risk that
    something will get horribly horribly confused between the programmer's
    brain and the end result (which could happen on either side of your
    code).

    As I just wrote in my reply to Dave, internally we know very well which window an action is to be performed in. This window is the window that'd be in the foreground after the previous action, if nothing interferes with the system while the script is being executed. A common exception is when you enter commands in the interactive interpreter: say you write

    start("Notepad")

    The Notepad window opens, but for you to enter the next command, you have to switch back to the interpreter window. If you do that, and enter

    write("Hello World")

    then we remember that you were previously working with the Notepad window and activate this window before performing the key strokes to type "Hello World".

    But if you can unambiguously identify a running instance of something
    and switch to it, then a method on the object that start() returns
    would be absolutely correct. So I'd be looking at either your second
    or fourth options from the original post.

    Those are also my favorites at the moment :)


    Michael
    www.getautoma.com
  • Ethan Furman at Mar 25, 2013 at 11:11 pm

    On 03/25/2013 12:29 PM, Michael Herrmann wrote:
    Hello everyone,

    my name is Michael, I'm the lead developer of a Python GUI automation library for Windows called Automa: http://www.getautoma.com. We want to add some features to our library but are unsure how to best expose them via our API. It would be extremely helpful for us if you could let us know which API design feels "right" to you.

    Our API already offers very simple commands for automating the GUI of a Windows computer. For example:

    from automa.api import *
    start("Notepad")
    write("Hello World!")
    press(CTRL + 's')
    write("test.txt", into="File name")
    click("Save")
    click("Close")

    When you execute this script, Automa starts Notepad and simulates key strokes, mouse movements and clicks to perform the required commands. At the moment, each action is performed in the currently active window.

    We do not (yet) have a functionality that allows you to explicitly switch to a specific window. Such a functionality would for instance make it possible to open two Notepad windows using the start(...) command, and copy text between them.

    One API design would be to have our start(...) function return a "Window" (say) object, whose methods allow you to do the same operations as the global functions write(...), press(...), click(...) etc., but in the respective window. In this design, the example of operating two Notepad windows could be written as

    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.write("Hello World!")
    notepad_1.press(CTRL + 'a', CTRL + 'c')
    notepad_2.press(CTRL + 'v')

    This is the way to go. Just move your global functions into the Window object (or whatever you call it), break
    backwards compatibility (major version number change, perhaps?), and call it good.


    It makes much more sense to call methods of several different objects (which is explicit -- you always know which object
    is being used) than having a magic function that changes the object in the background (plus you now have to search
    backwards for the last magic invocation to know -- and what if a called function changes it?).


    --
    ~Ethan~
  • Mitya Sirenef at Mar 25, 2013 at 11:40 pm

    On 03/25/2013 03:29 PM, Michael Herrmann wrote:
    Hello everyone,

    my name is Michael, I'm the lead developer of a Python GUI automation library for Windows called Automa: http://www.getautoma.com. We want to add some features to our library but are unsure how to best expose them via our API. It would be extremely helpful for us if you could let us know which API design feels "right" to you.

    Our API already offers very simple commands for automating the GUI of a Windows computer. For example:

    from automa.api import *
    start("Notepad")
    write("Hello World!")
    press(CTRL + 's')
    write("test.txt", into="File name")
    click("Save")
    click("Close")

    When you execute this script, Automa starts Notepad and simulates key strokes, mouse movements and clicks to perform the required commands. At the moment, each action is performed in the currently active window.

    We do not (yet) have a functionality that allows you to explicitly switch to a specific window. Such a functionality would for instance make it possible to open two Notepad windows using the start(...) command, and copy text between them.

    One API design would be to have our start(...) function return a "Window" (say) object, whose methods allow you to do the same operations as the global functions write(...), press(...), click(...) etc., but in the respective window. In this design, the example of operating two Notepad windows could be written as

    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.write("Hello World!")
    notepad_1.press(CTRL + 'a', CTRL + 'c')
    notepad_2.press(CTRL + 'v')

    The problem with this design is that it effectively duplicates our API: We want to keep our "global" functions because they are so easy to read. If we add methods to a new "Window" class that do more or less the same, we feel that we are violating Python's principle that "There should be one - and preferably only one - obvious way to do it."

    An alternative design would be to make the window switching an explicit action. One way of doing this would be to add a new global function, say "switch_to" or "activate", that takes a single parameter that identifies the window to be switched to. We could still have start(...) return a Window object, that could then be passed to our function:

    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v')

    Maybe our Window objects could also be used as context managers:

    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    with notepad_1:
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    with notepad_2:
    press(CTRL + 'v')

    As a final idea, switching could also be done as a method of the Window class:

    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.activate()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.activate()
    press(CTRL + 'v')

    It would be extremely helpful for us if you could let me know which way of using the API you would prefer. If you opt for an explicit version, how would you call the respective method? "activate" / "switch_to" / "focus" or something else?

    Thank you so much!

    Best wishes,
    Michael



    I think I would prefer context managers. I don't think it's a big
    problem for
    win users because this behaviour would be one of the first things documented
    in the start guide and would be all over example scripts, so a new user
    missing
    or forgetting it is not a realistic scenario.


    The advantages are that it's explicit, blocks are indented and it's
    impossible to
    miss which window is the action applied to, and at the same time actions are
    short and easy to type and read.


    -m




    --
    Lark's Tongue Guide to Python: http://lightbird.net/larks/
  • Michael Herrmann at Mar 26, 2013 at 9:06 am

    On Tuesday, March 26, 2013 12:11:34 AM UTC+1, Ethan Furman wrote:
    On 03/25/2013 12:29 PM, Michael Herrmann wrote:
    ...
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.write("Hello World!")
    notepad_1.press(CTRL + 'a', CTRL + 'c')
    notepad_2.press(CTRL + 'v')
    This is the way to go. Just move your global functions into the Window object (or whatever you call it), break
    backwards compatibility (major version number change, perhaps?), and call it good.

    It makes much more sense to call methods of several different objects (which is explicit -- you always know which object
    is being used) than having a magic function that changes the object in the background (plus you now have to search
    backwards for the last magic invocation to know -- and what if a called function changes it?).

    Your points are valid and I cannot really say anything against them. The problem with moving all global functions into the Window object is that this adds a lot of syntactic baggage that isn't needed in 90% of the cases. We really prefer the simplicity of


    start("Notepad")
    write("Hello World!")
    press(CTRL + 's')
    write("test.txt", into="File name")
    click("Save")
    press(ALT + F4)


    over


    notepad = start("Notepad")
    notepad.write("Hello World!")
    notepad.press(CTRL + 's')
    notepad.write("test.txt", into="File name")
    notepad.click("Save")
    notepad.press(ALT + F4).


    Also, there's a problem here: The "Save" dialogue that opens in the above script is technically a different window so in theory you would have to introduce a new object to distinguish between the original window that lets you edit your text document from the "Save" window. This is very tedious and error-prone. You are right, though, that we have to do some logic in the background to remember the last window.


    In light of this, could you live with something along the lines of design #4?


    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.focus()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.focus()
    press(CTRL + 'v')


    Thanks,
    Michael
  • Dave Angel at Mar 26, 2013 at 10:26 am

    On 03/26/2013 05:06 AM, Michael Herrmann wrote:
    On Tuesday, March 26, 2013 12:11:34 AM UTC+1, Ethan Furman wrote:
    On 03/25/2013 12:29 PM, Michael Herrmann wrote:
    ...
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.write("Hello World!")
    notepad_1.press(CTRL + 'a', CTRL + 'c')
    notepad_2.press(CTRL + 'v')
    This is the way to go. Just move your global functions into the Window object (or whatever you call it), break
    backwards compatibility (major version number change, perhaps?), and call it good.

    It makes much more sense to call methods of several different objects (which is explicit -- you always know which object
    is being used) than having a magic function that changes the object in the background (plus you now have to search
    backwards for the last magic invocation to know -- and what if a called function changes it?).
    Your points are valid and I cannot really say anything against them. The problem with moving all global functions into the Window object is that this adds a lot of syntactic baggage that isn't needed in 90% of the cases. We really prefer the simplicity of

    start("Notepad")
    write("Hello World!")
    press(CTRL + 's')
    write("test.txt", into="File name")
    click("Save")
    press(ALT + F4)

    over

    notepad = start("Notepad")
    notepad.write("Hello World!")
    notepad.press(CTRL + 's')
    notepad.write("test.txt", into="File name")
    notepad.click("Save")
    notepad.press(ALT + F4).

    Also, there's a problem here: The "Save" dialogue that opens in the above script is technically a different window so in theory you would have to introduce a new object to distinguish between the original window that lets you edit your text document from the "Save" window. This is very tedious and error-prone. You are right, though, that we have to do some logic in the background to remember the last window.

    In light of this, could you live with something along the lines of design #4?

    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.focus()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.focus()
    press(CTRL + 'v')

    Thanks,
    Michael

    Seems to me that the official interface should all be methods. However,
    you could have a new object which always represents the "focus" window.
    Then the USER could define trivial functions:


    def write(*args):
    focused.write(*args)


    Somewhere in this thread you mention that save() creates a new window,
    so a method-oriented approach would require that the user get that
    window object, and call its methods rather than the original window's.
    I say that's a very good thing, since the things you send may very well
    have very different meanings to the save dialog than they do in the
    original one.


    Another concern I'd have is what happens if the user changes focus with
    his mouse? Does that change the meaning you had for focus() in the
    above exchange? Do you want your press() method to apply to a different
    window when the user changes to that window?




    --
    DaveA
  • Michael Herrmann at Mar 26, 2013 at 12:04 pm

    On Tuesday, March 26, 2013 11:26:30 AM UTC+1, Dave Angel wrote:
    ...
    Seems to me that the official interface should all be methods. However,
    you could have a new object which always represents the "focus" window.
    Then the USER could define trivial functions:

    def write(*args):
    focused.write(*args)

    It's an interesting idea. But why not give this write(...) to them in the first place? Am I the only one who appreciates the simplicity of


    start("Notepad")
    write("Hello World!")
    press(CTRL + 's')
    write("test.txt", into="File name")
    click("Save")
    press(ALT + F4)


    over


    notepad = start("Notepad")
    notepad.write("Hello World!")
    notepad.press(CTRL + 's')
    notepad.write("test.txt", into="File name")
    notepad.click("Save")
    notepad.press(ALT + F4)?

    Somewhere in this thread you mention that save() creates a new window,
    so a method-oriented approach would require that the user get that
    window object, and call its methods rather than the original window's.
    I say that's a very good thing, since the things you send may very well
    have very different meanings to the save dialog than they do in the
    original one.

    save() is not a function, but I assume you mean the action that opens the "Save" dialogue (I think that'd be `press(CTRL + 's')`). You are right that it's nice for it to be explicit. However, in 95% of cases, the window you want the next action to be performed in is the window that is currently active. I appreciate the explicitness, but to force it on the user for only 5% of cases seems a bit much.

    Another concern I'd have is what happens if the user changes focus with
    his mouse? Does that change the meaning you had for focus() in the
    above exchange? Do you want your press() method to apply to a different
    window when the user changes to that window?

    No. Internally, we remember which window is the currently active window. If you just run a script without user-intervention, this will be the respective foreground window. If some other window is in the foreground - which most typically happens when the user is interactively entering commands one after the other, so the foreground window is the console window, we do switch to the window that's supposed to be the active one. It may sound like black magic, but it works very well in practice, and really is not too ambiguous. When you read a script like


    start("Notepad")
    write("Hello World")
    press(CTRL + 's')
    write("test.txt", into="File name")
    click("Save")
    click("Close")


    I hold that you intuitively know what's going on, without even thinking about window switching.


    Best,
    Michael
    www.getautoma.com
  • Dave Angel at Mar 26, 2013 at 12:42 pm

    On 03/26/2013 08:04 AM, Michael Herrmann wrote:
    On Tuesday, March 26, 2013 11:26:30 AM UTC+1, Dave Angel wrote:
    ...
    Seems to me that the official interface should all be methods. However,
    you could have a new object which always represents the "focus" window.
    Then the USER could define trivial functions:

    def write(*args):
    focused.write(*args)
    It's an interesting idea. But why not give this write(...) to them in the first place?

    Just to be clear, I was avoiding the problem of having two ways of
    accessing each function, since most of us prefer the methods, and you
    have some users who prefer simple functions.

    Am I the only one who appreciates the simplicity of

    start("Notepad")
    write("Hello World!")
    press(CTRL + 's')
    write("test.txt", into="File name")
    click("Save")
    press(ALT + F4)

    over

    notepad = start("Notepad")
    notepad.write("Hello World!")
    notepad.press(CTRL + 's')
    notepad.write("test.txt", into="File name")
    notepad.click("Save")
    notepad.press(ALT + F4)?
    Somewhere in this thread you mention that save() creates a new window,
    so a method-oriented approach would require that the user get that
    window object, and call its methods rather than the original window's.
    I say that's a very good thing, since the things you send may very well
    have very different meanings to the save dialog than they do in the
    original one.
    save() is not a function, but I assume you mean the action that opens the "Save" dialogue (I think that'd be `press(CTRL + 's')`). You are right that it's nice for it to be explicit. However, in 95% of cases, the window you want the next action to be performed in is the window that is currently active. I appreciate the explicitness, but to force it on the user for only 5% of cases seems a bit much.
    Another concern I'd have is what happens if the user changes focus with
    his mouse? Does that change the meaning you had for focus() in the
    above exchange? Do you want your press() method to apply to a different
    window when the user changes to that window?
    No. Internally, we remember which window is the currently active window. If you just run a script without user-intervention, this will be the respective foreground window. If some other window is in the foreground - which most typically happens when the user is interactively entering commands one after the other, so the foreground window is the console window, we do switch to the window that's supposed to be the active one. It may sound like black magic, but it works very well in practice, and really is not too ambiguous. When you read a script like

    start("Notepad")
    write("Hello World")
    press(CTRL + 's')
    write("test.txt", into="File name")
    click("Save")
    click("Close")

    I hold that you intuitively know what's going on, without even thinking about window switching.

    Until the program you're scripting makes some minor change in its
    interface, or has something conditional on an attribute not intuitively
    obvious.


    Also, it seems that in this thread, we are using "window" both to refer
    to a particular application instance (like Notepad1 and Notepad2), and
    to refer to windows within a single application.


    Anyway, if you're only automating a few specific apps, you're not likely
    to run into the problems that methods were intended to address. After
    all, Notepad's bugs haven't seemed to change for a couple of decades, so
    why should they fix anything now?


    Having a selected window be an implied object for those global functions
    yields at least the same problems as any writable global.
    Multithreading, unexpected side effects from certain functions,
    callbacks, etc.


    As long as you know the program is going to be simple, pile on the
    globals. But as soon as it advances, each of them is a trap to fall into.


    --
    DaveA
  • Steven D'Aprano at Mar 26, 2013 at 12:59 pm

    On Tue, 26 Mar 2013 05:04:43 -0700, Michael Herrmann wrote:

    On Tuesday, March 26, 2013 11:26:30 AM UTC+1, Dave Angel wrote:
    ...
    Seems to me that the official interface should all be methods.
    However, you could have a new object which always represents the
    "focus" window.
    Then the USER could define trivial functions:

    def write(*args):
    focused.write(*args)
    It's an interesting idea. But why not give this write(...) to them in
    the first place? Am I the only one who appreciates the simplicity of

    start("Notepad")
    write("Hello World!")
    press(CTRL + 's')
    write("test.txt", into="File name")
    click("Save")
    press(ALT + F4)

    over

    notepad = start("Notepad")
    notepad.write("Hello World!")
    notepad.press(CTRL + 's')
    notepad.write("test.txt", into="File name")
    notepad.click("Save")
    notepad.press(ALT + F4)?

    You are not the only one.


    I suggest that you have a set of functions that work on "the current
    window", whatever that is. Preferably there should always be a current
    window, but if not, ensure that you give a clear error message.


    Then you have syntax for operating on any named(?) window. So a user can
    implicitly operate on the current window:


    select(notepad)
    write("goodbye cruel world")
    save()


    or explicitly on any window they like:


    excel.quit()




    I suggest you dig up an old book on "Hypercard", for Apple Macs in the
    1980s and 90s. Back in the day, Macs could only run a single application
    at a time, and Hypercard was limited to a single window at a time (called
    a "stack"). But that stack (think: window) could have multiple
    "cards" (think: window tabs), one of which was always current.
    Hypercard's built-in programming language Hypertalk let you do things
    like this:




    go to stack "Notepad"
    type "goodbye cruel world" in field "main" of card 7
    click button "Save"


    click button "Quit" of card "Main" of stack "Excel"




    (more or less... it's been a few years since I've had a classic Mac
    capable of running Hypercard.)








    --
    Steven
  • Michael Herrmann at Mar 26, 2013 at 2:26 pm

    On Tuesday, March 26, 2013 1:59:58 PM UTC+1, Steven D'Aprano wrote:
    On Tue, 26 Mar 2013 05:04:43 -0700, Michael Herrmann wrote:
    ...
    Am I the only one who appreciates the simplicity of
    start("Notepad")
    write("Hello World!")
    press(CTRL + 's')
    write("test.txt", into="File name")
    click("Save")
    press(ALT + F4)
    over
    notepad = start("Notepad")
    notepad.write("Hello World!")
    notepad.press(CTRL + 's')
    notepad.write("test.txt", into="File name")
    notepad.click("Save")
    notepad.press(ALT + F4)?
    You are not the only one.

    I suggest that you have a set of functions that work on "the current
    window", whatever that is. Preferably there should always be a current
    window, but if not, ensure that you give a clear error message.

    This is exactly the way it is currently. I am glad you also see it that way.

    Then you have syntax for operating on any named(?) window. So a user can
    implicitly operate on the current window:

    select(notepad)
    write("goodbye cruel world")
    save()

    One idea would be to use the Window(...) constructor to select windows:


    notepad = Window('Untitled - Notepad')
    select(notepad)
    save()


    One advantage of using a global method to switch to a window is that this would allow you to directly switch to a Window without having to call Window(...):


    switch_to('Untitled - Notepad')


    I'm still not fully convinced of a global method though, for the reasons several people here have already mentioned.

    or explicitly on any window they like:

    excel.quit()

    This example makes it look likely that there will have to be other operations that can be performed on windows (/running applications as Dave pointed out). So, a 'quit()' method that closes a window in addition to the already mentioned focus/select/activate method. This in turn makes the global function less attractive as once we start going down that route, global functions will proliferate.

    I suggest you dig up an old book on "Hypercard", for Apple Macs in the
    1980s and 90s. Back in the day, Macs could only run a single application
    at a time, and Hypercard was limited to a single window at a time (called
    a "stack"). But that stack (think: window) could have multiple
    "cards" (think: window tabs), one of which was always current.
    Hypercard's built-in programming language Hypertalk let you do things
    like this:

    go to stack "Notepad"
    type "goodbye cruel world" in field "main" of card 7
    click button "Save"
    click button "Quit" of card "Main" of stack "Excel"

    (more or less... it's been a few years since I've had a classic Mac
    capable of running Hypercard.)

    Very interesting. I had never heard of HyperCard. I read up on it a little and it sounds very similar to the model we are internally building of open applications (stacks) and their windows (cards). Also funny that HyperCard was one of Ward Cunningham's inspirations for coming up with the Wiki idea: http://c2.com/cgi/wiki?WikiWikiHyperCard


    Thanks for this!


    Michael
    www.getautoma.com
  • Michael Herrmann at Mar 26, 2013 at 2:33 pm

    On Tuesday, March 26, 2013 1:42:26 PM UTC+1, Dave Angel wrote:
    ...

    Also, it seems that in this thread, we are using "window" both to refer
    to a particular application instance (like Notepad1 and Notepad2), and
    to refer to windows within a single application.



    Anyway, if you're only automating a few specific apps, you're not likely
    to run into the problems that methods were intended to address. After
    all, Notepad's bugs haven't seemed to change for a couple of decades, so
    why should they fix anything now?

    Having a selected window be an implied object for those global functions
    yields at least the same problems as any writable global.
    Multithreading, unexpected side effects from certain functions,
    callbacks, etc.

    As long as you know the program is going to be simple, pile on the
    globals. But as soon as it advances, each of them is a trap to fall into.

    You're right with everything you say. globals are bad and it may happen that this will bite me. I'm just not sure whether we should sacrifice the simpler syntax useful in say 80% of cases for something I'm not yet sure will ever become a real problem.
  • Steven D'Aprano at Mar 26, 2013 at 10:37 pm

    On Tue, 26 Mar 2013 07:33:18 -0700, Michael Herrmann wrote:


    As long as you know the program is going to be simple, pile on the
    globals. But as soon as it advances, each of them is a trap to fall
    into.
    You're right with everything you say. globals are bad and it may happen
    that this will bite me.

    Global *variables* are bad, not global functions. You have one global
    variable, "the current window". So long as your API makes it obvious when
    the current window changes, implicitly operating on the current window is
    no more dangerous than Python's implicit operations on the current
    namespace (e.g. "x = 2" binds 2 to x in the current namespace).


    I recommend you look at the random.py API. You have a Random class, that
    allows the user to generate as many independent random number generators
    as needed. And the module also initialises a private instance, and
    exposes the methods of that instance as top-level functions, to cover the
    90% simple case where your application only cares about a single RNG.




    --
    Steven
  • Michael Herrmann at Mar 27, 2013 at 9:34 am

    On Tuesday, March 26, 2013 11:37:23 PM UTC+1, Steven D'Aprano wrote:
    Global *variables* are bad, not global functions. You have one global
    variable, "the current window". So long as your API makes it obvious when
    the current window changes, implicitly operating on the current window is
    no more dangerous than Python's implicit operations on the current
    namespace (e.g. "x = 2" binds 2 to x in the current namespace).

    I'm generally wary of everything global, but you're right as long as no (global) state is involved.

    I recommend you look at the random.py API. You have a Random class, that
    allows the user to generate as many independent random number generators
    as needed. And the module also initialises a private instance, and
    exposes the methods of that instance as top-level functions, to cover the
    90% simple case where your application only cares about a single RNG.

    I looked it up - I think this is a very good approach; to provide easy access to the functionality used in 90% of cases but still give users the flexibility to cover the edge cases.


    After everybody's input, I think Design #2 or Design #4 would be the best fit for us:


    Design #2:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v')


    Design #4:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.activate()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.activate()
    press(CTRL + 'v')


    Normally, I'd go for Design #4, as it results in one less global, is better for autocompletion etc. The thing with our library is that it tries to make its scripts as similar as possible to giving instructions to someone looking over their shoulder at a screen. And in this situation you would just say


    activate(notepad)


    rather than


    notepad.activate().


    So the problem lies in a difference between Python's and English grammar. For beauty, I should go with #2. For pragmatism, I should go with #4. It hurts, but I'm leaning towards #4. I have to think about it a little.


    Thank you so much to everybody for your inputs so far!
    Best,
    Michael
    www.getautoma.com
  • Ethan Furman at Mar 27, 2013 at 4:45 pm

    On 03/27/2013 02:34 AM, Michael Herrmann wrote:
    After everybody's input, I think Design #2 or Design #4 would be the best fit for us:

    Design #2:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v')

    Design #4:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.activate()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.activate()
    press(CTRL + 'v')

    Normally, I'd go for Design #4, as it results in one less global, is better for autocompletion etc. The thing with our library is that it tries to make its scripts as similar as possible to giving instructions to someone looking over their shoulder at a screen. And in this situation you would just say

    activate(notepad)

    rather than

    notepad.activate().

    So the problem lies in a difference between Python's and English grammar. For beauty, I should go with #2. For pragmatism, I should go with #4. It hurts, but I'm leaning towards #4. I have to think about it a little.

    Go with #2. Not everything has to be a method. len(), for example, is not a method, even though it calls one.


    The 'with' support would also be cool. __enter__ sets the new global window object whilst saving the old one, and then
    __exit__ restores the old one.


    --
    ~Ethan~
  • Steven D'Aprano at Mar 28, 2013 at 12:42 am

    On Wed, 27 Mar 2013 02:34:09 -0700, Michael Herrmann wrote:

    On Tuesday, March 26, 2013 11:37:23 PM UTC+1, Steven D'Aprano wrote:

    Global *variables* are bad, not global functions. You have one global
    variable, "the current window". So long as your API makes it obvious
    when the current window changes, implicitly operating on the current
    window is no more dangerous than Python's implicit operations on the
    current namespace (e.g. "x = 2" binds 2 to x in the current namespace).
    I'm generally wary of everything global, but you're right as long as no
    (global) state is involved.

    That comment surprises me. Your preferred API:


    switch_to(notepad)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')




    uses implied global state (the current window). Even if you avoid the use
    of an actual global for (say) an instance attribute, it's still
    semantically a global. Surely you realise that?


    Not trying to be argumentative, I'm just surprised at your comment.



    I recommend you look at the random.py API. You have a Random class,
    that allows the user to generate as many independent random number
    generators as needed. And the module also initialises a private
    instance, and exposes the methods of that instance as top-level
    functions, to cover the 90% simple case where your application only
    cares about a single RNG.
    I looked it up - I think this is a very good approach; to provide easy
    access to the functionality used in 90% of cases but still give users
    the flexibility to cover the edge cases.

    After everybody's input, I think Design #2 or Design #4 would be the
    best fit for us:

    Design #2:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v')



    This is nice syntax for trivial cases and beginners whose needs are not
    demanding, but annoying for experts who have more complicated
    requirements. If this is the only API, experts who need to simultaneously
    operate in two windows will be forced to write unproductive boilerplate
    code that does nothing but jump from window to window.


    Well what do you know, even in the simple case above, you have
    unproductive code that does nothing but jump from window to window :-)


    I'm not against this API, I'm just against it as the *only* API.



    Design #4:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.activate()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.activate()
    press(CTRL + 'v')

    This is actually no different from #2 above, except that it uses method
    call syntax while #2 uses function call syntax. So it has the same
    limitations as above: it's simple for simple uses, but annoying for
    complex use.


    Neither API supports advanced users with complicated needs. A hybrid
    approach, where you have function call syntax that operates on the
    implicit current window, plus method call syntax that operates on any
    window, strikes me as the best of both worlds. With a little forethought
    in your implementation, you don't have to duplicate code. E.g. something
    like this:




    class WindowOps:
    def __init__(self, theWindow=None):
    self.theWindow = None


    def press(self, c):
    win = self.getWindow()
    send_keypress_to(win)


    def getWindow(self):
    if self.theWindow is None:
    return gTheTopWindow
    return self.theWindow




    _implicit = WindowOps(None)


    press = _implicit.press
    # etc.


    del _implicit






    This gives you the best of both worlds, for free: a simple API using an
    implicit top window for simple cases, and a slightly more complex API
    with an explicit window for advanced users.





    Normally, I'd go for Design #4, as it results in one less global,

    I don't see how this is possible. Both APIs use an implicit "top window".
    What's the one less global you are referring to?



    is
    better for autocompletion etc. The thing with our library is that it
    tries to make its scripts as similar as possible to giving instructions
    to someone looking over their shoulder at a screen. And in this
    situation you would just say

    activate(notepad)

    rather than

    notepad.activate().

    Depends like Yoda they talk whether or not.


    Unless you go all the way to writing your own parser that accepts English-
    like syntax, like Hypertalk:


    select notepad
    type hello world


    I don't think it makes that much difference. Function call syntax is not
    exactly English-like either. We don't generally speak like this:


    write bracket quote hello world quote close bracket




    My personal feeling is that people aren't going to be *too* confused by
    method call syntax, especially not if they've seen or been introduced to
    any programming at all. You say tom-a-to, I say tom-ar-to.


    But I think it is useful to distinguish between the "basic API" using
    function call syntax and an implied current window, and an "advanced API"
    using method call syntax with an explicit window:


    # Basic API is pure function calls, using an implicit window
    switch_to(notepad)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(calculator)
    write('2+3=')
    press(CTRL + 'a')
    switch_to(notepad)
    press(CTRL + 'v')




    # Advanced API uses an explicit window and method calls:
    notepad.write("Hello World!")
    notepad.press(CTRL + 'a', CTRL + 'c')
    calculator.write('2+3=')
    calculator.press(CTRL + 'a')
    notepad.press(CTRL + 'v')




    # Of course you can mix usage:
    switch_to(notepad)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    calculator.write('2+3=')
    calculator.press(CTRL + 'a')
    press(CTRL + 'v')




    You could avoid method call syntax altogether by giving your functions an
    optional argument that points to the window to operate on:


    write("Hello World!")
    write("Hello World!", notepad)


    but the difference is mere syntax.


    There's little or no additional complexity of implementation to allow the
    user to optionally specify an explicit window. I expect you have
    something like this:


    def write(string):
    window = gTheCurrentWindow # use a global
    send characters of string to window # magic goes here




    This can trivially be changed to:




    def write(string, window=None):
    if window is None:
    window = gTheCurrentWindow
    send characters of string to window # magic goes here




    See, for example, the decimal module. Most operations take an optional
    "context" argument that specifies the number of decimal places, rounding
    mode, etc. If not supplied, the global "current context" is used.
    This gives the simplicity and convenience of a global, without the
    disadvantages.


    (Recent versions of decimal have also added "with context" syntax, for
    even more power.)








    --
    Steven
  • Michael Herrmann at Mar 28, 2013 at 11:41 am

    On Thursday, March 28, 2013 1:42:35 AM UTC+1, Steven D'Aprano wrote:
    On Wed, 27 Mar 2013 02:34:09 -0700, Michael Herrmann wrote:
    On Tuesday, March 26, 2013 11:37:23 PM UTC+1, Steven D'Aprano wrote:

    Global *variables* are bad, not global functions. You have one global
    variable, "the current window". So long as your API makes it obvious
    when the current window changes, implicitly operating on the current
    window is no more dangerous than Python's implicit operations on the
    current namespace (e.g. "x = 2" binds 2 to x in the current namespace).
    I'm generally wary of everything global, but you're right as long as no
    (global) state is involved.
    That comment surprises me. Your preferred API:

    switch_to(notepad)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')

    uses implied global state (the current window). Even if you avoid the use
    of an actual global for (say) an instance attribute, it's still
    semantically a global. Surely you realise that?

    I do :-) You made the statement that global variables are bad, not global functions. I didn't want to agree completely with this comment, because if a global function refers to a global variable, I would consider it "bad" too. You correctly point out that our global functions would be exactly of that "bad" kind. Of course, it doesn't make sense to be too dogmatic about "bad", which is why I am considering the global functions as an option, for advantages they have despite being "bad".

    Not trying to be argumentative, I'm just surprised at your comment.

    No offense taken :) I guess I just wasn't expressing myself clearly.

    ...
    After everybody's input, I think Design #2 or Design #4 would be the
    best fit for us:

    Design #2:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v')
    This is nice syntax for trivial cases and beginners whose needs are not
    demanding, but annoying for experts who have more complicated
    requirements. If this is the only API, experts who need to simultaneously
    operate in two windows will be forced to write unproductive boilerplate
    code that does nothing but jump from window to window.


    Well what do you know, even in the simple case above, you have
    unproductive code that does nothing but jump from window to window :-)

    I'm not against this API, I'm just against it as the *only* API.
    Design #4:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.activate()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.activate()
    press(CTRL + 'v')
    This is actually no different from #2 above, except that it uses method
    call syntax while #2 uses function call syntax. So it has the same
    limitations as above: it's simple for simple uses, but annoying for
    complex use.

    Neither API supports advanced users with complicated needs. A hybrid
    approach, where you have function call syntax that operates on the
    implicit current window, plus method call syntax that operates on any
    window, strikes me as the best of both worlds. With a little forethought
    in your implementation, you don't have to duplicate code. E.g. something
    like this:

    class WindowOps:
    def __init__(self, theWindow=None):
    self.theWindow = None

    def press(self, c):
    win = self.getWindow()
    send_keypress_to(win)

    def getWindow(self):
    if self.theWindow is None:
    return gTheTopWindow
    return self.theWindow

    _implicit = WindowOps(None)
    press = _implicit.press
    # etc.
    del _implicit

    This gives you the best of both worlds, for free: a simple API using an
    implicit top window for simple cases, and a slightly more complex API
    with an explicit window for advanced users.

    I understand completely where you are coming from, however if we offer two ways of doing the same thing, people will start mixing the styles and things will get messy. A user commented above that this approach - offering global as well as object oriented functions to do the same thing - is offered by matplotlib and makes examples on the net very confusing and difficult to read. For this reason, I would rather only offer one way of doing things now, and add additional ways later in case they are really needed. You are right that this may not cater for experts' needs very well, but I think I prefer a smaller API that can be extended to one that may result in being difficult to read.

    Normally, I'd go for Design #4, as it results in one less global,
    I don't see how this is possible. Both APIs use an implicit "top window".
    What's the one less global you are referring to?

    By "global" I meant function name.

    is
    better for autocompletion etc. The thing with our library is that it
    tries to make its scripts as similar as possible to giving instructions
    to someone looking over their shoulder at a screen. And in this
    situation you would just say
    activate(notepad)
    rather than
    notepad.activate().
    Depends like Yoda they talk whether or not.

    Unless you go all the way to writing your own parser that accepts English-
    like syntax, like Hypertalk:

    select notepad
    type hello world

    I don't think it makes that much difference. Function call syntax is not
    exactly English-like either. We don't generally speak like this:

    write bracket quote hello world quote close bracket

    My personal feeling is that people aren't going to be *too* confused by
    method call syntax, especially not if they've seen or been introduced to
    any programming at all. You say tom-a-to, I say tom-ar-to.

    But I think it is useful to distinguish between the "basic API" using
    function call syntax and an implied current window, and an "advanced API"
    using method call syntax with an explicit window:


    # Basic API is pure function calls, using an implicit window

    switch_to(notepad)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(calculator)
    write('2+3=')
    press(CTRL + 'a')
    switch_to(notepad)
    press(CTRL + 'v')

    # Advanced API uses an explicit window and method calls:

    notepad.write("Hello World!")
    notepad.press(CTRL + 'a', CTRL + 'c')
    calculator.write('2+3=')
    calculator.press(CTRL + 'a')
    notepad.press(CTRL + 'v')

    # Of course you can mix usage:

    switch_to(notepad)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    calculator.write('2+3=')
    calculator.press(CTRL + 'a')
    press(CTRL + 'v')

    As I said, I think I prefer not giving the possibility to mix usage and potentially adding it later over offering it to begin with.

    You could avoid method call syntax altogether by giving your functions an
    optional argument that points to the window to operate on:

    write("Hello World!")
    write("Hello World!", notepad)

    but the difference is mere syntax.

    That's been pointed out above. I guess it's mostly a question of taste, but I'd like to avoid mixing the concept of "window switching" with the not-very related concepts of typing and clicking. I'm a big fan of orthogonality, mainly because of the book "The Pragmatic Programmer" but also because of the article http://www.artima.com/intv/dry3.html.

    There's little or no additional complexity of implementation to allow the
    user to optionally specify an explicit window.

    That's of course true. What I'm worried about is API complexity: I think the matplotlib example shows that offering too many ways of doing one thing leads to scripts that are difficult to read/maintain. I want to keep the API as simple as possible at first, and then add to it when it turns out it's needed.

    ...
    See, for example, the decimal module. Most operations take an optional
    "context" argument that specifies the number of decimal places, rounding
    mode, etc. If not supplied, the global "current context" is used.
    This gives the simplicity and convenience of a global, without the
    disadvantages.

    That's a very interesting example! I think the decimal module's "setcontext" would be similar to our "switch_to". The fact that this module offers the optional "context" parameter makes it likely that you'll prove me wrong in not including offering such an optional parameter, but I want to see first if we can get by without it, too.


    Thanks for all your input.


    Michael
    www.getautoma.com
  • Michael Herrmann at Mar 28, 2013 at 10:54 am

    On Wednesday, March 27, 2013 5:45:49 PM UTC+1, Ethan Furman wrote:
    On 03/27/2013 02:34 AM, Michael Herrmann wrote:
    Design #2:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v')
    ...

    Go with #2. Not everything has to be a method. len(), for example, is not a method, even though it calls one.

    That's a good point. I actually think #2 is the one we'll use.
  • Michael Herrmann at Mar 26, 2013 at 9:38 am

    On Tuesday, March 26, 2013 12:40:45 AM UTC+1, Mitya Sirenef wrote:
    ...

    I think I would prefer context managers. I don't think it's a big
    problem for
    win users because this behaviour would be one of the first things documented
    in the start guide and would be all over example scripts, so a new user
    missing
    or forgetting it is not a realistic scenario.

    The advantages are that it's explicit, blocks are indented and it's
    impossible to
    miss which window is the action applied to, and at the same time actions are
    short and easy to type and read.

    Thank you for your reply. What do you think of Chris Angelico's points?
    He wrote:
    What happens at the __exit__ of the context manager? What happens if
    context managers are nested? I'd be inclined to the simpler option of
    an explicit switch (since focus doesn't really "stack" and it'd feel
    weird for focus to *sometimes* switch away when you're done working
    with one window), though the context manager syntax does have its
    advantages too.

    What I am most afraid of: that the window that's currently the context "disappears":
    notepad = start("Notepad")
    with notepad:
    press(ALT + TAB)
    write("Am I in Notepad now?")


    What do you think of designs #3 and #4?


    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v')


    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.activate()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.activate()
    press(CTRL + 'v')


    I somehow prefer "activate" over "focus" as in my feeling, you'd normally say that you focus *on* something, so it should be called "focus_on" or "give_focus[_to]". Can you say, in everyday English, that you "focus a window"? I'm not a native speaker so maybe my feeling is misguided.


    Thanks,
    Michael
  • Chris Angelico at Mar 26, 2013 at 11:43 am

    On Tue, Mar 26, 2013 at 8:38 PM, Michael Herrmann wrote:
    What do you think of designs #3 and #4?

    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v')

    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.activate()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.activate()
    press(CTRL + 'v')

    Ehh, I referred to these as options 2 and 4. Got lost in the indexing
    somewhere. These are the same two I meant, though - these are the
    options I think are the most plausible.


    (Hindsight being 20/20, it'd have been awesome if the original
    snippets had had identifiers next to them. Oh well, no matter.)


    ChrisA
  • Michael Herrmann at Mar 26, 2013 at 12:18 pm

    On Tuesday, March 26, 2013 12:43:18 PM UTC+1, Chris Angelico wrote:
    On Tue, Mar 26, 2013 at 8:38 PM, Michael Herrmann
    What do you think of designs #3 and #4?
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v')
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.activate()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.activate()
    press(CTRL + 'v')
    Ehh, I referred to these as options 2 and 4. Got lost in the indexing
    somewhere. These are the same two I meant, though - these are the
    options I think are the most plausible.

    (Hindsight being 20/20, it'd have been awesome if the original
    snippets had had identifiers next to them. Oh well, no matter.)

    True, and the indexing mistake was my fault... Here goes:


    Design #1:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.write("Hello World!")
    notepad_1.press(CTRL + 'a', CTRL + 'c')
    notepad_2.press(CTRL + 'v')


    Design #2:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v')


    Design #3:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    with notepad_1:
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    with notepad_2:
    press(CTRL + 'v')


    Design #4:
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.activate()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.activate()
    press(CTRL + 'v')


    Michael
    www.getautoma.com
  • Mitya Sirenef at Mar 26, 2013 at 1:41 pm

    On 03/26/2013 05:38 AM, Michael Herrmann wrote:
    On Tuesday, March 26, 2013 12:40:45 AM UTC+1, Mitya Sirenef wrote:
    ...
    >>
    I think I would prefer context managers. I don't think it's a big
    problem for
    win users because this behaviour would be one of the first things
    documented
    in the start guide and would be all over example scripts, so a new user
    missing
    or forgetting it is not a realistic scenario.
    >>
    The advantages are that it's explicit, blocks are indented and it's
    impossible to
    miss which window is the action applied to, and at the same time
    actions are
    short and easy to type and read.
    >
    Thank you for your reply. What do you think of Chris Angelico's points?



    At the __exit__, further commands are no longer routed to that window;
    if it was a nested context, window is switched to the outer context,
    WHEN there are commands in it (i.e. on the first command). This seems
    pretty intuitive to me:


    with notepad1:
    ^S
    with notepad2:
    ^S
    write('something')


    >
    He wrote:
    What happens at the __exit__ of the context manager? What happens if
    context managers are nested? I'd be inclined to the simpler option of
    an explicit switch (since focus doesn't really "stack" and it'd feel
    weird for focus to *sometimes* switch away when you're done working
    with one window), though the context manager syntax does have its
    advantages too.
    >
    What I am most afraid of: that the window that's currently the
    context "disappears":
    notepad = start("Notepad")
    with notepad:
    press(ALT + TAB)
    write("Am I in Notepad now?")



    Alt-tab needs to be handled by a wrapper function that gives you the
    object of the window you've switched to:


    otherwin = alt_tab()
    with otherwin:
    ...


    If window is changed within 'with' block, the rest of block should be
    ignored. Perhaps there could also be a way to switch this behaviour off,
    for the entire script or for current block only.


    >
    What do you think of designs #3 and #4? >
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    switch_to(notepad_1)
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    switch_to(notepad_2)
    press(CTRL + 'v') >
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.activate()
    write("Hello World!")
    press(CTRL + 'a', CTRL + 'c')
    notepad_2.activate()
    press(CTRL + 'v') >
    I somehow prefer "activate" over "focus" as in my feeling, you'd
    normally say that you focus *on* something, so it should be called
    "focus_on" or "give_focus[_to]". Can you say, in everyday English, that
    you "focus a window"? I'm not a native speaker so maybe my feeling is
    misguided.




    These are ok, too, but I feel it's much easier to send commands to a
    wrong window vs. context managers. The same command in a different
    window can have vastly different and dangerous effect. In other python
    code that's generally not common at all, and would be bad style:


    lst = lst1
    lst.append('x')
    del lst[3]
    lst.insert(0, 'a')
    lst = lst2
    del lst[2]
    lst.append('y')
    lst = lst3
    lst.insert(0, 'x')
    lst += [1,2]




    I think current window should also be acquired explicitly:


    with get_current_window():
    type("some kind of snippet")


    For usage when a command should apply to all types of windows.


    HTH, -m






    --
    Lark's Tongue Guide to Python: http://lightbird.net/larks/


    Food is an important part of a balanced diet.
    Fran Lebowitz
  • Michael Herrmann at Mar 26, 2013 at 2:59 pm

    On Tuesday, March 26, 2013 2:41:38 PM UTC+1, Mitya Sirenef wrote:
    ...
    At the __exit__, further commands are no longer routed to that window;
    if it was a nested context, window is switched to the outer context,
    WHEN there are commands in it (i.e. on the first command). This seems
    pretty intuitive to me:

    with notepad1:
    ^S
    with notepad2:
    ^S
    write('something')
    ...
    What I am most afraid of: that the window that's currently the
    context "disappears":
    notepad = start("Notepad")
    with notepad:
    press(ALT + TAB)
    write("Am I in Notepad now?")

    Alt-tab needs to be handled by a wrapper function that gives you the
    object of the window you've switched to:

    otherwin = alt_tab()
    with otherwin:
    ...

    If window is changed within 'with' block, the rest of block should be
    ignored. Perhaps there could also be a way to switch this behaviour off,
    for the entire script or for current block only.

    What do you think of designs #3 and #4?
    ...
    These are ok, too, but I feel it's much easier to send commands to a
    wrong window vs. context managers. The same command in a different
    window can have vastly different and dangerous effect. In other python
    code that's generally not common at all, and would be bad style:

    lst = lst1
    lst.append('x')
    del lst[3]
    lst.insert(0, 'a')
    lst = lst2
    del lst[2]
    lst.append('y')
    lst = lst3
    lst.insert(0, 'x')
    lst += [1,2]

    I think current window should also be acquired explicitly:

    with get_current_window():
    type("some kind of snippet")

    For usage when a command should apply to all types of windows.

    I was skeptical of your suggestion at first but trying it out on an example script made me see its appeal:


    notepad_main = start("Notepad")
    with notepad_main:
    write("Hello World!")
    save_dialogue = press(CTRL + 's')
    with save_dialogue:
    write("test.txt", into="File name")
    click("Save")
    click("Close")


    Forcing the library user to always use the "with ..." seems like overkill though. I think the gained precision does not justify this burden on the library user. Hm....
  • Chris Angelico at Mar 26, 2013 at 3:16 pm

    On Wed, Mar 27, 2013 at 1:59 AM, Michael Herrmann wrote:
    save_dialogue = press(CTRL + 's')

    Does every single API need to then consider the possibility of focus
    changing? How does the press() function know that this will (or might
    - if the file's already been named, Ctrl-S won't open a dlg) change
    focus? How does the caller know?


    ChrisA
  • Mitya Sirenef at Mar 26, 2013 at 10:01 pm

    On 03/26/2013 10:59 AM, Michael Herrmann wrote:
    On Tuesday, March 26, 2013 2:41:38 PM UTC+1, Mitya Sirenef wrote:
    ...
    At the __exit__, further commands are no longer routed to that window;
    if it was a nested context, window is switched to the outer context,
    WHEN there are commands in it (i.e. on the first command). This seems
    pretty intuitive to me:
    >>
    with notepad1:
    ^S
    with notepad2:
    ^S
    write('something')
    >>
    >
    ...
    What I am most afraid of: that the window that's currently the
    context "disappears":
    >>
    notepad = start("Notepad")
    with notepad:
    press(ALT + TAB)
    write("Am I in Notepad now?")
    >>
    >>
    Alt-tab needs to be handled by a wrapper function that gives you the
    object of the window you've switched to:
    >>
    otherwin = alt_tab()
    with otherwin:
    ...
    >>
    If window is changed within 'with' block, the rest of block should be
    ignored. Perhaps there could also be a way to switch this behaviour off,
    for the entire script or for current block only.
    >>
    >>
    What do you think of designs #3 and #4?
    ...
    >>
    These are ok, too, but I feel it's much easier to send commands to a
    wrong window vs. context managers. The same command in a different
    window can have vastly different and dangerous effect. In other python
    code that's generally not common at all, and would be bad style:
    >>
    lst = lst1
    lst.append('x')
    del lst[3]
    lst.insert(0, 'a')
    lst = lst2
    del lst[2]
    lst.append('y')
    lst = lst3
    lst.insert(0, 'x')
    lst += [1,2]
    >>
    I think current window should also be acquired explicitly:
    >>
    with get_current_window():
    type("some kind of snippet")
    >>
    For usage when a command should apply to all types of windows.
    >
    I was skeptical of your suggestion at first but trying it out on an
    example script made me see its appeal:
    >
    notepad_main = start("Notepad")
    with notepad_main:
    write("Hello World!")
    save_dialogue = press(CTRL + 's')
    with save_dialogue:
    write("test.txt", into="File name")
    click("Save")
    click("Close") >
    Forcing the library user to always use the "with ..." seems like
    overkill though. I think the gained precision does not justify this
    burden on the library user. Hm....




    I don't see why that's a big deal, I've used AHK extensively and in my
    experience you don't switch windows all that often. I think it's best to
    optimize to have easy to type and read commands while you're working in
    the same window.


    I think you could argue that dialogs that belong to the main window
    should be handled implicitly, though. I think for other windows it'd
    definitely be good to use context managers, but for quick/simple dialogs
    it's too much hassle, although for large, complex dialogs that have
    inner tabs and require a lot of work, it again starts to make sense.


    At the very least, for small dialogs it's sipmpler to do:


    with press(CTRL + 's'):
    write("test.txt", into="File name")
    click("Save")




    -m




    --
    Lark's Tongue Guide to Python: http://lightbird.net/larks/


    Calamities are of two kinds: misfortunes to ourselves, and good fortune
    to others.
    Ambrose Bierce, The Devil's Dictionary
  • Michael Herrmann at Mar 27, 2013 at 8:45 am

    On Tuesday, March 26, 2013 4:16:57 PM UTC+1, Chris Angelico wrote:
    On Wed, Mar 27, 2013 at 1:59 AM, Michael Herrmann

    wrote:
    save_dialogue = press(CTRL + 's')
    Does every single API need to then consider the possibility of focus
    changing? How does the press() function know that this will (or might
    - if the file's already been named, Ctrl-S won't open a dlg) change
    focus? How does the caller know?

    While I can see where it is coming from, I am also not a big fan of this idea.


    Michael
  • Michael Herrmann at Mar 27, 2013 at 9:10 am

    On Tuesday, March 26, 2013 11:01:08 PM UTC+1, Mitya Sirenef wrote:
    On 03/26/2013 10:59 AM, Michael Herrmann wrote:
    ...
    Forcing the library user to always use the "with ..." seems like
    overkill though. I think the gained precision does not justify this
    burden on the library user. Hm....

    I don't see why that's a big deal, I've used AHK extensively and in my
    experience you don't switch windows all that often. I think it's best to
    optimize to have easy to type and read commands while you're working in
    the same window.

    I think you could argue that dialogs that belong to the main window
    should be handled implicitly, though. I think for other windows it'd
    definitely be good to use context managers, but for quick/simple dialogs
    it's too much hassle, although for large, complex dialogs that have
    inner tabs and require a lot of work, it again starts to make sense.

    At the very least, for small dialogs it's sipmpler to do:

    with press(CTRL + 's'):
    write("test.txt", into="File name")
    click("Save")

    I think what the context manager approach really has going for itself is the syntactic structure it gives to scripts, that makes it easy to see what is going on in which window. Semantically, however, I think the fit of this approach has some rough edges: The fact that there needs to be some special treatment for ALT + TAB, that actions such as `press` "sometimes" return values that are needed to continue the script and so on. It really has its appeal, but I think it's a bit too special and intricate to be used by a broad audience.

    Calamities are of two kinds: misfortunes to ourselves, and good fortune
    to others.

    ;-)


    Michael
    www.getautoma.com
  • Mitya Sirenef at Mar 27, 2013 at 1:56 pm

    On 03/27/2013 05:10 AM, Michael Herrmann wrote:
    At the very least, for small dialogs it's sipmpler to do:
    with press(CTRL + 's'):
    write("test.txt", into="File name")
    click("Save")
    I think what the context manager approach really has going for itself
    is the syntactic structure it gives to scripts, that makes it easy to
    see what is going on in which window. Semantically, however, I think
    the fit of this approach has some rough edges: The fact that there
    needs to be some special treatment for ALT + TAB, that actions such as
    `press` "sometimes" return values that are needed to continue the
    script and so on. It really has its appeal, but I think it's a bit too
    special and intricate to be used by a broad audience.
    >




    I think alt-tab has to be special in any case. Regular alt-tab would act
    like the GOTO statement. As a programmer looking at a script you have no
    idea where you just alt-tabbed to without possibly looking through
    dozens of lines of previous code.


    Keypresses that start a new window also seem pretty special to me.
    They're inherently special. After all, the essential function of a
    windowing system is when a new window is created, which means subsequent
    operations have an entirely different meaning, in a text editor <del>
    key will delete a character, in a file manager <del> key will delete a
    file!


    But, as I mentioned, if you can get away with treating simple dialogs
    implicitly (and I don't see why you can't, at this point), that'd be the
    preferred way for me.


    -m




    --
    Lark's Tongue Guide to Python: http://lightbird.net/larks/


    "The condition of man is already close to satiety and arrogance, and
    there is danger of destruction of everything in existence."
    - a Brahmin to Onesicritus, 327 BC, reported in Strabo's Geography
  • Michael Herrmann at Mar 28, 2013 at 10:52 am

    On Wednesday, March 27, 2013 2:56:55 PM UTC+1, Mitya Sirenef wrote:
    ...

    I think alt-tab has to be special in any case. Regular alt-tab would act
    like the GOTO statement. As a programmer looking at a script you have no
    idea where you just alt-tabbed to without possibly looking through
    dozens of lines of previous code.

    Keypresses that start a new window also seem pretty special to me.
    They're inherently special. After all, the essential function of a
    windowing system is when a new window is created, which means subsequent
    operations have an entirely different meaning, in a text editor <del>
    key will delete a character, in a file manager <del> key will delete a
    file!

    But, as I mentioned, if you can get away with treating simple dialogs
    implicitly (and I don't see why you can't, at this point), that'd be the
    preferred way for me.

    Ok. Thank you for your inputs!
  • Jean-Michel Pichavant at Mar 26, 2013 at 10:07 am

    ----- Original Message -----
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.write("Hello World!")
    notepad_1.press(CTRL + 'a', CTRL + 'c')
    notepad_2.press(CTRL + 'v')

    The problem with this design is that it effectively duplicates our
    API: We want to keep our "global" functions because they are so easy
    to read.

    So is the example above. This is the best solution in my opinion.
    I think you're having the same issue that some other APIs, let's say matplotlib for example. They try to accommodate scientists (matlab) and programmers(python) by having a double API style.


    One looks like


    legend()
    title()
    plot()
    save()


    the other looks like


    fig = figure()
    fig.add_legend()
    fig.title()
    fig.plot()
    fig.save()


    The problem is, when searching for example on the net, you'll end up with a mix of both, it can become a nightmare.
    I definitely prefer the later, for the reasons that have already been given to you in this thread and by the fact that with the correct (I)python shell, you can create your window object and get auto-completion on its methods just by hitting <tab>, very helpful when introspecting objects. Can be achieved of course in any python shell with function like dir() ; my point being that OOO design keeps things in their place, see the zen of python "Namespaces are one honking great idea -- let's do more of those!"


    JM






    -- IMPORTANT NOTICE:


    The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.
  • Michael Herrmann at Mar 26, 2013 at 11:52 am

    On Tuesday, March 26, 2013 11:07:45 AM UTC+1, Jean-Michel Pichavant wrote:
    ----- Original Message -----
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.write("Hello World!")
    notepad_1.press(CTRL + 'a', CTRL + 'c')
    notepad_2.press(CTRL + 'v')

    The problem with this design is that it effectively duplicates our
    API: We want to keep our "global" functions because they are so easy
    to read.
    So is the example above. This is the best solution in my opinion.

    Thanks for your reply. What do you mean by "So is the example above" though?

    I think you're having the same issue that some other APIs, let's say matplotlib for example. They try to accommodate scientists (matlab) and programmers(python) by having a double API style.

    One looks like

    legend()
    title()
    plot()
    save()

    the other looks like

    fig = figure()
    fig.add_legend()
    fig.title()
    fig.plot()
    fig.save()

    The problem is, when searching for example on the net, you'll end up with a mix of both, it can become a nightmare.

    Interesting point. I'll google a little about matplotlib.

    I definitely prefer the later, for the reasons that have already been given to you in this thread and by the fact that with the correct (I)python shell, you can create your window object and get auto-completion on its methods just by hitting <tab>, very helpful when introspecting objects. Can be achieved of course in any python shell with function like dir() ; my point being that OOO design keeps things in their place, see the zen of python "Namespaces are one honking great idea -- let's do more of those!"

    Doesn't the IPython do auto-completion for "global" functions?


    Thanks,
    Michael (www.getautoma.com)
  • Chris Angelico at Mar 26, 2013 at 11:57 am

    On Tue, Mar 26, 2013 at 10:52 PM, Michael Herrmann wrote:
    Doesn't the IPython do auto-completion for "global" functions?

    Even if it does, it'll be polluted with every other global. Methods
    don't have that problem. On the flip side, since presumably this is
    (will be) a module, anyone who wants autocomplete of its top-level
    functions can simply "import module" instead of "from module import
    *", which will do the same namespacing.


    ChrisA
  • Jean-Michel Pichavant at Mar 26, 2013 at 12:16 pm

    ----- Original Message -----
    On Tuesday, March 26, 2013 11:07:45 AM UTC+1, Jean-Michel Pichavant
    wrote:
    ----- Original Message -----
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.write("Hello World!")
    notepad_1.press(CTRL + 'a', CTRL + 'c')
    notepad_2.press(CTRL + 'v')

    ^
    here, this is an above example :D

    The problem with this design is that it effectively duplicates
    our
    API: We want to keep our "global" functions because they are so
    easy
    to read.
    So is the example above. This is the best solution in my opinion.
    Thanks for your reply. What do you mean by "So is the example above"
    though?

    Well the example above :).




    [snip]
    Doesn't the IPython do auto-completion for "global" functions?

    Yes it does, but as Chris pointed out, your global/module namespace will be "polluted" by a lot of names.
    By using completion on an object, you get the method it has access to, which is very useful to narrow down what you can do with it.


    JM




    -- IMPORTANT NOTICE:


    The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.
  • Michael Herrmann at Mar 26, 2013 at 12:20 pm

    On Tuesday, March 26, 2013 12:57:21 PM UTC+1, Chris Angelico wrote:
    On Tue, Mar 26, 2013 at 10:52 PM, Michael Herrmann
    Doesn't the IPython do auto-completion for "global" functions?
    Even if it does, it'll be polluted with every other global. Methods
    don't have that problem. On the flip side, since presumably this is
    (will be) a module, anyone who wants autocomplete of its top-level
    functions can simply "import module" instead of "from module import
    *", which will do the same namespacing.

    True! I don't think "polluting" the global namespace is that much of an issue.
  • Michael Herrmann at Mar 26, 2013 at 12:27 pm

    On Tuesday, March 26, 2013 1:16:56 PM UTC+1, Jean-Michel Pichavant wrote:
    ----- Original Message -----
    notepad_1 = start("Notepad")
    notepad_2 = start("Notepad")
    notepad_1.write("Hello World!")
    notepad_1.press(CTRL + 'a', CTRL + 'c')
    notepad_2.press(CTRL + 'v')
    ^
    here, this is an above example :D
    The problem with this design is that it effectively duplicates
    our
    API: We want to keep our "global" functions because they are so
    easy
    to read.
    So is the example above. This is the best solution in my opinion.

    Ah, so you meant "is also easy to read" ;) I agree but the example with global functions is even easier to read. I guess I am being pretty anal about these issues, but I see every unnecessary syntax we can save as a win.

    [snip]
    Doesn't the IPython do auto-completion for "global" functions?
    Yes it does, but as Chris pointed out, your global/module namespace will be "polluted" by a lot of names.
    By using completion on an object, you get the method it has access to, which is very useful to narrow down what you can do with it.

    I see. I know you prefer design #1 but that would at least place design #4 over #3, right?


    Thanks.
    Michael
    www.getautoma.com
  • Neil Cerutti at Mar 26, 2013 at 2:13 pm

    On 2013-03-25, Mitya Sirenef wrote:
    I think I would prefer context managers. I don't think it's a
    big problem for win users because this behaviour would be one
    of the first things documented in the start guide and would be
    all over example scripts, so a new user missing or forgetting
    it is not a realistic scenario.

    If window focus switching is really a rarity, and only done
    briefly then I agree that a context manager makes a nice and neat
    solution.


    But it's too powerful a generalisation for such a small corner
    case.


    Have you considered adding a keyword argument to each of your
    global functions, which is normally None, but allows a user to
    provide a prefered focus window?


    enter_text("test.txt", focus=save_dialog)
    press_button(Savebutton, focus=save_dialog)


    (Those are just guesses at your API functions; sorry.)


    When focus remains None, your usual assumptions about focus would
    apply, otherwise the user preference overrides it.


    --
    Neil Cerutti
  • Michael Herrmann at Mar 26, 2013 at 2:40 pm

    On Tuesday, March 26, 2013 3:13:30 PM UTC+1, Neil Cerutti wrote:
    On 2013-03-25, Mitya Sirenef wrote:

    I think I would prefer context managers. I don't think it's a
    big problem for win users because this behaviour would be one
    of the first things documented in the start guide and would be
    all over example scripts, so a new user missing or forgetting
    it is not a realistic scenario.

    If window focus switching is really a rarity, and only done
    briefly then I agree that a context manager makes a nice and neat
    solution.


    But it's too powerful a generalisation for such a small corner
    case.

    Have you considered adding a keyword argument to each of your
    global functions, which is normally None, but allows a user to
    provide a prefered focus window?

    enter_text("test.txt", focus=save_dialog)

    press_button(Savebutton, focus=save_dialog)

    It's an interesting new idea but I somehow feel it makes the existing functions too complicated. Also, having to add it to all existing, and future functions sounds a bit too cumbersome to me.

    (Those are just guesses at your API functions; sorry.)

    No worries! Thank you for your suggestion!


    Michael
    www.getautoma.com
  • Dave Angel at Mar 26, 2013 at 4:41 pm

    On 03/26/2013 10:40 AM, Michael Herrmann wrote:
    On Tuesday, March 26, 2013 3:13:30 PM UTC+1, Neil Cerutti wrote:

    <SNIP>
    Have you considered adding a keyword argument to each of your
    global functions, which is normally None, but allows a user to
    provide a prefered focus window?

    enter_text("test.txt", focus=save_dialog)

    press_button(Savebutton, focus=save_dialog)
    It's an interesting new idea but I somehow feel it makes the existing functions too complicated. Also, having to add it to all existing, and future functions sounds a bit too cumbersome to me.

    Perhaps Neil didn't make it clear enough. I figure he meant a keyword
    argument with an explicit default value of None. (or if you followed my
    earlier discussion, default value of focused)


    That way your user can keep using the functions for when there's no
    ambiguity, but add a focus= parameter only when needed.


    To go back to my sample wrapper functions, they'd look something like
    (untested):




    def write(*args, focus=focused):
    focus.write(*args)


    Of course, the user should only use the wrappers when things are sure to
    remain "simple."




    --
    DaveA
  • Neil Cerutti at Mar 26, 2013 at 5:25 pm

    On 2013-03-26, Dave Angel wrote:
    On 03/26/2013 10:40 AM, Michael Herrmann wrote:
    On Tuesday, March 26, 2013 3:13:30 PM UTC+1, Neil Cerutti wrote:

    <SNIP>
    Have you considered adding a keyword argument to each of your
    global functions, which is normally None, but allows a user to
    provide a prefered focus window?

    enter_text("test.txt", focus=save_dialog)

    press_button(Savebutton, focus=save_dialog)
    It's an interesting new idea but I somehow feel it makes the existing functions too complicated. Also, having to add it to all existing, and future functions sounds a bit too cumbersome to me.
    Perhaps Neil didn't make it clear enough. I figure he meant a keyword
    argument with an explicit default value of None. (or if you followed my
    earlier discussion, default value of focused)

    That way your user can keep using the functions for when there's no
    ambiguity, but add a focus= parameter only when needed.

    To go back to my sample wrapper functions, they'd look something like
    (untested):


    def write(*args, focus=focused):
    focus.write(*args)

    Of course, the user should only use the wrappers when things
    are sure to remain "simple."

    Yes, along those lines. Most code would never need to provide the
    focus= keyword. Only when setting focus in a weird way would it
    be needed.


    --
    Neil Cerutti
  • Michael Herrmann at Mar 27, 2013 at 8:55 am

    On Tuesday, March 26, 2013 5:41:42 PM UTC+1, Dave Angel wrote:
    On 03/26/2013 10:40 AM, Michael Herrmann wrote:
    On Tuesday, March 26, 2013 3:13:30 PM UTC+1, Neil Cerutti wrote:
    <SNIP>
    Have you considered adding a keyword argument to each of your
    global functions, which is normally None, but allows a user to
    provide a prefered focus window?
    enter_text("test.txt", focus=save_dialog)
    press_button(Savebutton, focus=save_dialog)
    It's an interesting new idea but I somehow feel it makes the existing functions too complicated. Also, having to add it to all existing, and future functions sounds a bit too cumbersome to me.
    Perhaps Neil didn't make it clear enough. I figure he meant a keyword
    argument with an explicit default value of None. (or if you followed my
    earlier discussion, default value of focused)

    That way your user can keep using the functions for when there's no
    ambiguity, but add a focus= parameter only when needed.

    To go back to my sample wrapper functions, they'd look something like
    (untested):

    def write(*args, focus=focused):
    focus.write(*args)

    I understood what you meant - I'm not so worried about the invocations, as of course the parameter can be omitted if there's a default value/behaviour. What I am worried about is the complexity this approach adds to several functions. Yes, you could argue that one keyword argument really isn't that much, but then you have to maintain and document it for all functions that have the new keyword parameter. In other words, a single functionality that is not needed 90% of the time increases the complexity of several, not really related functions. I am very grateful for your suggestions! But I don't think adding this keyword parameter is the way to go for us.


    Thanks,
    Michael
    www.getautoma.com
  • Chris Angelico at Mar 27, 2013 at 11:44 am

    On Wed, Mar 27, 2013 at 7:55 PM, Michael Herrmann wrote:
    On Tuesday, March 26, 2013 5:41:42 PM UTC+1, Dave Angel wrote:
    To go back to my sample wrapper functions, they'd look something like
    (untested):

    def write(*args, focus=focused):
    focus.write(*args)
    I understood what you meant - I'm not so worried about the invocations, as of course the parameter can be omitted if there's a default value/behaviour. What I am worried about is the complexity this approach adds to several functions. Yes, you could argue that one keyword argument really isn't that much, but then you have to maintain and document it for all functions that have the new keyword parameter. In other words, a single functionality that is not needed 90% of the time increases the complexity of several, not really related functions. I am very grateful for your suggestions! But I don't think adding this keyword parameter is the way to go for us.

    Not seeking to advocate this particular option, but it would be
    possible to make a single wrapper for all your functions to handle the
    focus= parameter:


    def focusable(func):
    @functools.wraps(func)
    def wrapper(*args,focus=None):
    if focus: focus.activate()
    return func(*args)
    return wrapper


    Then you just decorate all your functions with that:
    def write(string):
    # do something with the active window


    ChrisA
  • Michael Herrmann at Mar 27, 2013 at 12:23 pm

    On Wednesday, March 27, 2013 12:44:49 PM UTC+1, Chris Angelico wrote:
    ...
    Not seeking to advocate this particular option, but it would be
    possible to make a single wrapper for all your functions to handle the
    focus= parameter:

    def focusable(func):
    @functools.wraps(func)
    def wrapper(*args,focus=None):
    if focus: focus.activate()
    return func(*args)
    return wrapper

    Then you just decorate all your functions with that:

    def write(string):
    # do something with the active window

    Hi, sure, I wouldn't have copy-pasted the code and of course there are techniques to avoid code duplication. It's not so much what I'm worried about. What I'm worried about is that the concept of window-switching gets "smeared" over several other not-really related concepts such as clicking and typing. I feel it violates orthogonality: http://www.artima.com/intv/dry3.html is the best freely available resource I could find but I think it's best explained in The Pragmatic Programmer http://www.amazon.com/Pragmatic-Programmer-Journeyman-Master/dp/020161622X.


    Michael
    www.getautoma.com
  • Michael Herrmann at Apr 4, 2013 at 7:05 am
    Hi everyone,


    we just released the new version of our GUI automation tool with the improved API: http://www.getautoma.com/blog/Automa-1-5-1-window-switching. Thank you again for your help.


    Best regards,
    Michael

    On Monday, March 25, 2013 8:29:23 PM UTC+1, Michael Herrmann wrote:
    Hello everyone,



    my name is Michael, I'm the lead developer of a Python GUI automation library for Windows called Automa: http://www.getautoma.com. We want to add some features to our library but are unsure how to best expose them via our API. It would be extremely helpful for us if you could let us know which API design feels "right" to you.



    Our API already offers very simple commands for automating the GUI of a Windows computer. For example:



    from automa.api import *

    start("Notepad")

    write("Hello World!")

    press(CTRL + 's')

    write("test.txt", into="File name")

    click("Save")

    click("Close")



    When you execute this script, Automa starts Notepad and simulates key strokes, mouse movements and clicks to perform the required commands. At the moment, each action is performed in the currently active window.



    We do not (yet) have a functionality that allows you to explicitly switch to a specific window. Such a functionality would for instance make it possible to open two Notepad windows using the start(...) command, and copy text between them.



    One API design would be to have our start(...) function return a "Window" (say) object, whose methods allow you to do the same operations as the global functions write(...), press(...), click(...) etc., but in the respective window. In this design, the example of operating two Notepad windows could be written as



    notepad_1 = start("Notepad")

    notepad_2 = start("Notepad")

    notepad_1.write("Hello World!")

    notepad_1.press(CTRL + 'a', CTRL + 'c')

    notepad_2.press(CTRL + 'v')



    The problem with this design is that it effectively duplicates our API: We want to keep our "global" functions because they are so easy to read. If we add methods to a new "Window" class that do more or less the same, we feel that we are violating Python's principle that "There should be one - and preferably only one - obvious way to do it."



    An alternative design would be to make the window switching an explicit action. One way of doing this would be to add a new global function, say "switch_to" or "activate", that takes a single parameter that identifies the window to be switched to. We could still have start(...) return a Window object, that could then be passed to our function:



    notepad_1 = start("Notepad")

    notepad_2 = start("Notepad")

    switch_to(notepad_1)

    write("Hello World!")

    press(CTRL + 'a', CTRL + 'c')

    switch_to(notepad_2)

    press(CTRL + 'v')



    Maybe our Window objects could also be used as context managers:



    notepad_1 = start("Notepad")

    notepad_2 = start("Notepad")

    with notepad_1:

    write("Hello World!")

    press(CTRL + 'a', CTRL + 'c')

    with notepad_2:

    press(CTRL + 'v')



    As a final idea, switching could also be done as a method of the Window class:



    notepad_1 = start("Notepad")

    notepad_2 = start("Notepad")

    notepad_1.activate()

    write("Hello World!")

    press(CTRL + 'a', CTRL + 'c')

    notepad_2.activate()

    press(CTRL + 'v')



    It would be extremely helpful for us if you could let me know which way of using the API you would prefer. If you opt for an explicit version, how would you call the respective method? "activate" / "switch_to" / "focus" or something else?



    Thank you so much!



    Best wishes,

    Michael

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
grouppython-list @
categoriespython
postedMar 25, '13 at 7:29p
activeApr 4, '13 at 7:05a
posts47
users9
websitepython.org

People

Translate

site design / logo © 2022 Grokbase