Help me pick an API design (OO vs functional)

Discussion in 'Python' started by Michael Herrmann, Mar 25, 2013.

  1. 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 somefeatures 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 weadd methods to a new "Window" class that do more or less the same, we feelthat 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
     
    Michael Herrmann, Mar 25, 2013
    #1
    1. Advertising

  2. Michael Herrmann

    Kwpolska Guest

    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
     
    Kwpolska, Mar 25, 2013
    #2
    1. Advertising

  3. 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 targetgroup 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 theresources 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!
     
    Michael Herrmann, Mar 25, 2013
    #3
  4. 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 targetgroup 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 theresources 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!
     
    Michael Herrmann, Mar 25, 2013
    #4
  5. 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 makesit 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 andsome 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
     
    Chris Angelico, Mar 25, 2013
    #5
  6. Michael Herrmann

    Ethan Furman Guest

    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~
     
    Ethan Furman, Mar 25, 2013
    #6
  7. 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/
     
    Mitya Sirenef, Mar 25, 2013
    #7
  8. 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 havethe 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
     
    Michael Herrmann, Mar 26, 2013
    #8
  9. 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 callit 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 youedit 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
     
    Michael Herrmann, Mar 26, 2013
    #9
  10. 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
     
    Michael Herrmann, Mar 26, 2013
    #10
  11. Michael Herrmann

    Dave Angel Guest

    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
     
    Dave Angel, Mar 26, 2013
    #11
  12. 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 switchto a
    > specific window. Such a functionality would for instance make it possibleto
    > 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
     
    Chris Angelico, Mar 26, 2013
    #12
  13. 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
     
    Chris Angelico, Mar 26, 2013
    #13
  14. 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 witha 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)
     
    Michael Herrmann, Mar 26, 2013
    #14
  15. 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 witha 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)
     
    Michael Herrmann, Mar 26, 2013
    #15
  16. 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
     
    Chris Angelico, Mar 26, 2013
    #16
  17. 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. Ifyou 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 switchto 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
     
    Michael Herrmann, Mar 26, 2013
    #17
  18. 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 inthe 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
     
    Michael Herrmann, Mar 26, 2013
    #18
  19. 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 inthe 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
     
    Michael Herrmann, Mar 26, 2013
    #19
  20. 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
     
    Michael Herrmann, Mar 26, 2013
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. tom c
    Replies:
    5
    Views:
    400
    tom c
    Nov 1, 2006
  2. Replies:
    1
    Views:
    110
    Richard Cornford
    Sep 24, 2006
  3. Max Bucknell

    Functional vs. Object oriented API

    Max Bucknell, Apr 11, 2013, in forum: Python
    Replies:
    7
    Views:
    164
    Rui Maciel
    Apr 13, 2013
  4. Xavier Ho
    Replies:
    0
    Views:
    106
    Xavier Ho
    Apr 11, 2013
  5. Ian Kelly
    Replies:
    0
    Views:
    142
    Ian Kelly
    Apr 11, 2013
Loading...

Share This Page