Sync addons-source@maintenance/gramps61 with upstream (2026-07-10)#61
Open
eduralph wants to merge 57 commits into
Open
Sync addons-source@maintenance/gramps61 with upstream (2026-07-10)#61eduralph wants to merge 57 commits into
eduralph wants to merge 57 commits into
Conversation
Ships 10 worked example .gram.py scripts in a shared scripts/ folder that also serves as the default Open/Save location, so a user's own scripts naturally collect next to them. Descriptions live in a translatable script_descriptions.py module (picked up by the addon's existing xgettext pipeline) and are shown as a preview when browsing the Open dialog, with a fallback to a script's own leading comment for uncatalogued files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Places it directly below the script editor instead of below the Table/Output/Chart tabs, so it's closer to where the script is written. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a persistent filename label on the left of the status bar (split from the transient status message via an HBox), updated whenever a script is loaded or saved, so it's always clear which script is loaded. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The filename label was using the same bordered style as the status message, making the two indistinguishable. Give it its own bold, borderless style and more room from the status text next to it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Uses the buffer's built-in modified flag plus Gramps' standard SaveDialog (Save / Don't Save / Cancel) so New and Open no longer silently discard in-progress edits. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Moves get_columns()/extract_header_comment() into a new script_utils.py (no GTK/Gramps imports) so both GrampyScript.py and this new dev tool can share them, and so the two tests exercising them no longer need the ast-extraction workaround. update_script_descriptions.py scans scripts/*.gram.py and keeps SCRIPT_DESCRIPTIONS in script_descriptions.py in sync: adds a stub entry for new scripts, drops entries for deleted ones, and warns (without overwriting) when a file's title comment has drifted from the catalogued title. Existing entries' exact source text is preserved via ast slicing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Scripts can now import plain .py helper modules placed next to them (scripts dir and the open script's own dir are added to sys.path before exec), reuse an existing Gramps sidebar custom filter via custom_filter(), and remove an object via delete() instead of needing to know the per-class remove_* db call. Also fixes active_event to return a DataDict2 like every other active_* constant, instead of a raw handle. Adds three example scripts (and a script_helpers.py helper module) to scripts/, catalogued in script_descriptions.py and scripts/README.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wires a Tab-triggered, live-filtering completion popover into the script editor, covering plain Python names, the DSL's own functions (people(), custom_filter(), selected()/filtered(), ...), and nested attribute chains on Gramps records (person.primary_name.first_name), including through a user's own loop variables and list subscripts. completion.py wraps jedi.Interpreter for the actual lookups. stub_generator.py derives static type stubs straight from Gramps' own get_schema() so jedi can infer generator/loop-variable row types without ever executing anything (a live template object would require calling DataDict2's computed properties, e.g. father/birth, which only degrade to empty results for blank data anyway). namespace_builder.py supplies the remaining live objects (today, counter, database) that are safe to introspect directly. completion_popup.py is a standalone Gtk.Popover controller, kept independent of the Gramplet class so it's testable against a plain Gtk.TextView. DataDict2 gained a __dir__ override so introspection (jedi's runtime fallback) sees dynamic dict keys like primary_name, not just its declared properties. Also fixes the editor ScrolledWindow/TextView having no wrap mode or explicit scroll policy, which let long lines widen the whole gramplet instead of scrolling within it. Requires jedi (added to GrampyScript.gpr.py's requires_mod), which ships with Gramps 6.1. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tainer statusmsg's text sometimes embeds the current file's full path (e.g. "Loaded '/home/.../scripts/some_script.gram.py'"), and an unbounded Gtk.Label requests enough natural width to fit that whole string, which was pushing the gramplet wider than its panel and forcing horizontal scrolling. Capping it with set_ellipsize()/ set_max_width_chars() bounds the natural width regardless of message content. Also reverts the wrap-mode/scroll-policy change from the previous commit, which guessed the code editor's TextView was the cause; it wasn't, and auto-wrapping code isn't desirable anyway since it breaks visual alignment of indentation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Opens the addon's wiki help page (Addon:GrampyScript), reusing the same help_url the gramplet already exposes via self.gui.
Each scripts/*.gram.py file now carries its own description as a module docstring; script_descriptions.py is fully regenerated from those docstrings (plus each file's title comment) via update_script_descriptions.py, instead of hand-maintaining translated text disconnected from the examples it describes. Also fix the editor's syntax highlighter, which had no notion of string literals and was bolding keywords found inside quoted strings (most visibly inside the new docstrings). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Completions for callables (people(), families(), custom_filter(), dict methods, etc.) now insert with parens, landing the cursor between them when the function takes arguments. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Why show a dropdown with nothing to choose between? trigger() now inserts directly when there's exactly one candidate, falling back to the popover only when there's a real choice to make. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
age was typed as "object" even though DataDict2.age actually returns a gramps.gen.lib.date.Span (from Date - Date), so jedi had nothing to complete on it. back_references/back_references_recursively had the same "object" placeholder, which is worse than useless since jedi can't iterate a bare object at all -- completions on their loop items returned nothing. Both are now typed precisely: age as Span (imported into the stub preamble), and the back-reference properties as a union of every row type, mirroring the existing selected()/filtered() trick. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
COMPUTED_PROPERTIES was a single flat dict layered onto every schema class, including nested structural types (Name, Attribute, ...), so the editor offered fields like father/spouse/gender/age everywhere -- even where the underlying DataDict2 property would raise (gender on a non-Person) or silently do nothing. It's now name -> (type, valid root types), and build_registry() only attaches a property to the root record types it's actually valid on. `reference` moves out entirely, onto the nested *Ref wrapper types it actually belongs to. Also fixes two real datadict2.py bugs the audit turned up: surname/name used unguarded self["surname"]/self["name"], raising KeyError on any class without that field; reference always called get_raw_person_data regardless of which *Ref type it was wrapping. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
get_completions() returned bare function names (e.g. "people", "as_age") while get_completion_items() appended "()" to the same completions -- inconsistent, and a bare name reads as a field rather than a callable. Both now share a _display_name() helper so callables agree everywhere. Also exclude jedi type "class" completions altogether: the stub preamble injects scaffold classes (Person, Family, ...) purely for static analysis, and they aren't bound to anything in the namespace a script actually executes in, so offering them as completions would suggest names that raise NameError if accepted. Builtin classes (list, dict, ...) are dropped too, since the DSL has no use for instantiating classes directly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lename The status message duplicated the filename already shown by filename_label, so drop those redundant messages and use the freed-up space to surface the Tab-completion shortcut. Also fix New leaving the old filename label in place, which caused Save to silently overwrite the previous file instead of prompting Save As. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
columns, begin_changes, end_changes, delete, row, and chart are all top-level DSL callables bound as local closures inside execute_code(), so jedi never saw them since they weren't part of the completion stub or namespace. Adds a VOID_FUNCTIONS entry in stub_generator.py that renders their signatures as "-> None" completions.
open(path).read() without a context manager leaves the file handle open until garbage collected, which triggers ResourceWarning under python -m unittest. Use with-blocks in the four spots that did this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A nested wrapper's _object was rebuilt via data_to_object() from just its own dict slice, disconnected from the real object tree. Calling a set_*() method (e.g. surname.set_origintype()) mutated that throwaway clone, then the commit step re-serialized the untouched real object, so the change never reached the database. Now nested wrappers resolve _object by walking the root's real object via self.path, so set_*() calls (and attribute assignment) mutate the actual object that gets persisted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
[dd.primary_name] + dd.alternate_names goes through DataList2.__radd__, producing a DataList2 whose elements are already DataDict2/DataList2 instances. __getitem__ unconditionally re-wrapped dict/list values, and since those wrapper classes subclass dict/list, it re-wrapped already- wrapped items too -- discarding their real root/path and substituting this list's own (often None, defaulting to self) root. That produced a DataDict2 whose root was itself but whose path was non-empty, an inconsistent state that made attribute assignment recurse forever trying to resolve self.root._object. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… the label The raw serialized "string" field of a GrampsType value (NameOriginType, NameType, EventType, ...) is only the *custom*-type override text -- it is always "" for predefined values like PATRILINEAL. Since DataDict2's generic dict-key lookup returned that raw field directly, `.string` looked empty even after setting a real origin type. Add a `string` property that, when the wrapped value is a GrampsType, returns the actual computed label (str(the_type)) instead. Falls back to normal attribute lookup for anything without a "string" field, so unrelated objects are unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`names` was `[self.primary_name] + [self.alternate_names]` -- the extra brackets around alternate_names nested the whole list as a single element instead of spreading its items in. Separately, DataList2.__radd__ returned `self + value` instead of the mathematically required `value + self` (Python calls b.__radd__(a) to compute `a + b`), so `plain_list + data_list2` -- the exact pattern used to loop over primary + alternate names -- came out reversed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`dl.set_privacy(True)` fanned out attribute access first, collecting each item's unevaluated set_*() wrapper closure into a DataList2 -- then failed to call, since a DataList2 of closures isn't callable itself. Special-case set_*() the same way DataDict2 already does: return one callable that applies the same args to every item in the list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
begin_changes()/end_changes() called the lowlevel db._txn_begin()/_txn_commit() (raw SQL BEGIN/COMMIT) instead of db.transaction_begin()/transaction_commit(), so the DbTxn was never pushed onto undodb and script edits were invisible to Undo/Redo despite being written to disk. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PR gramps-project#978 added custom_filter(name, namespace="Person") and delete(obj) to GrampyScript's execution scope. GrampsAssistant drives that same scope via tools.py's execute_script/evaluate_expression docstrings, so the model needs to know these exist to use or suggest them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tions; added help url gramps-project#979
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
5 times, most recently
from
July 18, 2026 05:55
057f535 to
3a322f6
Compare
GrampsPreferences.__init__ gained an initial_panel keyword so callers
can open Preferences directly on a specific panel. themes_load.py
monkey-patches GrampsPreferences.__init__ with MyPrefs.__init__, which
didn't accept the new keyword, raising:
TypeError: MyPrefs.__init__() got an unexpected keyword argument
'initial_panel'
MyPrefs.__init__ now accepts initial_panel and forwards it to
select_panel(), matching the core implementation. Adds a regression
test.
PR gramps-project#950 added a repo-root tests/__init__.py that pins GTK/Gdk to 3.0 for the whole suite, matching the empty tests/__init__.py convention already used by every other addon. The per-addon pin here was redundant with that infrastructure. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
<size> is never used to compute layout in Form (editform.py/entrygrid.py size columns from their text content, not the XML value), and PDFForms consumes it purely as a relative weight that works with any positive total. The sum-to-100 warning added for bug 11010 was therefore firing on 78 shipped forms without there being any actual functional issue to fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
from
July 19, 2026 06:21
3a322f6 to
c3d08d5
Compare
WordleGramplet generated word clouds via wordle.net, which no longer exists. The gramplet was already unstable and excluded from the listing, so there is nothing left for it to work against.
The prepare() method built the matching event handle set in self.selected_handles but then tried to update self.events, an attribute that is never defined. This raised an AttributeError whenever the "Events of families matching a <family filter>" rule was applied, breaking the filter entirely. Reported at: https://gramps.discourse.group/t/crash-of-an-event-filter-using-a-functional-family-filter/9733
Covers the AttributeError fixed in the previous commit: prepare() crashed because it updated the never-defined self.events instead of self.selected_handles. Tests build a small in-memory database with two families/events, register a custom Family filter matching one of them, and assert prepare()/apply_to_one()/GenericFilter.apply() all behave correctly without raising.
PR gramps-project#950 pins GTK/GDK to 3.0 repo-wide via tests/__init__.py, so the per-file gi.require_version() calls here were redundant. Keep only the ImportError guard for hosts without PyGObject at all.
Also update 2 filters that were incorrectly targeted for 6.0 branch instead of 6.1 branch
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
5 times, most recently
from
July 24, 2026 06:17
c50bf67 to
e4c6ed3
Compare
Seventeen manual pages for addon authors - overview and getting started, tutorials per addon kind, the addon-kinds catalogue, registration fundamentals, data access, API reference, testing, debugging, troubleshooting, code analysis, internationalization, packaging, post-merge community steps, compatibility, per-release changes, normative guidelines, and roadmap - plus the diagrams they embed and a folder index. The new docs/ tree is invisible to make.py and CI: every enumeration keys on *.gpr.py or *.py globs that match nothing under docs/, verified with manifest-check and a no-op 'build docs' run.
README: the develop-your-own-addon pointer now leads to the in-repo docs/addon-development manual first, with CONTRIBUTING.md for the contributor workflow and the wiki page as an alternative rendering. The dead Travis badge is dropped. CONTRIBUTING: the deep technical sections that the manual now covers - addon kinds, registration and GENERAL plugins, prerequisites, addon configuration, localization, distribution contents, report categories, and the wiki listing/documentation templates - are reduced to a short retained summary plus a link into the manual, each under its original heading so existing deep links keep resolving. The contributor-workflow content that is unique to this document - repository and fork setup, development branches, the addon checklist, the pull-request walkthrough, and the maintenance guidance - is kept in place unchanged. Also repairs pre-existing broken links: seven table-of-contents and overview anchors that never matched their headings, two (#https://...) hrefs, the garbled Addon-list-legend link, and the Localization snippet that had lost its underscore binding. Depends on the PR that adds docs/addon-development.
The addons-source CI (PR 820) adds a lint step that fails on any tracked Python file carrying trailing whitespace. Three pre-existing files trip it (27 lines total): ArchiveAssist/ArchiveAssist.py (22), and two FilterRules modules (5). Strip the trailing whitespace so the gate can pass; whitespace only, no behavioural change (git diff -w is empty).
_build_with_progress used `for _ in range(batch_size)`, which makes _
a local variable throughout the whole method (Python binds it at compile
time). That shadows the module-level `_ = _trans.gettext`, so:
- the except handler at the top of the method, LOG.error(_("Error
initializing data: %s") % str(e)), runs before the loop assigns _
and raises UnboundLocalError; and
- the in-loop handler LOG.warning(_("Error processing person %s: %s"
% ...)) calls _ after the loop bound it to an int, raising TypeError.
Both fire only on the error paths, so they slipped through. The loop
counter is unused; rename it to _batch_step so _ resolves to the module
gettext everywhere. ruff (E9,F63,F7,F82) is clean on the module.
This also clears the F82x lint error PR 820's ruff gate reports on the
current tree.
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
from
July 25, 2026 06:08
e4c6ed3 to
4ebd81b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated nightly sync from
gramps-project/addons-source@maintenance/gramps61. Generated by .github/workflows/upstream-sync.yml on the testbed.