Sync addons-source@maintenance/gramps61 with upstream (2026-07-10)#61
Sync addons-source@maintenance/gramps61 with upstream (2026-07-10)#61eduralph wants to merge 37 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
…rt, update description - Add sort mode dropdown (proximity/month-day) matching BirthdaysGramplet - Fix cross-calendar sort by using gregorian() before constructing death_this_year - Move death/birth ref checks into __calculate() for cleaner main() - Update description string in .gpr.py and all .po files - Bump version to 1.1.0
Update all 22 .po files with adapted translations for the new description 'a gramplet that displays death dates in sorted order' (removed 'month and day' reference).
lxmlGramplet raised ErrorDialog(...).run() at module *import* time when gzip or lxml was unavailable. That is a blocking modal shown before any GUI action: it stalls plugin loading until dismissed, and fires (or aborts) when Gramps is imported without a display — e.g. under the CLI or a test harness, where a missing python3-lxml made the module hang or scatter dialogs. The gramplet already degrades gracefully — it falls back to xml.etree when lxml is absent — so the missing dependency is not fatal. Set the availability flags silently at import (log instead of a dialog), and show the notice in init(), when the gramplet is actually opened and the GUI is running. No .gpr.py change: the addon version is incremented automatically at publish time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 50c76012b7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| GrampyScript/scripts/*.gram.py | ||
| GrampyScript/scripts/README.md |
There was a problem hiding this comment.
Package the helper module used by the sample script
When this addon is packaged, make.py only includes root-level GrampyScript/*.py plus the patterns listed here, so GrampyScript/scripts/script_helpers.py is omitted even though 11_import_example.gram.py imports it. Installed users who run that bundled example will hit ModuleNotFoundError: script_helpers; add the helper file, or a GrampyScript/scripts/*.py pattern, to the manifest.
Useful? React with 👍 / 👎.
7a94ebe to
78beec7
Compare
78beec7 to
b796298
Compare
Automated nightly sync from
gramps-project/addons-source@maintenance/gramps61. Generated by .github/workflows/upstream-sync.yml on the testbed.