Cannot get tool method run to execute

Hi,
Developing a tool (my first) to check a db, but cannot get the run method to execute except by explicitly calling it in the tool init method (which I am sure is not what’s intended, if I do that the tool does what I expect). I have looked at the Tool code but cannot figure it out. Also, I have an options class derived from MenuToolOptions and can see it getting initialized and its add_menu_options method being called, but am not getting a window for interaction. Thanks.

I’m not going to be much direct help but…
do you have a repository on GitHub with the code shared? Other volunteers will be able to hone in on an answer if they can peek at your code.

Not yet, will do that. Thanks

1 Like

You could share your code here too if you want to do the GitHub account later.

Post the code snippet with 3 backticks on the lines above and below.
Adding python after the initial backticks line would make this forum apply the right formatting:

Let’s see if this makes any sense to anybody. Thanks.

"""FamilySearch Check Tool"""
import os
import pdb
from typing import List, Optional, AnyStr, NoReturn
# from queue import Queue
import logging
import re
# from unicodedata import category

# from gi.repository import Gtk

import gramps.gen.lib
from gramps.gen.plug.menu import BooleanListOption
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gui.plug import tool, MenuToolOptions
#from gramps.gen.plug.menu import FilterOption, StringOption, BooleanOption
from gramps.gen.plug.menu import StringOption
from gramps.gui.managedwindow import ManagedWindow
# from gramps.gen.simple import SimpleAccess

from genealogy_familysearch import (FamilySearchPopulator, Missing,
    FamilySearchPerson)
from genealogy_gramps import GrampsPopulator, GrampsPerson
# from po.update_po import merge

LOG = logging.getLogger("FSCheck")
LOG.setLevel(logging.DEBUG)
_ = glocale.translation.sgettext


class WorkItem:
    """Work Item to process"""
    def __init__(self, gid: AnyStr, fsid: AnyStr, gen: int = 0):
        """Initializer"""
        self._gid: str = gid
        self._fsid: str = fsid
        self._gen: int = gen

    @property
    def gid(self) -> str:
        """Gramps ID"""
        return self._gid

    @property
    def fsid(self) -> str:
        """FamilySearch ID"""
        return self._fsid

    @property
    def gen(self) -> int:
        """Generation of the work item"""
        return self._gen


# class FamilySearchCheck(tool.Tool, ManagedWindow):
class FamilySearchCheck(tool.Tool, ManagedWindow):
    """Check a tree against FamilySearch"""
    # pylint: disable=unused-argument
    # pylint: disable=too-many-arguments,too-many-positional-arguments
    def __init__(self, dbstate, user, options_class, name, callback=None):
        LOG.debug("FamilySearchCheck init")
        # self.window = None
        # uistate = user.uistate
        # This inheritance provides the db property
        # This will instantiate options_class and set self.options to the
        # result
        tool.Tool.__init__(self, dbstate, options_class, name)
        # tool.BatchTool.__init__(self, dbstate, user, options_class, name)

        # self.window_name = _('FamilySearch Check Tool')
        # ManagedWindow.__init__(self, user.uistate, [], self.__class__)
        # self.set_window(Gtk.Window(), Gtk.Label(), self.window_name)

        # We need a stack
        self._todo: List = []
        #self._inque: Set = set()
        self._gens: int = 0
        # We are only ever dealing with a single database
        # self._sdb = dbstate
        LOG.debug("Database: %s", self.db)

        # The populators
        self._gpop = None
        self._fspop = None
        # self.show()

        # self.run()
        LOG.debug("FamilySearchCheck init done")

class FamilySearchCheckOptions(MenuToolOptions):
    """Option class for event description editor."""

    def __init__(self, name, person_id=None, dbstate=None):
        # name is the id from the gpr
        LOG.debug("FamilySearchCheckOptions init")
        # add_menu_options is called here
        MenuToolOptions.__init__(self, name, person_id, dbstate)
        pdb.set_trace()
        LOG.debug("FamilySearchCheckOptions init done")

    def add_menu_options(self, menu):
        """Menu options."""
        LOG.debug("Adding options")
        # menu.filter_list = CustomFilters.get_filters("Event")
        # all_filter = GenericFilterFactory("Event")()
        # all_filter.set_name(_("All Events"))
        # all_filter.add_rule(rules.event.AllEvents([]))
        # all_filter_in_list = False
        # for fltr in menu.filter_list:
        #     if fltr.get_name() == all_filter.get_name():
        #         all_filter_in_list = True
        # if not all_filter_in_list:
        #     menu.filter_list.insert(0, all_filter)
        #
        # events = FilterOption(_("Events"), 0)
        # menu.add_option(_("Option"), "events", events)
        # events.set_filters(menu.filter_list)
        #
        #find = StringOption(_("Find"), "")
        #menu.add_option(_("Option"), "find", find)

        category_name = "Tool Options"
        merge = BooleanListOption("Merge options")
        merge.add_button("Always", False)
        merge.add_button("Confirm", False)
        merge.add_button("Never", True)
        menu.add_option(category_name, "merge", merge)

        # replace = StringOption(_("Replace"), "")
        # menu.add_option(_("Option"), "replace", replace)
        #
        # keep_old = BooleanOption(_("Replace substring only"), False)
        # keep_old.set_help(_("If True only the substring will be replaced, "
        #                     "otherwise the whole description will be deleted "
        #                     "and replaced by the new one."))
        # menu.add_option(_("Option"), "keep_old", keep_old)
        #
        # regex = BooleanOption(_("Allow regex"), False)
        # regex.set_help(_("Allow regular expressions."))
        # menu.add_option(_("Option"), "regex", regex)
        LOG.debug("Adding options done")

1 Like

Committed to GitHub - lschopitea/gramps-addon-familysearch-check: Gramps addon to check person's ancestors against FamilySearch

1 Like

FamilySearch, eh? Nice target!

You might want to collaborate with @jmichault
His https://raw.githubusercontent.com/jmichault/gramps-kromprogramoj/gramps52 repository can be added to your Addon Manager (see adding project to Addon Manager) He has already in beta with 2 tools and a gramplet related to to FamilySearch.

And the old repository of ElderEvans has a decade-old stagnant barebones experiment. But he’s the contact in the LDS technical organization.

The US Web Connect in searches for FamilySearch. But it keeps falling afoul of linkrot in login sysem and the search parameter passing.

Trivial things first…

set your addons Registration help_url attribute to your GitHub repository while you are in development. People can communicate with you via GitHub Issues

help_url="https://github.com/lschopitea/gramps-addon-familysearch-check/tree/main/FamilySearchCheck",

Set the status=BETA (or UNSTABLE or EXPERIMENTAL )

Got it. Set it to STABLE because at some point it stopped loading because it failed the check (probably something I have to set in my settings).

Which version of Gramps are you using? The gpr file says it is only for 6.0

1 Like

The error message in the Gramps 6.0 GUI is not enlightening.

But the Console error message running the addon tool is better

2025-02-20 18:20:26.148: WARNING: _manager.py: line 326: Plugin error (from 'familysearchcheck'): No module named 'genealogy_familysearch'

There is no genealogy_familysearch or genealogy_gramps from which to import.

UNSTABLE status Plugins are supposed to only be available when you are running Gramps with the Debug/Developer mode switch

For sometime since 5.0 , that restriction became broken and the developer Gramps showed everything. I think that there was a fix in 6.0 and that would be a reason the addon would stop showing up.

But the registration doesn’t give a lot of feedback on a failure either.

(BTW, I am not a developer.)

Yes, I am running from a checkout of master.

Yes, I did not post those up. Here is my run log (partial):

2025-02-20 20:19:46.584: DEBUG: _manager.py: line 301: Importing familysearchcheck
2025-02-20 20:19:46.882: DEBUG: familysearchcheck.py: line 62: FamilySearchCheck init
2025-02-20 20:19:46.882: DEBUG: familysearchcheck.py: line 359: FamilySearchCheckOptions init
2025-02-20 20:19:46.882: DEBUG: familysearchcheck.py: line 367: Adding options
2025-02-20 20:19:46.883: DEBUG: familysearchcheck.py: line 405: Adding options done

/home/lsc/.gramps/gramps60/plugins/familysearchcheck.py(363)init()
→ LOG.debug(“FamilySearchCheckOptions init done”)
(Pdb) c
2025-02-20 20:19:52.401: DEBUG: familysearchcheck.py: line 363: FamilySearchCheckOptions init done
2025-02-20 20:19:52.402: DEBUG: familysearchcheck.py: line 81: Database: <sqlite.SQLite object at 0x75866fde0580>
2025-02-20 20:19:52.402: DEBUG: familysearchcheck.py: line 89: FamilySearchCheck init done

It goes through all the init code, but the run method never executes, and I don’t get any sor of options dialog.

The missing libraries are only exercised in the run() method (which I have tried as run() and run_tool()).
You may also note that I added some logging to the gramps libs to see what was going on.

Afraid that we’ve reached the limits of my awareness of the obvious stuff.

So this seems to be the relevant code that instantiates the tool object, notice that it does not execute the run() method:

def gui_tool(
    dbstate, user, tool_class, options_class, translated_name, name, category, callback
):
    """
    tool - task starts the report. The plugin system requires that the
    task be in the format of task that takes a database and a person as
    its arguments.
    """

    try:
        tool_class(
            dbstate=dbstate,
            user=user,
            options_class=options_class,
            name=name,
            callback=callback,
        )
    except WindowActiveError:
        pass
    except:
        log.error("Failed to start tool.", exc_info=True)

Called from here:

    def run_plugin(self, pdata):
        """
        run a plugin based on it's PluginData:
          1/ load plugin.
          2/ the report is run
        """
        mod = self._pmgr.load_plugin(pdata)
        if not mod:
            # import of plugin failed
            return

        if pdata.ptype == REPORT:
            report(
                self.state,
                self.uistate,
                self.uistate.get_active("Person"),
                getattr(mod, pdata.reportclass),
                getattr(mod, pdata.optionclass),
                pdata.name,
                pdata.id,
                pdata.category,
                pdata.require_active,
            )
        else:
            from ..user import User

            tool.gui_tool(
                dbstate=self.state,
                user=User(uistate=self.uistate),
                tool_class=getattr(mod, pdata.toolclass),
                options_class=getattr(mod, pdata.optionclass),
                translated_name=pdata.name,
                name=pdata.id,
                category=pdata.category,
                callback=self.state.db.request_rebuild,
            )

and again, no call to the run() method. So where, and under what conditions, is the run() method called?

In my experience you typically really have to call run() explicitly in __init__. And you must write the run method yourself. Gramps just instantiates the Tool object and it must take care of everything else (like setting up the GUI, for example). This is a bit different than in e.g. gramplets. Of course, there also does not need to be a separate method - you can do everything in __init__.

2 Likes

If your tool inherits from the ToolManagedWindow or ToolManagedWindowBatch class, then the run() method is called for you.

2 Likes

When developing different types of Tools for Gramps, should the approach to triggering the run() method vary based on the Tool’s behavior? Specifically, consider these three observed behavioral types of Tools:

  1. Immediate execution Tools: These run as soon as they’re selected from the menu, typically without an ellipsis ( U+2026 Horizontal Ellipsis Unicode Character) in their menu item name. These might have a “Undoable” warning dialog with a cancel option (although the cancel option means the menu could have the ), progress dialog and/or ‘after action’ report.
  2. Configurable Tools: These have options that need to be set before execution, after which the tool’s actions run once and are done.
  3. Interactive Tools: These allow for ongoing user interaction and iterative actions.

How would the implementation and triggering of the run() method differ for each of these Tool types to ensure proper functionality and user experience within the Gramps environment?