Sync addons-source@maintenance/gramps61 with upstream (2026-07-10) - #61
Open
eduralph wants to merge 155 commits into
Open
Sync addons-source@maintenance/gramps61 with upstream (2026-07-10)#61eduralph wants to merge 155 commits into
eduralph wants to merge 155 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
from
August 7, 2026 05:26
0fc7d61 to
f88aa53
Compare
…ckend Lets Gramps open a gramps-web-api server (e.g. gramps-connect, Gramps Web) as a regular family tree -- read and write, no export/import step. Subclasses the stock SQLite DBAPI backend rather than reimplementing DbReadBase/DbWriteBase, and keeps a local mirror in sync incrementally via the server's transaction-history endpoint; local edits push back through transaction_commit(). Credentials come from a single GRAMPS_WEB_API_KEY env var (a non-expiring refresh token) rather than a login dialog, which also makes the same webapi_client.py usable as a bare SDK outside Gramps. Status UNSTABLE: no conflict handling (writes are last-write-wins by design, not yet), no undo/redo integration, no media sync. Verified end-to-end against a live gramps-web-api server, including live use from Gramps desktop itself, but no automated test suite yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…radeoff Explains that GRAMPS_WEB_API_KEY carries a standard, non-expiring refresh token from the server's normal login flow rather than a scoped/revocable personal access token, so a leaked key is as damaging as a leaked password until it's changed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Cover webapi_client.WebApiHandler (token codec, JWT decoding, auth flows, 429/401 retry and API-prefix fallback, transaction-history/push request shape) and grampswebapidb.WebApiDB (transaction_to_json, _apply_change, _sync_from_server pagination, transaction_commit ordering and error handling). No real server or SQLite file is needed; urlopen and the DBAPI/SQLite base are stubbed throughout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drop force=1 from POST /transactions/ so the server's old-data-mismatch check actually runs. A rejected push now raises WebApiPushConflict (webapi_client.py), which transaction_commit() catches separately from generic connection errors: it logs a distinct warning and resyncs from the server so the local mirror stops showing an edit the server never accepted, rather than drifting silently. webapi_client.py's docstring also now notes it's a hand-synced vendored copy of the standalone gramps-web-api-client package. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…y CLI Documents the standalone package's CLI as the primary way to mint a GRAMPS_WEB_API_KEY, with the addon's own vendored WebApiHandler.mint_api_key() as the equivalent no-extra-dependency fallback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…-api-client rename The standalone client package was renamed (gramps_web_api_client -> gramps_api_client, new checkout at ~/gramps/gramps-api-client). Updated webapi_client.py's docstring and README.md accordingly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ncing it Gramps core's DbGenericUndo._undo()/_redo() revert the local mirror via low-level _txn_begin()/undo_data()/_txn_commit() calls that never go through transaction_commit(), so a local Undo/Redo previously left the server unchanged with no error at all -- worse than a push conflict, since nothing was even logged. WebApiDB now overrides undo()/redo(): both grab the relevant DbTxn off DbGenericUndo's queue before delegating to super(), rebuild its payload with the existing transaction_to_json(), and push it. Undo sends it to POST /transactions/?undo=1, where gramps-web-api reverses it itself (reverse_transaction()); redo just re-pushes the original forward payload, same as an ordinary commit. Both share the same conflict-detection/resync path as transaction_commit(), factored out into _push_payload(). Verified end-to-end against a live server: add a person, undo (a fresh mirror sync confirms the server no longer has it), redo (confirms it's back). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- grampswebapidb.py: use the documented try/except get_addon_translator fallback (glocale.translation directly if the addon has no locale/ translations yet) instead of a bare glocale.translation.gettext. - grampswebapidb.gpr.py: add help_url pointing at the addon's wiki page. - MANIFEST: include README.md in the built .addon.tgz -- it documents the GRAMPS_WEB_API_KEY security tradeoff, not just dev notes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ommit gap A batch=True commit (any bulk import, merge, or tool run through gramps-web-api) leaves an empty-changes marker in the transaction history instead of per-object entries, since DBAPI's commit/remove methods skip the undo-log call for batch transactions. _sync_from_server() had no way to detect this and silently missed everything the batch commit did -- confirmed live: importing example.gramps's 2157 people into a synced tree left the local mirror stuck at its pre-import count indefinitely, no matter how often it resynced. _sync_from_server() now treats an empty-changes transaction as a signal, not a no-op, and falls back to a new _full_resync(): download the server's current Gramps XML export and reimport it (via the same stock ImportXml the batch commit itself used) into a freshly wiped local mirror.
…live load() now schedules a periodic re-sync (GLib.timeout_add_seconds), matching gramps-connect's own browser-client poll against the same endpoint, so edits made from another client show up here without closing and reopening the tree; close() cancels the pending timeout. Since _sync_from_server()'s replay runs in a batch=True DbTxn, DBAPI's own add/update/delete signals never fire for it -- _emit_change_signals() reproduces them by hand so already-open views refresh the same way they would for a local edit, and _full_resync() now calls request_rebuild() for the same reason on its wipe-and-reimport path.
Previously a rejected push (someone else changed the object server-side since the local mirror last synced) just resynced from the server and dropped the local edit. Now it retries the edit on top of the resynced data, merging list-valued fields (notes, citations, events, ...) via the object's own merge() -- the same logic behind Gramps' Merge People/ Family/... tools, ported from GrampsWebSync's diffhandler.py -- instead of one side blindly overwriting the other. A second conflict on the retry itself is logged and dropped rather than retried again.
…report sync progress Nothing tied a local mirror to a particular GRAMPS_WEB_API_KEY account: switching the env var to a different server/tree while reopening the same Family Tree left the mirror silently mixing stale data from the old account with whatever the incremental sync happened to pull from the new one. load() now calls _check_identity(), which requires the Family Tree's own name to be "<username>@<host>" for whoever the key currently authenticates as (normalized through the same character substitution Gramps' own Family Tree Manager applies when renaming a tree, since a hostname's dots can never survive that GUI round-trip intact) -- a mismatch fails DbConnectionError instead of loading. load()'s callback parameter (the same hook Gramps wires to its real progress bar) is now threaded through to _sync_from_server(), which reports page-by-page percent-complete using the server's X-Total-Count, and to _full_resync(), which gets coarse 0/100 markers since ImportXml has no internal step reporting to forward finer progress from. A slow initial catch-up now shows real progress instead of Gramps looking hung. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Core sync/write-through/undo-redo/conflict-detection paths are verified against a live server and covered by tests; remaining gaps (media file sync, scalar-field conflict resolution) are documented limitations rather than missing basics. template.pot was stale, missing strings added by the last commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
2 times, most recently
from
August 9, 2026 04:53
93d4dc3 to
65de30a
Compare
…e its Family Tree Minting a key previously required a shell with gramps-api-client installed, or hand-writing the three lines of Python from the addon's README. Tools -> Utilities -> Generate Gramps Web API key now turns a server URL/username/password into a key from a form, sets it in the running Gramps process's environment so a WebApiDB-backed tree can be opened without restarting, and offers a follow-up button to create a correctly-named, correctly-backed Family Tree for that key via the same CLIDbManager API Gramps' own Family Tree Manager uses. Also tightens grampswebapidb.py's own error handling: load()'s initial sync is now wrapped the same way _check_identity() already was, so a failure there raises the addon's DbConnectionError instead of escaping as a raw HTTPError that Gramps' generic dbloader dialog shows unhelpfully; and HTTP 403s specifically get a message calling out that it's a permissions problem with the authenticated account, not a URL/credentials one.
Gramps only populates the Tools menu once some Family Tree is open, so the "Generate Gramps Web API key" tool still needs one open (any backend) to be reachable, even though it doesn't touch that tree's data. README.md and the tool's own module docstring said otherwise. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… create its Family Tree#1012 Also update template.pot
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
3 times, most recently
from
August 12, 2026 05:22
d4cbcfe to
b35f1c7
Compare
The exporter writes one JSON object per line (JSON Lines format), not a single JSON document, so the file extension should be .jsonl rather than .json.
eduralph
force-pushed
the
sync/upstream-maintenance-gramps61-auto
branch
from
August 13, 2026 05:26
b35f1c7 to
df2d04b
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.