FAQ
Only recently I have started developing code for application providing
both a GUI and a command line interface (CLI). Naturally I want to reuse
the business logic code for both GUI and CLI interfaces. The problem is
to provide feedback to the GUI on the one hand, to the CLI on the other
hand - by the same piece of code. My example shows a find_files() method
browsing through a directory tree. As this might take a while every
directory browsed shall be displayed on the terminal or in the GUI (an
optimisation is to display the current directory only once a second).
When a file fitting the criteria is found it is shown as well in both
terminal and GUI list widget.

I can easily write a piece of code for the CLI for that:

import logging
import os

LOG = logging.getLogger()

def find_files(src_dir):
for root, dirs, files in os.walk(src_dir):
for file in files:
# feedback for the CLI
LOG.info("scanning %s %s", root, file)
# check for file pattern here
if os.path.exists(file2):
# feedback for the CLI
LOG.info("found %s", file2)
# retrieve more file_details
files.append((file, file_details))
else:
LOG.warn("no file found for %s", file)

and for the GUI version:

import Queue
import os

class Model:
def __init__(self, model):
self.model = model
self.status_text = ""

def notify()
"send message to Tk root widget to tell GUI thread to
synchronise view"

def find_files(src_dir, model):
for root, dirs, files in os.walk(src_dir):
for file in files:
# feedback for the GUI
model.status_text = "scanning %s %s" % (root, file)
model.notify()
# check for file pattern here
if os.path.exists(file2):
# feedback for the GUI
# retrieve more file_details
model.files.append((file, file_details))
model.notify()
else:
pass

Now I have duplicated the "business logic" of find_files() for a GUI
driven application and a CLI application. Using the same code is easy as
long as no feedback is required during operation. But in this case the
os.walk() might scan an entire disk taking a minute or two. Here both
the terminal or the GUI need to provide feedback about the directories
being scanned or the user might think the application has died (I would
certainly).

Solutions with callbacks seem kind of ugly to me as they require "model"
to be passed through for GUI and LOG for the CLI version.

Does a good (pythonic) pattern exist to solve this in a better way?

Note: sorry, the code sample will not run on its own. I thought it'd be
to lengthy to have a functioning sample, especially for the gui.

--
Andreas Balogh
baloand (at) gmail.com

Search Discussions

  • Chris Rebert at Apr 16, 2009 at 7:55 pm

    On Thu, Apr 16, 2009 at 12:36 PM, Andreas Balogh wrote:
    Only recently I have started developing code for application providing both
    a GUI and a command line interface (CLI). Naturally I want to reuse the
    business logic code for both GUI and CLI interfaces. The problem is to
    provide feedback to the GUI on the one hand, to the CLI on the other hand -
    by the same piece of code. My example shows a find_files() method browsing
    through a directory tree. As this might take a while every directory browsed
    shall be displayed on the terminal or in the GUI (an optimisation is to
    display the current directory only once a second). When a file fitting the
    criteria is found it is shown as well in both terminal and GUI list widget.

    I can easily write a piece of code for the CLI for that:

    ? import logging
    ? import os

    ? LOG = logging.getLogger()

    ? def find_files(src_dir):
    ? ? ? for root, dirs, files in os.walk(src_dir):
    ? ? ? ? ? for file in files:
    ? ? ? ? ? ? ? # feedback for the CLI
    ? ? ? ? ? ? ? LOG.info("scanning %s %s", root, file)
    ? ? ? ? ? ? ? # check for file pattern here
    ? ? ? ? ? ? ? if os.path.exists(file2):
    ? ? ? ? ? ? ? ? ? # feedback for the CLI
    ? ? ? ? ? ? ? ? ? LOG.info("found %s", file2)
    ? ? ? ? ? ? ? ? ? # retrieve more file_details
    ? ? ? ? ? ? ? ? ? files.append((file, file_details))
    ? ? ? ? ? ? ? else:
    ? ? ? ? ? ? ? ? ? LOG.warn("no file found for %s", file)

    and for the GUI version:

    ? import Queue
    ? import os

    ? class Model:
    ? ? ? def __init__(self, model):
    ? ? ? ? ? self.model = model
    ? ? ? ? ? self.status_text = ""

    ? ? ? def notify()
    ? ? ? ? ? "send message to Tk root widget to tell GUI thread to
    ? synchronise view"

    ? def find_files(src_dir, model):
    ? ? ? for root, dirs, files in os.walk(src_dir):
    ? ? ? ? ? for file in files:
    ? ? ? ? ? ? ? # feedback for the GUI
    ? ? ? ? ? ? ? model.status_text = "scanning %s %s" % (root, file)
    ? ? ? ? ? ? ? model.notify()
    ? ? ? ? ? ? ? # check for file pattern here
    ? ? ? ? ? ? ? if os.path.exists(file2):
    ? ? ? ? ? ? ? ? ? # feedback for the GUI
    ? ? ? ? ? ? ? ? ? # retrieve more file_details
    ? ? ? ? ? ? ? ? ? model.files.append((file, file_details))
    ? ? ? ? ? ? ? ? ? model.notify()
    ? ? ? ? ? ? ? else:
    ? ? ? ? ? ? ? ? ? pass

    Now I have duplicated the "business logic" of find_files() for a GUI driven
    application and a CLI application. Using the same code is easy as long as no
    feedback is required during operation. But in this case the os.walk() might
    scan an entire disk taking a minute or two. Here both the terminal or the
    GUI need to provide feedback about the directories being scanned or the user
    might think the application has died (I would certainly).

    Solutions with callbacks seem kind of ugly to me as they require "model" to
    be passed through for GUI and LOG for the CLI version.
    Really? Seems like quite a simple and elegant solution to me. IMHO, a
    mere one extra parameter does not bad code make.

    Cheers,
    Chris
    --
    I have a blog:
    http://blog.rebertia.com
  • Norseman at Apr 23, 2009 at 12:24 am

    Chris Rebert wrote:
    On Thu, Apr 16, 2009 at 12:36 PM, Andreas Balogh wrote:
    Only recently I have started developing code for application providing both
    a GUI and a command line interface (CLI). Naturally I want to reuse the
    business logic code for both GUI and CLI interfaces. The problem is to
    provide feedback to the GUI on the one hand, to the CLI on the other hand -
    by the same piece of code. My example shows a find_files() method browsing
    through a directory tree. As this might take a while every directory browsed
    shall be displayed on the terminal or in the GUI (an optimisation is to
    display the current directory only once a second). When a file fitting the
    criteria is found it is shown as well in both terminal and GUI list widget.

    I can easily write a piece of code for the CLI for that:

    import logging
    import os

    LOG = logging.getLogger()

    def find_files(src_dir):
    for root, dirs, files in os.walk(src_dir):
    for file in files:
    # feedback for the CLI
    LOG.info("scanning %s %s", root, file)
    # check for file pattern here
    if os.path.exists(file2):
    # feedback for the CLI
    LOG.info("found %s", file2)
    # retrieve more file_details
    files.append((file, file_details))
    else:
    LOG.warn("no file found for %s", file)

    and for the GUI version:

    import Queue
    import os

    class Model:
    def __init__(self, model):
    self.model = model
    self.status_text = ""

    def notify()
    "send message to Tk root widget to tell GUI thread to
    synchronise view"

    def find_files(src_dir, model):
    for root, dirs, files in os.walk(src_dir):
    for file in files:
    # feedback for the GUI
    model.status_text = "scanning %s %s" % (root, file)
    model.notify()
    # check for file pattern here
    if os.path.exists(file2):
    # feedback for the GUI
    # retrieve more file_details
    model.files.append((file, file_details))
    model.notify()
    else:
    pass

    Now I have duplicated the "business logic" of find_files() for a GUI driven
    application and a CLI application. Using the same code is easy as long as no
    feedback is required during operation. But in this case the os.walk() might
    scan an entire disk taking a minute or two. Here both the terminal or the
    GUI need to provide feedback about the directories being scanned or the user
    might think the application has died (I would certainly).

    Solutions with callbacks seem kind of ugly to me as they require "model" to
    be passed through for GUI and LOG for the CLI version.
    Really? Seems like quite a simple and elegant solution to me. IMHO, a
    mere one extra parameter does not bad code make.

    Cheers,
    Chris
    =======================================

    Do what?
    'Solutions with callbacks seem kind of ugly to me as they require
    "model" to be passed through for GUI and LOG for the CLI version.
    '
    Print subdir being searched to Command Line and/or to msgbox in GUI.
    No big thing.


    Something to consider:

    Build program with the intent that the "guts", "main portion", "common
    stuff" or whatever you want to call it goes in one callable section.
    Then the I/O is split into two parts. One "Command Line" and one "GUI".

    I'm not familiar with Mac; MSDOS is probably not a real concdern here
    and the rest that I have played with and/or use will take care of
    routing user screen (or window or whatever) to/from program. All the
    I/O sections need is proper coding as if they were the only I/O to be
    used. Then it won't matter which way it is called. This way, that way,
    couple of these and a few more of those ---- who cares.... You are
    using a multi-user, multi-tasking OS aren't you? (don't answer, just a
    reminder)

    A small piece of code can detect which I/O module is best. Did the
    "start program" request come from a command line or a "click-it"?
    Activate accordingly. OR do like the DOSEMU people. Program has two
    distinct ways to be started. (actually there are more but ...) One is
    to type "dosemu" the other is to type "xdosemu". The second is a soft
    link in Linux. ie... another name for the same program. The program
    checks the name (argv[0]) used to start it and selects the I/O requested
    and BINGO... things work as expected. Under Windows you might put each
    of the the two names in their own small frontends and let the frontend
    specified call the primary passing it which I/O to use. In Linux you
    just put the program in your path and ln -s ./program xprogram and
    use the argv[0] to see which was activated and thus which I/O to use.


    To repeat the above:
    "... a mere one extra parameter does not bad code make."
    Meaning I agree with Chris.


    The three piece job has more reusable sections and makes the program
    proper a single maintainable piece. Nicer in larger programs. But both
    methods get the job done.

    As far as getting the job done is concerned, getting the job done is
    what it is all about. Do you really need titanium cobalt steel in the
    hammer that is only going to be used to hang the occasional picture
    somewhere in the house? On the other hand, if hanging big pictures in
    public buildings is your occupation, then....


    Steve
  • MRAB at Apr 16, 2009 at 8:07 pm

    Andreas Balogh wrote:
    Only recently I have started developing code for application providing
    both a GUI and a command line interface (CLI). Naturally I want to reuse
    the business logic code for both GUI and CLI interfaces. The problem is
    to provide feedback to the GUI on the one hand, to the CLI on the other
    hand - by the same piece of code. My example shows a find_files() method
    browsing through a directory tree. As this might take a while every
    directory browsed shall be displayed on the terminal or in the GUI (an
    optimisation is to display the current directory only once a second).
    When a file fitting the criteria is found it is shown as well in both
    terminal and GUI list widget.

    I can easily write a piece of code for the CLI for that:

    import logging
    import os

    LOG = logging.getLogger()

    def find_files(src_dir):
    for root, dirs, files in os.walk(src_dir):
    for file in files:
    # feedback for the CLI
    LOG.info("scanning %s %s", root, file)
    # check for file pattern here
    if os.path.exists(file2):
    # feedback for the CLI
    LOG.info("found %s", file2)
    # retrieve more file_details
    files.append((file, file_details))
    else:
    LOG.warn("no file found for %s", file)

    and for the GUI version:

    import Queue
    import os

    class Model:
    def __init__(self, model):
    self.model = model
    self.status_text = ""

    def notify()
    "send message to Tk root widget to tell GUI thread to
    synchronise view"

    def find_files(src_dir, model):
    for root, dirs, files in os.walk(src_dir):
    for file in files:
    # feedback for the GUI
    model.status_text = "scanning %s %s" % (root, file)
    model.notify()
    # check for file pattern here
    if os.path.exists(file2):
    # feedback for the GUI
    # retrieve more file_details
    model.files.append((file, file_details))
    model.notify()
    else:
    pass

    Now I have duplicated the "business logic" of find_files() for a GUI
    driven application and a CLI application. Using the same code is easy as
    long as no feedback is required during operation. But in this case the
    os.walk() might scan an entire disk taking a minute or two. Here both
    the terminal or the GUI need to provide feedback about the directories
    being scanned or the user might think the application has died (I would
    certainly).

    Solutions with callbacks seem kind of ugly to me as they require "model"
    to be passed through for GUI and LOG for the CLI version.

    Does a good (pythonic) pattern exist to solve this in a better way?

    Note: sorry, the code sample will not run on its own. I thought it'd be
    to lengthy to have a functioning sample, especially for the gui.
    You could pass a notification object into the function. There would be a
    CLINotification class and a GUINotification class, both having methods
    for reporting status info ('report_status') and adding files
    ('add_files').

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
grouppython-list @
categoriespython
postedApr 16, '09 at 7:36p
activeApr 23, '09 at 12:24a
posts4
users4
websitepython.org

People

Translate

site design / logo © 2022 Grokbase