dahlia / logging-spinner

Display spinners (in CLI) through Python standard logging
https://pypi.python.org/pypi/logging-spinner
GNU General Public License v3.0
34 stars 1 forks source link

How about using a context manager? #1

Open KleinerNull opened 7 years ago

KleinerNull commented 7 years ago

Implementing a spinner through a context manager would provide a even better interface. Something like this for example:

with Spinner() as s:
    s.info('Loading please wait.')
    time.sleep(interval)
    s.info('Done')

So you could provide even more complex behaivor like stopping the spinner or change color or whatever.

For a simple spinner you could also provide this:

with Spinner(msg='Loading please wait', final_msg='Done.'):
    time.sleep()

It is just a suggestion, because I really like the context manager interface, keeps the code cleaner and so on ;)

dahlia commented 7 years ago

No matter how it would look like, I'm sure that we need to any way to manage contexts. The current logging-spinner has nothing about this at all.

One thing making me to consider is that logging-spinner's first purpose is to display spinners without introducing any third-party APIs. Applications don't have to depend on logging-spinner's own API, but a loose protocol of the user_waiting extra field. However, context managers introduce its own new APIs.

Actually logging-spinner uses pyspin under the hood. Although it doesn't provide context managers, there's @make_spin decorator to manage contexts.

KleinerNull commented 7 years ago

I don't see any third party API introduction here. It is just a simple context manager, you can provide him additionally:

In [1]: class SpinManager:
   ...:     def __init__(self, logger, start_msg, final_msg):
   ...:         self.logger = logger
   ...:         self.start_msg = start_msg
   ...:         self.final_msg = final_msg
   ...:     def __enter__(self):
   ...:         self.logger.info(self.start_msg, extra={'user_waiting': True})
   ...:     def __exit__(self, *args):
   ...:         self.logger.info(self.final_msg, extra={'user_waiting': False})
   ...:         

In [2]: from logging_spinner import SpinnerHandler

In [3]: import logging

In [4]: logger = logging.getLogger('manager')

In [5]: logger.setLevel(logging.INFO)

In [6]: logger.addHandler(SpinnerHandler())

In [7]: from time import sleep

In [8]: with SpinManager(logger, 'Loading', 'Done'):
   ...:     sleep(1)
   ...:     
⠏ Loading
Done

The whole purpose of context manager in python is to manage resources. Everything that has some kind of open-close or start-end procedures is suited perfect for this. Opening and automatically closing files, connect and disconnect to databases, creating and deleting of tmp files and dictionaries etc.

Creating takes one or two minutes, why not provide it from the beginning?

By the way controlling the spinner is also not that hard:

In [14]: class SpinController(SpinManager):
    ...:     def start(self, msg):
    ...:         self.logger.info(msg, extra={'user_waiting': True})
    ...:     def stop(self, msg):
    ...:         self.logger.info(msg, extra={'user_waiting': False})

In [16]: with SpinController(logger, 'Loading', 'Done') as spinner:
    ...:     sleep(1)
    ...:     spinner.stop('Halt!')
    ...:     sleep(1)
    ...:     spinner.start('Loading again.')
    ...:     sleep(1)
    ...:     
    ...:     
    Halt!    # the first ⠏ Loading was overwritten by Halt, but it was there.
    ⠏ Loading again.
    Done

The with statement or also called context manager is considered as very safe, clean and therefore pythonic!