Repository navigation
Run sync wrappers without nest_asyncio so pyegeria works on Python 3.14 (ISSUE-168) - #513
Merged
Merged
Conversation
…14 (ISSUE-168) pyegeria applied nest_asyncio at import time so that the sync wrappers' get_event_loop().run_until_complete(...) worked inside a running loop (Jupyter, Textual). On Python 3.14 nest_asyncio makes asyncio.current_task() return None, which broke Textual's RLock and aiohttp's timeout (162 micro-test failures), and get_event_loop() no longer creates a loop. - pyegeria/core/async_runner.run_sync: this thread's persistent loop when no loop is running; a shared background loop thread when one is; a one-off thread when already on that background loop. - All 1310 sync-wrapper call sites in 41 files use run_sync (mechanical); both make_request sync paths too. - The per-thread httpx session is replaced when the thread runs a different event loop; a session assigned via the setter is kept. - nest_asyncio removed from pyegeria, commands, my_egeria and the dependencies; requires-python <3.15; CI tests 3.12/3.13/3.14; release stays on 3.13 with a corrected comment. - Tests: test_async_runner.py. Micro-tests pass on 3.13 and 3.14 (1669, 0 failed); live read-only smoke on both. Claude-Session: https://claude.ai/code/session_015pdMuQanRknaXmcpx4LmLb Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
dwolfson
force-pushed
the
fix/issue-168-py314-sync-runner
branch
from
October 30, 2026 12:38
3d77bdf to
bc41cea
Compare
Docs: docs/user_programming.md gets a "Sync and async" section with when-to-use guidance and examples (script, Jupyter, async app, asyncio.gather, worker threads), all run against a live quickstart on Python 3.13 and 3.14. The Dr.Egeria manual, CONTRIBUTING, AGENTS, CLAUDE.md and the package READMEs now describe run_sync instead of get_event_loop().run_until_complete and nest_asyncio. Fixes found while writing them: - 15 ActorManager sync wrappers (contribution records, contact details, perspectives, skills) used asyncio.run(), which fails in a running loop now that nest_asyncio is gone; they use run_sync. - md_processing.dr_egeria.process_md_file used asyncio.run(); it uses run_sync, so it works in a Jupyter cell. - BaseServerClient.__exit__ called session.aclose() without awaiting it (also on main); it uses run_sync. - EgeriaTech gains _async_close_session() so async code can close the sessions on its own loop. - A test fails if asyncio.run/get_event_loop/new_event_loop appears in pyegeria/ or md_processing/ outside a __main__ block. Claude-Session: https://claude.ai/code/session_015pdMuQanRknaXmcpx4LmLb Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
dwolfson
marked this pull request as ready for review
October 30, 2026 14:07
A sync call made from a coroutine already running on the background loop runs on a one-off thread, which starts with an empty context, so a surrounding lineage_visible() block was silently dropped there and the request went without forLineage=true. The thread now runs inside a copy of the caller's context. The other two paths already kept it (run_until_complete and call_soon_threadsafe copy the context); tests now cover all three. Claude-Session: https://claude.ai/code/session_015pdMuQanRknaXmcpx4LmLb Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
dwolfson
added a commit
that referenced
this pull request
Oct 31, 2026
ISSUE-168 was released in 6.2.16 (#513) but still read "not merged"; ISSUE-169..172 are merged (#516..#519) and ship in 6.2.17. Their statuses now say so, the five entries move to Fixed / Resolved, and the open-items index and at-a-glance count (now 2: ISSUE-155, 150) match. No entry's content changed beyond its status line. Claude-Session: https://claude.ai/code/session_015pdMuPkqttD1yKAKJLS9Fc Signed-off-by: Dan Wolfson <dan.wolfson@pdr-associates.com>
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.
Release: ship this as its own pyegeria version, with a release note that code relying on pyegeria applying
nest_asynciomust change. The downstream fixes (see Downstream below) land with each repo's floor bump to that version.Why
requires-pythonwas capped at<3.14, and the release workflow blamed Textual. The real cause: pyegeria appliesnest_asyncioon import (pyegeria/view/mermaid_utilities.py), and on Python 3.14nest_asyncio1.6.0 (no release since 2024) makesasyncio.current_task()return None. That breaks Textual's RLock and aiohttp's timeouts: 162 of 1661 micro-tests fail on 3.14, all in the Textual apps andserve_my_egeria.nest_asynciowas there so the sync wrappers'get_event_loop().run_until_complete(...)worked inside a running loop.What
pyegeria/core/async_runner.run_sync(coro):run_sync(a mechanical rewrite). So do bothmake_requestsync paths, which deadlocked inside a running loop withoutnest_asyncio.ActorManagerwrappers that usedasyncio.run()(contribution records, contact details, perspectives, skills). They would have failed in Jupyter oncenest_asynciowas gone; they now userun_sync. So does the libraryprocess_md_file, which now works in a Jupyter cell. A new test fails ifasyncio.run/get_event_loop/new_event_loopappears inpyegeria/ormd_processing/outside a__main__block.run_sync's nested path (a sync call from a coroutine already on the background loop) ran on a new thread with an empty context, so a surroundinglineage_visible()was silently dropped and the request went withoutforLineage=true. That thread now runs in a copy of the caller's context. The other two paths already kept it, and tests now cover all three.BaseServerClient.__exit__now awaitssession.aclose(); it never did, on main either.EgeriaTechgains_async_close_session()so async code can close sessions on its own loop.nest_asyncioremoved from pyegeria,commands/,my_egeriaand the dependencies.requires-pythonis now<3.15. CI tests 3.12/3.13/3.14; releases stay on 3.13.Docs
docs/user_programming.mdhas a new Sync and async section, replacing the old "Async API" section and the outdated advice that the sync token call fails in coroutines. It covers:asyncio.gather, worker threads;All the examples were run against a live quickstart on 3.13 and 3.14. Measured there:
gatherover three queries took about 4 s, against about 7.5 s one after another;The old doc example
_async_find_in_asset_domain("*")is a 400 on the server (a null search string), so the new one uses a real search string. The Dr.Egeria manual, CONTRIBUTING (with a wrapper example), AGENTS, CLAUDE.md and the package READMEs now describerun_syncinstead ofrun_until_completeandnest_asyncio.Tested
Micro-tests: 3.13 and 3.14, 1672 tests, 0 failed (1 skipped), including 11 new runner, session, context and guard tests.
Live, read-only, 3.13 and 3.14:
await,make_request, and notebooks that applynest_asynciothemselves. On released 6.2.15,make_requestinside Jupyter hangs; here it returns 200;serve_my_egeria: a websocket session;list_reportsandrun_reporttwice;asyncio.run;process_md_file(validate) inside a 3.14 Jupyter kernel.Downstream fixes, live, read-only (egeria-workspaces-fs-44, 2026-10-30, Python 3.13 venv on dae2ffe, portal untouched): each break was reproduced with the old code, and each fix passed with the new:
make_requestreturns 6 DataValueSpecs, twice.dr_egeria_run_block: the old code fails quietly with "asyncio.run() cannot be called from a running event loop"; awaiting the async function validates, and no write was made.make_requestworks. Sync, thenawait, then sync on one client also works.No unclosed-client, ResourceWarning or event-loop warnings appeared.
trellis Resource Explorer suite against dae2ffe (coordinator, 2026-10-30; SQLite, -n 4, no Egeria calls): 4 failed of 13,628, against 1 known flake on the 6.2.15 baseline. None of them needs a pyegeria change:
test_catalogue_issue117_blocktimesasyncio.run()around ato_threadread. The realasyncio.runwaits for the default executor on exit; nest_asyncio's did not. Only the test changes.egeria_publisher._resolve_template_guid(in the Downstream list). After any earlierasyncio.runon the same thread,get_event_loop()raises "There is no current event loop", and the publisher catches that as "Could not catalog sub-resource". Reproduced deterministically by adding oneasyncio.runbefore those tests. The same quiet failure would happen in production on such a thread.Behaviour change
Code that relied on pyegeria applying
nest_asynciofor it changes:asyncio.get_event_loop().run_until_complete(...)in a worker thread now raises "There is no current event loop".asyncio.run(...)inside a running loop now fails.make_request) orawaitinstead.Downstream (to change together with the floor bump to the release carrying this)
egeria-workspaces (fs-44's static check of upstream/main 8c4efacd, 2026-10-30). Three sites break and must ship with the floor bump:
PyegeriaWebHandler/data_design_handler.py::_search_data_value_specs(quickstart and freshstart) breaks the Data Grains/Classes lists. Change it tomgr.make_request(...).mcp_server.py::dr_egeria_run_block(quickstart and freshstart). The async MCP tool calls the syncdr_egeria_md.process_markdown_file[_structured], which usesasyncio.run(dr_egeria_md.py:192/204). Without nest_asyncio that fails inside the running loop, and the wrapper swallows the error, so the Dr.Egeria MCP tool fails quietly. Fix:awaittheprocess_md_file*_asyncfunctions directly.coco-workbooks/1. coco-data-hub/mapping-the-systems/supply-chain-lineage.ipynb, cell 7, runsasyncio.get_event_loop().run_until_complete(egeria_client._async_make_request(...))in the kernel and relied on pyegeria's nest_asyncio. It breaks on the 3.13 container too. Fix:egeria_client.make_request("POST", url, body).Checked and fine: the other
asyncio.runcallers, which run on sync routes or executor threads; thecreate_task/run_in_executoruses; and nest_asyncio, which is declared directly in requirements, pyproject, the Dockerfile and the conda env, so there is no transitive ImportError.trellis / Resource Explorer (coordinator's static sweep of trellis main e7cda8ea, 2026-10-30). Four sites run pyegeria coroutines with
get_event_loop().run_until_complete. They break on a thread with no loop, or under a running loop:catalogue_gateway.py:1304(_initiate_action_type)surveyors/database/egeria_database_surveyor.py:1000(_async_initiate_survey)surveyors/egeria_publisher.py:687(_async_initiate_survey)surveyors/egeria_publisher.py:2119(_async_get_template_guid_for_technology_type)The
_resolve_template_guidfix is the syncget_template_guid_for_technology_type. Its tests'_publisherfake would then fake the sync method instead of the async one. Also, anything that timesasyncio.run()aroundto_threadwork now includes the executor shutdown.The loop-setting wrappers that exist only to feed them can go too:
resource_scope.py:209-225,web/routes/databases.py:1029,egeria_resync.py:1392. The fix is the sync pyegeria call, orawait. trellis-analytics is unaffected, and RE itself dropped nest-asyncio on 2026-09-04.trellis / egeria-advisor:
conversation_agent.py:435/444/458call bareasyncio.run, which raises if reached from a running loop once pyegeria stops applying nest_asyncio. It declares nest-asyncio but never applies it.Other sample notebooks: no change needed on the Python 3.13 container (apart from the lineage notebook above). For a 3.14 kernel, drop the
nest_asyncio.apply()calls in the twoinitialize-pyegeria.ipynbcopies and inunity_catalog_viewer.ipynb.🤖 Generated with Claude Code
https://claude.ai/code/session_015pdMuQanRknaXmcpx4LmLb