jquast / blessed

Blessed is an easy, practical library for making python terminal apps
http://pypi.python.org/pypi/blessed
MIT License
1.18k stars 71 forks source link

Blessed converting integer to parameterized string. #229

Closed SystematicError closed 2 years ago

SystematicError commented 2 years ago

Hello recently I've been working on a prototype terminal app and I've been facing some issues. Blessed seems to convert my int type variable into blessed.formatters.ParameterizingString when passing it through StatusBar.render. My code is provided down below:

from .config_loader import config
from .gui.components import Border, StatusBar
from .gui.core import App

class FileManager(App):
    def __init__(self):
        super().__init__()
        self.current_workspace = 0

    def on_load(self):
        keybinds = config.keybinds

        self.keybinds[keybinds["quit"]] = quit

        # for workspace_keybind in keybinds["workspaces"]:
        #    self.keybinds[workspace_keybind] = lambda: setattr(self, "current_workspace", int(workspace_keybind))

    def render(self):
        StatusBar(self).render(self.current_workspace)
        Border(self).render(20)

FileManager()
class App(Terminal):
    def __init__(self):
        super().__init__()

        self.keybinds = {}

        def handle_resize(*args):
            print(self.clear)
            self.render()

        with self.fullscreen(), self.cbreak(), self.hidden_cursor():
            signal(SIGWINCH, handle_resize)

            self.on_load()
            self.render()

            while True:
                key = self.inkey()

                if key in self.keybinds:
                    self.keybinds[key]()

                self.render()
avylove commented 2 years ago

Can you provide some a simplified example that illustrates the issue? It should be standalone code that we can copy and paste to execute.

SystematicError commented 2 years ago

Sure, here is a simplified version:

from blessed import Terminal

class BaseApp(Terminal):
    def __init__(self):
        super().__init__()
        self.my_int_1 = 0
        self.render()

class App(BaseApp):
    def __init__(self):
        super().__init__()
        self.my_int_2 = 0

    def render(self):
        print(f"Integer defined in parent class is of type {type(self.my_int_1)}")
        print(f"Integer defined in child class is of type {type(self.my_int_2)}")

App()

And here is the output:

Integer defined in parent class is of type <class 'int'>
Integer defined in parent class is of type <class 'blessed.formatters.ParameterizingString'>
avylove commented 2 years ago

The issue is you're not referencing App.my_int_2 since you're calling render() before you've declared it, so it's going to Terminal.getattr() which will return an empty ParameterizingString if an attribute can't be resolved.

>>> term = Terminal()
>>> my_int = term.my_int_2
>>> my_int
''
>>> type(my_int)
<class 'blessed.formatters.ParameterizingString'>

I would suggest not subclassing Terminal and instead create an attribute to store the terminal object. Generally it's best to create one Terminal instance and use it for the life of the program. There are a lot of cached lookups so you can save a lot of overhead depending on what you're doing.

Here's a simplified case, or you can see what I did in Enlighten.

class BaseApp:
    def __init__(self, term=None):
            self.term = term or blessed.Terminal()
SystematicError commented 2 years ago

Thanks for the help!