Explorar el Código

refactor callback hell

Daniel Sheffield hace 2 años
padre
commit
e1766aeaf0

+ 49 - 26
app/activities/NewProduct.py

@@ -1,62 +1,75 @@
 #
-# Copyright (c) Daniel Sheffield 2022 - 2022
+# Copyright (c) Daniel Sheffield 2022 - 2023
 #
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
 from collections import OrderedDict
 
-import urwid
-from app.activities import ActivityManager
-from app.db_utils import NON_IDENTIFIER_COLUMNS, QueryManager
-
+from urwid import (
+    connect_signal,
+    AttrMap,
+    Button,
+    Divider,
+    ListBox,
+    LineBox,
+    Overlay,
+    Padding,
+    Pile,
+    SimpleListWalker,
+    Text,
+)
+from . import ActivityManager
+from ..db_utils import NON_IDENTIFIER_COLUMNS, QueryManager, get_insert_product_statement
 from ..widgets import AutoCompleteEdit, AutoCompletePopUp
 
-
-class NewProduct(urwid.Overlay):
+class NewProduct(Overlay):
 
     def __init__(self,
         activity_manager: ActivityManager,
         query_manager: QueryManager,
         under, name, data,
-        autocomplete_cb, change_cb, apply_cb, esc_cb,
+        change_cb,
     ):
-        self.esc_cb = esc_cb
         self.under = under
+        self.activity_manager = activity_manager
         self.query_manager = query_manager
         self.name = name
 
-        title = urwid.Text('Enter Product Info', align='center')
+        title = Text('Enter Product Info', align='center')
         self.fields = OrderedDict()
+        self.autocomplete_callback = lambda widget, options: len(options) and widget._emit('open', options)
         for f in ('product', 'category', 'group'):
             w = AutoCompleteEdit(('bg', f))
             self.fields[f] = w
             w.set_edit_text(data[f])
-            urwid.connect_signal(w, 'change', lambda w, v: change_cb(w.name, v))
-            urwid.connect_signal(w, 'apply', lambda w, name: autocomplete_cb(w, name, self.data))
-
-        ok = urwid.Button('Done', on_press=lambda _: apply_cb(**self.data))
-
-        body = urwid.AttrMap(urwid.ListBox(urwid.SimpleListWalker([
-            urwid.Padding(
-                urwid.Pile([
-                    #urwid.Divider(),
-                    urwid.AttrMap(title, 'banner'),
-                    #urwid.Divider(),
-                    *[urwid.AttrMap(urwid.LineBox(urwid.AttrMap(
+            connect_signal(w, 'change', lambda w, v: change_cb(w.name, v))
+            connect_signal(w, 'apply', lambda w, name: self.autocomplete_callback(
+                w, query_manager.unique_suggestions(name, **self.data)
+            ))
+
+        ok = Button('Done', on_press=lambda _: self.insert_new_product())
+
+        body = AttrMap(ListBox(SimpleListWalker([
+            Padding(
+                Pile([
+                    #Divider(),
+                    AttrMap(title, 'banner'),
+                    #Divider(),
+                    *[AttrMap(LineBox(AttrMap(
                         AutoCompletePopUp(
                             v,
                             self.apply_choice,
                             lambda: activity_manager.show(self.update())
                         ),'streak'), title=k.title(), title_align='left'), 'bg'
                     ) for k,v in self.fields.items()],
-                    urwid.Divider(),
-                    urwid.AttrMap(ok, 'bg'),
+                    Divider(),
+                    AttrMap(ok, 'bg'),
                 ], focus_item=2),
                 align='center', left=1, right=1,
             )
         ])), 'banner')
-        super().__init__(urwid.AttrMap(body, 'bg'), under,
+        super().__init__(AttrMap(body, 'bg'), under,
             align='center', width=('relative', 40),
             valign='middle', height=13,
             min_width=20)
@@ -72,6 +85,16 @@ class NewProduct(urwid.Overlay):
         for k,v in _data.items():
             self.fields[k].set_edit_text(v)
 
+    def insert_new_product(self):
+        product, category, group = [ self.fields[k].get_edit_text() for k in (
+            'product', 'category', 'group'
+        )]
+        self.activity_manager.app.log.write(
+            f'{get_insert_product_statement(product, category, group)};\n'
+        )
+        self.query_manager.insert_new_product(product, category, group)
+        self.activity_manager.show(self.under.update())
+
     def apply_choice(self, name, value):
         self.data = {
             name: value
@@ -91,7 +114,7 @@ class NewProduct(urwid.Overlay):
 
     def keypress(self, size, key):
         if key == 'esc':
-            self.esc_cb()
+            self.activity_manager.show(self.under.update())
             return
 
         if key == 'tab':

+ 5 - 5
app/activities/PriceCheck.py

@@ -1,5 +1,5 @@
 #
-# Copyright (c) Daniel Sheffield 2021 - 2022
+# Copyright (c) Daniel Sheffield 2021 - 2023
 #
 # All rights reserved
 #
@@ -173,9 +173,6 @@ class PriceCheck(FocusWidget):
     def __init__(self,
         activity_manager: ActivityManager,
         query_manager: QueryManager,
-        autocomplete_cb: Callable[[
-            Union[AutoCompleteEdit, AutoCompleteFloatEdit], str, dict
-        ], None],
     ):
         button_group = []
         self.buttons = {
@@ -221,9 +218,12 @@ class PriceCheck(FocusWidget):
         self.query_manager = query_manager
         self.organic_checkbox = self.checkboxes['organic']
         connect_signal(self.organic_checkbox, 'postchange', lambda _,v: self.update())
+        self.autocomplete_callback = lambda widget, options: len(options) and widget._emit('open', options)
         for (k, ef) in self.edit_fields.items():
             connect_signal(ef, 'postchange', lambda _,v: self.update())
-            connect_signal(ef, 'apply', lambda w, name: autocomplete_cb(w, name, self.data))
+            connect_signal(ef, 'apply', lambda w, name: self.autocomplete_callback(
+                w, query_manager.unique_suggestions(name, **self.data)
+            ))
 
         for b in button_group:
             connect_signal(b, 'postchange',lambda *_: self.update())

+ 38 - 9
app/activities/TransactionEditor.py

@@ -1,5 +1,5 @@
 #
-# Copyright (c) Daniel Sheffield 2021 - 2022
+# Copyright (c) Daniel Sheffield 2021 - 2023
 #
 # All rights reserved
 #
@@ -33,6 +33,7 @@ from ..widgets import (
 )
 from . import ActivityManager
 from .Rating import Rating
+from .NewProduct import NewProduct
 
 class TransactionEditor(FocusWidget):
 
@@ -181,17 +182,43 @@ class TransactionEditor(FocusWidget):
         self.rating.update_rating(_avg, _min, _max, unit, price=price, quantity=quantity)
         self.update_graph(df)
 
+    def autocomplete_callback(self, widget, name, options):
+        if len(options):
+            widget._emit('open', options)
+            return
+        
+        new_product_activity = self.activity_manager.get('new_product')
+        current_activity = self.activity_manager.current()
+        if current_activity is not new_product_activity and name in (
+            'product', 'category', 'group'
+        ):
+            self.new_product_callback(name)
+        return
+    
+    def new_product_callback(self, name):
+        cur = self.activity_manager.current()
+        txn = self.activity_manager.get('transaction')
+        new_product = self.activity_manager.create(
+            NewProduct, 'new_product',
+            self.activity_manager, self.query_manager,
+            cur, name, txn.data,
+            txn.apply_changes,
+        )
+        self.activity_manager.show(new_product)
+
+    def save_and_clear_callback(self):
+        txn = self.activity_manager.get('transaction')
+        self.activity_manager.app.save(txn.data)
+        txn.clear()
+        txn.focus_on(txn.edit_fields['product'])
+        self.activity_manager.show(txn.update())
 
     def __init__(self,
         activity_manager: ActivityManager,
         query_manager: QueryManager,
-        save_and_clear_cb,
-        autocomplete_cb: Callable[[
-            Union[AutoCompleteEdit, AutoCompleteFloatEdit], str, dict
-        ], None],
     ):
+        self.activity_manager = activity_manager
         self.query_manager = query_manager
-        self.save_and_clear_cb = save_and_clear_cb
         self.buttons = {
             'done': Button(('streak', u'Done')),
             'clear': Button(('streak', u'Clear')),
@@ -260,8 +287,10 @@ class TransactionEditor(FocusWidget):
             )
         })
         for (k, ef) in self.edit_fields.items():
-            connect_signal(ef, 'postchange', lambda _,v: self.update())
-            connect_signal(ef, 'apply', lambda w, name: autocomplete_cb(w, name, self.data))
+            connect_signal(ef, 'postchange', lambda *_: self.update())
+            connect_signal(ef, 'apply', lambda w, name: self.autocomplete_callback(
+                w, name, query_manager.unique_suggestions(name, **self.data)
+            ))
 
         _widgets.update(dict([
             (k, LineBox(
@@ -302,7 +331,7 @@ class TransactionEditor(FocusWidget):
                 ])),
             ])
         })
-        connect_signal(self.buttons['done'], 'click', lambda _: save_and_clear_cb())
+        connect_signal(self.buttons['done'], 'click', lambda _: self.save_and_clear_callback())
         connect_signal(self.buttons['clear'], 'click', lambda _: self.clear())
 
         banner = Pile([

+ 7 - 61
grocery_transactions.py

@@ -1,22 +1,16 @@
 #!/usr/bin/python3
 #
-# Copyright (c) Daniel Sheffield 2021 - 2022
+# Copyright (c) Daniel Sheffield 2021 - 2023
 #
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
 import sys
-from sqlite3 import Cursor
-from typing import Union
-
-import urwid
-from urwid import raw_display
+from psycopg import Cursor
+from urwid import raw_display, WidgetPlaceholder, SolidFill, MainLoop
 from app.activities import ActivityManager, show_or_exit
-from app.activities.NewProduct import NewProduct
 from app.activities.TransactionEditor import TransactionEditor
-from app.db_utils import (QueryManager, display_mapper,
-                          get_insert_product_statement)
-from app.widgets import AutoCompleteEdit, AutoCompleteFloatEdit
+from app.db_utils import QueryManager, display_mapper
 from app.palette import solarized
 
 try:
@@ -38,58 +32,12 @@ except:
     print('Failed to set up db connection. Entering Mock mode')
     from mock import *
 
-def _insert_new_product_callback(activity_manager, query_manager, product, category, group):
-    activity_manager.app.log.write(
-        '{};\n'.format(get_insert_product_statement(product, category, group)))
-    query_manager.insert_new_product(product, category, group)
-    activity_manager.show(activity_manager.get('transaction').update())
-
-def _new_product_callback(
-    activity_manager: ActivityManager,
-    query_manager: QueryManager,
-    name: str
-):
-    cur = activity_manager.current()
-    txn : TransactionEditor = activity_manager.get('transaction')
-    activity_manager.show(
-        activity_manager.create(NewProduct, 'new_product',
-            activity_manager, query_manager,
-            cur, name, txn.data,
-            lambda w, t, data: _autocomplete_callback(activity_manager, query_manager, w, t, data),
-            txn.apply_changes,
-            lambda product, category, group: _insert_new_product_callback(
-                activity_manager, query_manager, product, category, group),
-            lambda: activity_manager.show(cur.update()),
-        )
-    )
-
-def _autocomplete_callback(
-    activity_manager: ActivityManager,
-    query_manager: QueryManager,
-    widget: Union[AutoCompleteEdit, AutoCompleteFloatEdit],
-    name: str, data: dict
-):
-    options = query_manager.unique_suggestions(name, **data)
-    if len(options) > 0:
-        widget._emit('open', options)
-    elif len(options) == 0 and activity_manager.current() is not activity_manager.get('new_product'):
-        if name in ('product', 'category', 'group'):
-            _new_product_callback(activity_manager, query_manager, name)
-
-
-def _save_and_clear_callback(activity_manager):
-    txn = activity_manager.get('transaction')
-    activity_manager.app.save(txn.data)
-    txn.clear()
-    txn.focus_on(txn.edit_fields['product'])
-    activity_manager.show(txn.update())
-
 args = sys.argv
 log = args[1]
 
-class GroceryTransactionEditor(urwid.WidgetPlaceholder):
+class GroceryTransactionEditor(WidgetPlaceholder):
     def __init__(self, activity_manager, cur, log):
-        super().__init__(urwid.SolidFill(u'/'))
+        super().__init__(SolidFill(u'/'))
         self.activity_manager = activity_manager
         self.cur = cur
         txn: TransactionEditor = self.activity_manager.get('transaction')
@@ -159,8 +107,6 @@ query_manager = QueryManager(cur, display_mapper)
 
 activity_manager.create(TransactionEditor, 'transaction',
     activity_manager, query_manager,
-    lambda: _save_and_clear_callback(activity_manager),
-    lambda widget, name, data: _autocomplete_callback(activity_manager, query_manager, widget, name, data),
 )
 
 app = None
@@ -176,7 +122,7 @@ palettes = iter_palettes()
 try:
     app = GroceryTransactionEditor(activity_manager, cur, log)
     screen = raw_display.Screen()
-    loop = urwid.MainLoop(app, next(palettes), screen=screen,
+    loop = MainLoop(app, next(palettes), screen=screen,
         unhandled_input=lambda k: show_or_exit(k, screen=screen, palettes=palettes),
         pop_ups=True
     )

+ 7 - 21
price_check.py

@@ -1,16 +1,12 @@
 #!/usr/bin/python3
 #
-# Copyright (c) Daniel Sheffield 2021 - 2022
+# Copyright (c) Daniel Sheffield 2021 - 2023
 #
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-from sqlite3 import Cursor
-from typing import Union
-
-import urwid
-from urwid import raw_display
-from app.widgets import (AutoCompleteEdit, AutoCompleteFloatEdit)
+from psycopg import Cursor
+from urwid import raw_display, WidgetPlaceholder, SolidFill, MainLoop
 from app.activities import ActivityManager, show_or_exit
 from app.activities.PriceCheck import PriceCheck
 from app.db_utils import QueryManager, display_mapper
@@ -36,18 +32,9 @@ except:
     exit(1)
     #from mock import *
 
-def _autocomplete_callback(
-    query_manager: QueryManager,
-    widget: Union[AutoCompleteEdit, AutoCompleteFloatEdit],
-    name: str, data: dict
-):
-    options = query_manager.unique_suggestions(name, **data)
-    if len(options) > 0:
-        widget._emit('open', options)
-
-class GroceryPriceCheck(urwid.WidgetPlaceholder):
+class GroceryPriceCheck(WidgetPlaceholder):
     def __init__(self, activity_manager):
-        super().__init__(urwid.SolidFill(u'/'))
+        super().__init__(SolidFill(u'/'))
         self.activity_manager = activity_manager
         price_check = self.activity_manager.get('price_check')
 
@@ -60,8 +47,7 @@ activity_manager = ActivityManager()
 query_manager = QueryManager(cur, display_mapper)
 
 activity_manager.create(PriceCheck, 'price_check',
-    activity_manager, query_manager,
-    lambda widget, name, data: _autocomplete_callback(query_manager, widget, name, data),
+    activity_manager, query_manager
 )
 
 app = GroceryPriceCheck(activity_manager)
@@ -76,7 +62,7 @@ def iter_palettes():
 palettes = iter_palettes()
 
 screen = raw_display.Screen()
-loop = urwid.MainLoop(app, next(palettes), screen=screen,
+loop = MainLoop(app, next(palettes), screen=screen,
         unhandled_input=lambda k: show_or_exit(k, screen=screen, palettes=palettes),
         pop_ups=True)
 loop.run()