Closed moi90 closed 2 years ago
Enlighten already supports this somewhat in two ways:
Custom manager classes
If you want to create a new type of manager, just subclass enlighten._basemanager.BaseManager and define the required methods, stop()
, write()
, and _flush_streams()
. This is how the two existing manager classes, for the terminal and Jupyter notebooks, are created. This is not documented or exposed in the public API, but it probably should be. get_manager()
won't know about this class, but get_manager()
is really just a convenience function to get a manager class, and if you're creating your own manager class, you'd know better when it should be used.
String formatting only
If you only want the text and do not want Enlighten to handle the output, you can use the Counter.format()
and StatusBar.format()
methods. You may want to pass the width
parameter appropriate to your application.
It seems like custom manager classes are pretty close to what you're asking for. Will it satisfy your ask if they are better documented and BaseManager
is exposed in the public API (likely as enlighten.manager.BaseManager
)?
Thanks for the quick response!
Yes, I had a look at BaseManager. The problem is that it is still tightly coupled to a TTY, which is also apparent in NotebookManager:
I think that a further abstraction could be helpful here.
if you're creating your own manager class, you'd know better when it should be used.
That is really the problem I want to address: The application programmer knows which manager to use, the library programmer doesn't. Therefore, in a library, I would want get_default_manager()
to return whatever the application set up.
Application:
manager: BaseManager = ...
enlighten.set_default_manager(manager)
lib.do_stuff()
Library:
def do_stuff():
# We have no knowledge of the special needs of the application that is using us.
manager = enlighten.get_default_manager() # Default Manager instance OR whatever was previously configured by set_default_manager
with manager.counter():
...
(I assume it would be better to introduce get_default_manager()
without any parameters than to use get_manager(...)
, because the library wouldn't be able to setup any special configuration anyways.)
Alternative
Libraries could include a function to set up an enlighten manager. However, this would make their interface more complicated. This could be ok for one library, but for multiples, it would lead to much code duplication and quickly becomes a mess.
Application:
manager: BaseManager = ...
lib.set_enlighten_manager(manager)
lib2.set_enlighten_manager(manager)
lib3.set_enlighten_manager(manager)
...
lib.do_stuff()
Library:
_enlighten_manager = enlighten.get_manager()
def set_enlighten_manager(m)
global _enlighten_manager
_enlighten_manager = m
def do_stuff():
with _enlighten_manager.counter():
...
NotebookManager isn't tightly coupled to a TTY, it's basically an HTML manager. I can see why you'd think that, but the use of Blessed.Terminal in that manager is only to handle color and support additional text formatting. Essentially, the colors and other formatting become terminal escape codes which are later parsed into HTML. It handles a lot of the work of string length and color scaling that would need to be done in other ways if it wasn't used. Probably the terminal class could be configurable to implement other types terminals, but that hasn't really been needed.
I'm not sure it makes sense to implement this in Enlighten. Generally you only want one manager class instance (though I could see a case where maybe you'd have a terminal manager and an HTML manager in the same program), so get_default_manager()
would only get called once. It which case, it doesn't make sense to define a default and then call a function that returns what you just set.
Your alternative is more in line with with what I might expect, but it would belong in your library and set_enlighten_manager()
should take a manager instance not a manager class. I also personally don't like the use of globals, so I'd move it to a class so it's in it's own namespace. Something like this.
Library:
class LibConfig:
enlighten_manager = None
def set_enlighten_manager(self, manager):
self.enlighten_manager = manager
lib_config = LibConfig()
set_enlighten_manager = lib_config.set_enlighten_manager
Application:
set_enlighten_manager(manager())
That said, I prefer, when possible, to use an object as the main library interface like Enlighten does with Manager
and Blessed does with Terminal
. Even larger libraries like FastAPI use this model.
class Lib:
def __init__(self, manager):
self.manager = manager
# Other library methods
...
Thank you for your thoughts! I will go with the library option and come back if this develops into something that could fit into Enlighten.
Is your feature request related to a problem? Please describe. I want to use enlighten in a library. I want to use the default enlighten frontend when the library is used in a command line program, but I want to allow users of the library to hook into the progress reporting, for example to display it in a GUI or to store the progress of an asynchronous job in a web application.
Describe the solution you'd like Ideally, I would some
enlighten.set_default_manager
function that allows a consumer of my library to configure the behavior of enlighten, e.g. to forward status updates to a GUI Widget or status report of a background job.Describe alternatives you've considered Alternatively, I could build a wrapper that provides an interface similar to enlighten but allows to select different backends.
Additional context I might build the wrapper solution anyway but if you're open to this feature request, I would build it so that it can be merged into enlighten some time in the future.
An abstraction like this could also help in the case of #43 where child processes could be set up so that progress updates are reported back to the parent process and displayed there.