Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IDLE: configdialog -- factor out VarTrace class #75036

Closed
terryjreedy opened this issue Jul 5, 2017 · 22 comments
Closed

IDLE: configdialog -- factor out VarTrace class #75036

terryjreedy opened this issue Jul 5, 2017 · 22 comments
Assignees
Labels
3.7 (EOL) end of life topic-IDLE type-feature A feature request or enhancement

Comments

@terryjreedy
Copy link
Member

BPO 30853
Nosy @terryjreedy, @csabella
PRs
  • bpo-30853: IDLE: TraceVar class and tests #2872
  • [3.6] bpo-30853: IDLE: Factor a VarTrace class from configdialog.Con… #2903
  • bpo-30853: IDLE: Convert font and general vars to use VarTrace #2914
  • [3.6] bpo-30853: IDLE: Convert font and general vars to use VarTrace … #2935
  • bpo-30853: IDLE - touch-up configdialog.VarTrace and tests. #2936
  • [3.6] bpo-30853: IDLE - touch-up configdialog.VarTrace and tests. (GH… #2937
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/terryjreedy'
    closed_at = <Date 2017-07-28.22:37:27.464>
    created_at = <Date 2017-07-05.06:03:26.402>
    labels = ['expert-IDLE', 'type-feature', '3.7']
    title = 'IDLE: configdialog -- factor out VarTrace class'
    updated_at = <Date 2017-07-28.22:39:16.355>
    user = 'https://github.com/terryjreedy'

    bugs.python.org fields:

    activity = <Date 2017-07-28.22:39:16.355>
    actor = 'terry.reedy'
    assignee = 'terry.reedy'
    closed = True
    closed_date = <Date 2017-07-28.22:37:27.464>
    closer = 'terry.reedy'
    components = ['IDLE']
    creation = <Date 2017-07-05.06:03:26.402>
    creator = 'terry.reedy'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 30853
    keywords = []
    message_count = 22.0
    messages = ['297703', '298886', '298921', '298929', '299012', '299014', '299270', '299271', '299274', '299280', '299283', '299286', '299312', '299313', '299359', '299382', '299388', '299420', '299428', '299430', '299438', '299439']
    nosy_count = 2.0
    nosy_names = ['terry.reedy', 'cheryl.sabella']
    pr_nums = ['2872', '2903', '2914', '2935', '2936', '2937']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue30853'
    versions = ['Python 3.6', 'Python 3.7']

    @terryjreedy
    Copy link
    Member Author

    configdialog.ConfigDialog creates about 20 tk Variables corresponding to configuration options. It defines a var_changed_xyz callback for each xyz variable. 6 have a common template. ConfigDialog turns tracing on in attach_var_callbacks and off in remove_var_callbacks. Each of these two functions has the callbacks set hard-coded.

    Proposal: Factor out what can. Register callbacks in set when var created. Iterate register to attach and remove callbacks. Something like

    def IVariable:
    changes = <instance of changes> # set externally somehow
    var = set()

        def register(self, callback)
            if isinstance(callback, tuple):
                self.callback = default_callback
                self.args = callback
            else:
                self.callback = callback
            self.vars.add()
    
        def default_callback(self):  # Used for 6 vars.
            changes.add_item(*self.args, self.get())
    
        @classmethod
        def attach(cls):
            for var in cls.vars:
            var.trace_add('write', self.callback)
    
        @classmethod
        def remove(cls):
            for var in cls.vars:
                var.trace_remove('write', var.trace_info()[0][1])
            cls.vars = set()

    To get String/Int/BooleanVars, maybe this will work. (I have only toyed with multiple inheritance.)

    class IString(tk.StringVar, IVariable):
        def __init__(self, callback).
            StringVar.__init()  # Possibly not needed with no value to pass.
            self.register(callback)

    @terryjreedy terryjreedy self-assigned this Jul 5, 2017
    @terryjreedy terryjreedy added topic-IDLE type-feature A feature request or enhancement labels Jul 5, 2017
    @terryjreedy
    Copy link
    Member Author

    This issue is really about managing the set of var,callback pairs. The idea of wrapping vars just clouded the issue.

    class VarTrace:
        def __init__(self):
            self.tracers = []  # or set if need to delete
        def add(self, var, callback):
            if isinstance(callback, tuple):
                callback = self.make_callback(var, callback)
            self.vartrace.add((var, callback))
            return var
        def make_callback(self, var, args):  # Used for 6 vars.
            def var_callback(*params):
                changes.add_item(*args, var.get())
            return var_callback
        def attach(self):
            for var, callback in self.tracers:
                var.trace_add('write', callback)
        def remove(cls):  # detach?
            for var, _ in self.tracers:
                var.trace_remove('write', var.trace_info()[0][1]) # doc this

    # A functionalist would define a function that returns a sequence of closures.

    tracers = VarTrace()

    # write tests, then patch creation of tested vars, retest, patch rest.

    •    self.font_name = StringVar(parent)
      

    + self.font_name = tracers.add(StringVar(parent), var_changed_font)

    @terryjreedy terryjreedy changed the title IDLE: configdialog -- factor out Variable subclass IDLE: configdialog -- factor out Tracer subclass Jul 23, 2017
    @csabella
    Copy link
    Contributor

    Would you like this change for fonts and indent before the tests for the other tabs are ready or would you like to have tests for all the tabs first?

    @terryjreedy
    Copy link
    Member Author

    Step 1 is to add the class and then add tests. The tests can use artificial examples. Say 3 vars, 2 callbacks, with the 3rd done as default. I want to start this now -- see below. Let me know if you start working on this so we don't duplicate work.

    I just determined that calling trace_add with the same var,c pair is a bad idea.

    import tkinter as tk
    r = tk.Tk()
    v = tk.StringVar(r)
    def c(*args): print("hello", args)
    print(v.trace_add('write', c))
    print(v.trace_add('write', c))
    v.set('a')

    2545142942664c
    2545153771784c
    hello ('PY_VAR0', '', 'write')
    hello ('PY_VAR0', '', 'write')

    The class needs two lists: untraced and traced. Attach should pop from untraced and append to traced. The reverse for detach (remove renamed).
    One of the test tracers should increment an int var, so we can test that after calling attach twice, there is one one increment.

    I will take care of looking at the doc or code to refresh my memory of why the detach is written the way it is.

    Step 2, putting this in use, is a prerequisite for writing proper tests for extracted tab page classes, which are a prerequisite for putting the class in use. So I am inclined to convert font vars when a tested class is ready and temporarily have old and new attach functions called.

    We may discover a need or at least a use for other VarTrace methods when writing tests.

    @csabella
    Copy link
    Contributor

    I would like to work on this, but I don't think I'd be able to have something ready for a few days, so I don't want to hold you up if you would like it sooner.

    @terryjreedy
    Copy link
    Member Author

    I am going to continue with the tests.

    @terryjreedy
    Copy link
    Member Author

    New changeset 45bf723 by Terry Jan Reedy (csabella) in branch 'master':
    bpo-30853: IDLE: Factor a VarTrace class from configdialog.ConfigDialog. (bpo-2872)
    45bf723

    @terryjreedy
    Copy link
    Member Author

    I will include new blurb with PR that uses this with font vars. I might do this tonight.

    Test coverage of class is 100%.

    @terryjreedy terryjreedy added the 3.7 (EOL) end of life label Jul 26, 2017
    @csabella
    Copy link
    Contributor

    Thanks!

    I can try the font vars if you like.

    One question I keep forgetting to ask -- I can't figure out how remove_var_callbacks gets called. I've grepped for the name and didn't find it anywhere. I don't know what I'm missing.

    @terryjreedy
    Copy link
    Member Author

    New changeset 0243bea by Terry Jan Reedy in branch '3.6':
    [3.6] bpo-30853: IDLE: Factor a VarTrace class from configdialog.ConfigDialog. (GH-2872) (bpo-2903)
    0243bea

    @terryjreedy
    Copy link
    Member Author

    Go ahead. If it works with font, add general tab.

    CD.remove_var_callbacks is currently only called in test_configdialog.tearDownModule. I added the call to prevent getting a TclError for each callback after the test finished. .destroy does not destroy callbacks.

    I presume remove_var_callbacks was written to be called in self.cancel. Then perhaps someone discovered that there was no visible effect of deleting it, perhaps because TclErrors are caught and ignored. I want to put it, or the new equivalent, into cancel, before destroy. And make sure to add to teardownmodule.

    @terryjreedy
    Copy link
    Member Author

    See in you can install blurb into 3.6 or even 3.5, (pip should work) and run it to add the blurb file. I will fill in the body.

    @csabella
    Copy link
    Contributor

    The font vars went well, so I also added general vars.

    I created the blurb. I didn't know if it would work with no body, so I just put a placeholder.

    I did have another question. Instead of just one tracers.add() method, I was wondering if there's an advantage to having one called add_callback(var, callback) and a separate one called add_default(var, config)? Design-wise, I don't know which is the preferred way, but changing the existing code seemed a little hackish.

    @csabella
    Copy link
    Contributor

    Just as an FYI with blurb, I installed it in the same venv as coverage, so I was able to run it with 3.7. It's really cool.

    @terryjreedy
    Copy link
    Member Author

    A minor change. Complete the path by adding the other var-trace pairs and after checking carefully, I will be ready to push.

    I prefer one easy to remember short-name function to two longer-named functions. However, the docstring should specify the tuple as idleConf config-type, section, and option names. Either *add* or *make_callback* could check len = 3 and callback[0] in idleConf.config_types, with a test added.

    "changing the existing code seemed a little hackish": I don't understand exactly what you think is hackish.

    There might be people who would prefer (possibly as less hakish)

    def add(tkvar, parent, cb_info):
        var = tkvar(parent)
        if isinstance(callback, tuple):
            callback = self.make_callback(var, cb_info)
        else:
            callback = cb_info
        self.untraced.append((var, callback))
        return var

    I don't know if this would make the add calls readable, but is does factor the var calls into one place, replaces () with ',' (at the cost of passing one more reference), and avoids the hackish return of an input. It also simplifies the code in a way by making it 'flatter' instead of nested. Did I miss something? What do you think?

    The parameter name change is an independent idea that should satisfy someone who objects to calling something an x that is not an x.

    @csabella
    Copy link
    Contributor

    Instead 'hackish', maybe I should have used 'magic'. The overloading just wasn't obvious to me, meaning I have:

    self.font_bold = tracers.add(BooleanVar(parent), self.var_changed_font)
    self.space_num = tracers.add(IntVar(parent), ('main', 'Indent', 'num-spaces'))

    We defined VarTrace as being (var, callback) pairs and the second example isn't sending a function. So, even though I understand what we're doing, I wanted to ask about using different names for my own education. I was even thinking of a different interface --

    add(var, callback=default, config=None)

    If config was specified even for the non-default callbacks, then each var could have its config defined at create time instead of in the var_changed* function. This wouldn't work for theme/keys name and name2 though (I think that's the only one with two add_option calls). If the callback didn't have a changes.add_option, then it can send None for config.

    I hadn't thought of separating parent, but I like that idea. It fits in with the rest of how the widgets are created.

    So, if both changes were incorporated:

    self.font_bold = tracers.add(parent, BooleanVar, 
                             callback=self.var_changed_font, 
                             config=('main', 'EditorWindow', 'font-bold'))
    self.space_num = tracers.add(parent, IntVar, 
                             callback=default, 
                             config=('main', 'Indent', 'num-spaces'))

    Maybe that expands VarTrace too much? Or maybe instead of (var, callback) pairs, it's a dictionary? tracers = {var: (callback, config)}

    And then the non-default var_changed methods could use:
    changes.add_option(*tracers[var].config, value)

    Wouldn't work for var_changed_font because that has the three add_option calls.

    @csabella
    Copy link
    Contributor

    "Either *add* or *make_callback* could check len = 3 and callback[0] in idleConf.config_types, with a test added."

    I didn't add this because I wasn't sure what you wanted to happen if it wasn't right. I suspect it should fail gracefully (not break IDLE), but in what way?

    @terryjreedy
    Copy link
    Member Author

    New changeset 5b59154 by Terry Jan Reedy (csabella) in branch 'master':
    bpo-30853: IDLE: Convert font and general vars to use VarTrace (bpo-2914)
    5b59154

    @terryjreedy
    Copy link
    Member Author

    New changeset 02f88d2 by Terry Jan Reedy in branch '3.6':
    [3.6] bpo-30853: IDLE: Convert font and general vars to use VarTrace (GH-2914) (bpo-2935)
    02f88d2

    @terryjreedy
    Copy link
    Member Author

    IDLE *will* fail if idleConf.SetOption is called with a wrong config-type (KeyError) or the wrong number of other arguments (TypeError). I did not add the check because it does not cover the non-default callbacks (the majority), let alone all the other idleConf calls. The important thing is to make sure that every callback gets called in a test (and indeed, eventually, that every line is executed). Enhancing the doc string should be enough.

    @terryjreedy
    Copy link
    Member Author

    New changeset 5d0f30a by Terry Jan Reedy in branch 'master':
    bpo-30853: IDLE - touch-up configdialog.VarTrace and tests. (bpo-2936)
    5d0f30a

    @terryjreedy
    Copy link
    Member Author

    New changeset ecc80b3 by Terry Jan Reedy in branch '3.6':
    [3.6] bpo-30853: IDLE - touch-up configdialog.VarTrace and tests. (GH-2936) (bpo-2937)
    ecc80b3

    @terryjreedy terryjreedy changed the title IDLE: configdialog -- factor out Tracer subclass IDLE: configdialog -- factor out VarTrace class Jul 28, 2017
    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.7 (EOL) end of life topic-IDLE type-feature A feature request or enhancement
    Projects
    None yet
    Development

    No branches or pull requests

    2 participants