Skip to content

Sync addons-source@maintenance/gramps61 with upstream (2026-07-10)#61

Open
eduralph wants to merge 37 commits into
maintenance/gramps61from
sync/upstream-maintenance-gramps61-auto
Open

Sync addons-source@maintenance/gramps61 with upstream (2026-07-10)#61
eduralph wants to merge 37 commits into
maintenance/gramps61from
sync/upstream-maintenance-gramps61-auto

Conversation

@eduralph

Copy link
Copy Markdown
Owner

Automated nightly sync from gramps-project/addons-source@maintenance/gramps61. Generated by .github/workflows/upstream-sync.yml on the testbed.

dsblank and others added 30 commits July 9, 2026 08:32
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>
javadr and others added 6 commits July 9, 2026 13:57
…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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread GrampyScript/MANIFEST
Comment on lines +1 to +2
GrampyScript/scripts/*.gram.py
GrampyScript/scripts/README.md

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@eduralph eduralph force-pushed the sync/upstream-maintenance-gramps61-auto branch 2 times, most recently from 7a94ebe to 78beec7 Compare July 12, 2026 06:25
@eduralph eduralph force-pushed the sync/upstream-maintenance-gramps61-auto branch from 78beec7 to b796298 Compare July 13, 2026 06:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants