Release 0.15.0a1 - #445
Open
github-actions[bot] wants to merge 105 commits into
Open
Conversation
* feat: AsyncFakeBus alongside FakeBus Adds an asyncio-native sibling to FakeBus that mirrors the surface of ovos_bus_client.client.AsyncMessageBusClient (the [async] extra shipped in ovos-bus-client 2.0). ovos_utils.fakebus.AsyncFakeBus - connect/close/emit/wait_for_message/wait_for_response: coroutines - on/once/remove/remove_all_listeners: synchronous (matches the real AsyncMessageBusClient's handler-registration contract — pyee, no awaitable callbacks) - Same session-context injection side effects as FakeBus, so multi-turn flows behave identically - Lazy-imports from ovos_bus_client.session when present; gracefully degrades without it (same pattern as FakeBus) - Backwards-compat shims (create_client, run_forever, run_in_thread) so it is a drop-in replacement anywhere FakeBus is currently used Where this pays off - Tests of code that calls 'await bus.emit(...)' or 'await bus.wait_for_response(...)' (test_fakebus's sync equivalents cover that surface for the old client; this covers the new one). - Runtime: in-process bus stand-in for asyncio-native components that do not need a real ovos-core (mirrors how FakeBus is used today by HiveMessageBusClient.connect(bus=FakeBus())). Tests (test/unittests/test_async_fakebus.py, 19 tests) - Lifecycle (construct/connect/close, session_id from kwarg) - Handler registration (on/once/remove/remove_all_listeners + dispatch) - emit side effects (session injection, raw 'message' event) - wait_for_message (matched concurrently, timeout) - wait_for_response (default <type>.response, explicit reply_type, timeout) - Backwards-compat shims No changes to existing FakeBus; all 23 existing FakeBus tests still pass. * docs: add AsyncFakeBus to fakebus documentation Documents the new AsyncFakeBus class alongside FakeBus in docs/fakebus.md: coroutine/sync split table, key-methods table with file:line citations, session-handling note, and a minimal usage snippet. Updated docs/index.md module table and Contents list to include AsyncFakeBus. AI-Generated Change: - Model: claude-sonnet-4-6 - Intent: keep docs accurate after feat: AsyncFakeBus alongside FakeBus (14ccc3d) - Impact: updated docs/fakebus.md, docs/index.md; added citations; no stale content removed - Verified via: manual review against ovos_utils/fakebus.py
* feat: expand_template delegates to ovos-spec-tools ovos-utils' bracket expander is one of ~7 copies of the same logic across the OVOS ecosystem. It now delegates to the OVOS-INTENT-1 reference expander, ovos_spec_tools.expand — the single conformant implementation — instead of carrying its own. - expand_template() is a thin wrapper over ovos_spec_tools.expand. - adds the ovos-spec-tools dependency. Behaviour change (conformance with OVOS-INTENT-1): - a malformed template — a single-branch group, an empty sample, a slot-only template — now raises MalformedTemplate instead of silently producing a degenerate result. The old lenient behaviour was a bug. - whitespace in a sample is normalized to single spaces; an emptied [optional] no longer leaves a double space. Tests updated to the conformant output; a test added for the malformed-template raise. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: migrate lang and dialog onto ovos-spec-tools; deprecate the shims Completes the ovos-utils migration onto ovos-spec-tools. - lang.get_language_dir() delegates to ovos_spec_tools.closest_lang. - dialog.py and file_utils.py use ovos_spec_tools.expand directly. - expand_template, get_language_dir, MustacheDialogRenderer, load_dialogs and get_dialog are deprecated: each both emits a DeprecationWarning (visible to IDEs and tooling) and logs via the @deprecated helper, pointing callers at the ovos-spec-tools equivalent. The removal version is derived from version.py — the next major release (VERSION_MAJOR + 1). standardize_lang_tag is left unchanged: its `macro` parameter has no ovos-spec-tools equivalent and documented, tested behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: migrate standardize_lang_tag to ovos-spec-tools standardize_lang_tag now delegates to ovos_spec_tools.standardize_lang and is deprecated (DeprecationWarning + the @deprecated log helper). The `macro` parameter is kept for backward compatibility but no longer affects the result: its region-stripping only ever happened on the no-langcodes fallback path — inconsistent with the langcodes path, which never stripped the region — so it was a latent bug. geolocation.py now uses ovos_spec_tools.standardize_lang directly. test_lang.py updated to the conformant behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: standardize_lang_tag macro=True returns the bare language Keep the `macro` parameter functional — when set, drop the region with a plain .split("-")[0] on the standardized tag, so standardize_lang_tag(x, macro=True) returns the bare primary language subtag as before. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ak (#375) * feat: fakebus Message is the ovos-spec-tools class directly; publish attached as a method OVOS-MSG-1 lives in ovos-spec-tools 0.5.0a1+. The 165-line FakeMessage class (with its _MutableMessage metaclass + dynamic __new__) is gone; fakebus now re-exports the spec-tools Message directly and attaches the one legacy convenience method downstream still uses: from ovos_spec_tools.message import Message as FakeMessage FakeMessage.publish = _publish_function The MutableMessage metaclass / runtime indirection that tried to return an ovos_bus_client.Message when bus-client was installed is no longer needed: spec-tools is a hard dependency, the canonical class is always present, and ovos-bus-client.Message is the **same** class. What's gone: * The historical reply() quirk that promoted data['destination'] into context['destination'] was always a bug. * as_dict is now on the spec-tools Message itself; no need to define it here. What stays: * publish() — attached at module import; relay under a new topic, drop 'target', no swap, no deep-copy of data; * The deprecated ovos_utils.fakebus.Message alias for downstream callers still doing from ovos_utils.fakebus import Message. pyproject pin: ovos-spec-tools>=0.5.0a1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: deprecate fakebus.FakeMessage.publish — slated for removal in next major publish is a bus-client tradition outside OVOS-MSG-1 (the spec defines forward / reply / response as the only normative derivations). Every call now fires a DeprecationWarning via both warnings.warn and the @deprecated decorator from ovos_utils.log, naming the next major (computed f'{VERSION_MAJOR + 1}.0.0' from version.py) as the removal target. Migration: switch publish() callers to forward() or reply(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: bump ovos-spec-tools to >=0.5.1a1 to pick up the empty-msg_type accept 0.5.0a1 still rejected empty `msg_type` at construction, which broke the `Message("").forward(real_type, data)` scaffold pattern used by the scheduler/event tests. 0.5.1a1 accepts empty at construction and gates only on serialize/wire output. Align both pins. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: align coverage workflow with build-tests (install extras, scope to test/unittests) The coverage job was installing the base package only — test modules that import `ovos_bus_client` / `ovos_config` failed at collection. Match the build-tests config so coverage actually runs the suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: pass install_extras as a pip arg, not an extras name The gh-automations coverage workflow installs literally what install_extras contains: `pip install ${install_extras}`. `extras` alone is meaningless; `.[extras]` is what installs the optional-dependencies group. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…des semantics) (#377) The spec-tools migration of `standardize_lang_tag` changed the `macro` argument from its historic meaning (langcodes-defined **macrolanguage substitution** — `cmn` -> `zh`, `nb` -> `no`) to **"strip the region"** (`en-US` -> `en`) via `tag.split('-')[0]`. Every caller that passes the default `macro=True` (`ovos_bus_client.session`, many transformer plugins, …) now gets region-stripped output — including OVOS's `SessionManager`, which silently rewrites `session.lang` from `en-US` to `en` on every message and breaks downstream consumers that key on region (locale resource lookups, regional dialog, ovoscope final-session assertions). Restore the historic semantics by delegating to `langcodes.standardize_tag(lang_code, macro=macro)` — which is what the pre-migration body did (commit 9baa615). When langcodes is unavailable, fall back to spec-tools' `standardize_lang` (also region-preserving) and treat `macro` as a no-op. Tests pin all three: - macro=True preserves the region (`en-US` round-trips) - macro=True substitutes macrolanguages (`cmn` -> `zh`) - macro=False keeps both the region and the sublanguage Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
json_database~=0.10 resolves to >=0.10,<1.0, excluding json_database 1.x (now published, 1.0.2a1, which dropped the duplicate hivemind-json-db-plugin entry point). As ovos-utils is a foundational dependency, this cap blocks json_database 1.x from resolving anywhere in the OVOS alpha set. Widen to >=0.10,<2.0.0. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FakeBus now uses ovos_spec_tools.NamespaceTranslator (the same logic as MessageBusClient) so the test/satellite double bridges legacy<->ovos.* topics and dedupes dual-listeners identically: emit() also dispatches the counterpart topic(s); on() wraps migrated-topic handlers with the shared mirror-guard. Both flags default on; override per-instance with modernize=/emit_legacy=. Bumps ovos-spec-tools>=0.9.0a1 (NamespaceTranslator).
…383) fakebus.py imports NamespaceTranslator from ovos_spec_tools at module top level (unconditional), and that symbol first ships in ovos-spec-tools 0.10.0a1. The previous >=0.9.0a1 floor could resolve to a version without it, making `import ovos_utils.fakebus` fail at install time. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: translate mirrored payload onto counterpart topic in FakeBus FakeBus mirrors MessageBusClient's namespace-migration bridge; it now calls NamespaceTranslator.translate_payload() at its counterpart-emit point so the mirrored Message carries the payload in the COUNTERPART topic's shape instead of a verbatim copy. This keeps the test double faithful to the real bus. Identity transform for payload-compatible renames (behaviour unchanged); reshaped per direction for the shape-changing renames (handler trio, detach_intent, enable/disable_intent). Pins ovos-spec-tools floor to >=0.14.1a1 (the version that will publish the translate_payload API, ovos-spec-tools#42). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: install in-flight spec-tools cross-dep from git * ci: pre-install spec-tools conformance-message for coverage job * fix: lower spec-tools floor to 0.14.0a1 (translate_payload branch version) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: mirror namespace migration in AsyncFakeBus + env/config flag parity
AsyncFakeBus now mirrors FakeBus / MessageBusClient namespace migration:
builds a NamespaceTranslator, wraps migrated-topic handlers with a
mirror-guard for dedup in on()/remove(), and dispatches counterpart
topics with translate_payload on emit().
Both FakeBus and AsyncFakeBus resolve modernize/emit_legacy the way the
real client's _bus_flag does: explicit kwarg wins, else env var ->
websocket.* config -> default True. A local _bus_flag helper mirrors the
semantics without importing from ovos-bus-client (layering). A _UNSET
sentinel distinguishes "kwarg omitted" from "kwarg passed True/False".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: drop obsolete spec-tools git-ref in CI; floor to published translate_payload
The build/coverage workflows pinned ovos-spec-tools@fix/conformance-message
via pre_install_pip because translate_payload was unpublished. It has shipped
since 0.16.1a1, so the git-ref is obsolete (and masked an inadequate
>=0.14.0a1 floor that cannot satisfy the FakeBus namespace-migration code).
Floor bumped to >=0.16.1a2 (the published min carrying the NamespaceTranslator
payload-transform API) and the CI git-ref removed — versions belong in
pyproject, not CI.
* chore: remove stray agent scratch files (AGENTS.md, TODO.md, console-script artifact)
* chore: drop requirements/*.txt — pyproject.toml is the single source of truth
The requirements/{requirements,extras}.txt files duplicated the inline
[project.dependencies] / [project.optional-dependencies] (pyproject was already
a superset — it additionally carried python-dateutil and packaging). Nothing
reads them (no setup.py/MANIFEST; CI installs .[extras]). Removed per the
pyproject-only packaging rule.
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…390) The FakeBus shape-changing reshape tests asserted a mycroft.skill.handler.* <-> ovos.intent.handler.* migration that does not (and should not) exist — the handler-lifecycle trio is orchestrator-owned spec topics, not a rename of the legacy per-skill handler events, so it was correctly absent from ovos-spec-tools' MIGRATION_MAP. The tests therefore failed (the spec listener was never reached). Retarget them at an actual shape-changing pair from MIGRATION_PAYLOAD_TRANSFORMS: detach_intent <-> ovos.intent.deregister, which splits the compound "skill:intent" name into skill_id + intent_name (and rejoins it in reverse). Same reshape coverage, real mapping. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* perf: skip disabled debug call-site resolution * fix: mirror stdlib logging suppression * perf: gate all disabled log levels
…error type (#429) mpris_metadata used Variant('d', length) for mpris:length, but MPRIS2 requires signature 'x' (int64 microseconds); a non-finite length also crashed at Variant construction. update() set attributes via raw setattr with no validation, letting a non-numeric length/position/match_confidence poison later consumers such as Playlist.length's sum(). dict2entry raised AttributeError on non-dict input instead of a consistent ValueError.
) log_deprecation() called inspect.stack() (a full O(n) frame walk) before checking whether the deprecation had already been logged, so every deduplicated repeat call paid the same walk as the first. A caller invoking a deprecated helper once per file in a batch (e.g. once per .desktop file at boot) paid this cost on every repeat instead of just the first, measured at ~8 minutes of added boot time on a real install. log_deprecation now consults a cheap, inspect.stack()-free pre-check keyed on the immediate caller's code object/line (via sys._getframe(1)) plus the message and any explicit func_name/func_module before doing anything else. Only a call from a not-yet-seen call site falls through to the existing stack walk that resolves the full (message, origin) dedup key; repeats from the same site return immediately. Logged messages and dedup semantics (same message, different callers still each log once; same site, same message logs once) are unchanged. Added a regression test pinning existing dedup behavior on top of the existing test_log_deprecation_dedupe (adding a fast-cache clear it didn't need to know about before), and a perf regression guard that patches inspect.stack with a counting wrapper: on unfixed dev code the wrapper is called 3/3 times (and further fails at `_logged_deprecations_fast` not existing before that); after the fix only the first of three repeat calls invokes inspect.stack. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…it (#411) _bridge_intent_topic() runs unguarded in both FakeBus.emit and AsyncFakeBus.emit, unlike the counterpart-topics loop right above it, which isolates per-topic errors with try/except + LOG.exception. A raising bridge would propagate out of emit() and take down local handler dispatch with it. Wrap both call sites in the same guard. The FakeBus/AsyncFakeBus intent-topic bridge itself (RULE 1 canonical twin, RULE 2 legacy modernize) already landed on dev via #417 with a slightly different design (separate modernize/emit_legacy flags, per-topic-pair mirror-guard dedup, no wire-firehose replay for the twin) than this branch originally shipped, so the regression suite added here targets the current, merged behaviour instead of duplicating the bridge. Rebased onto dev (was 7 commits atop a stale base with heavy conflicts in fakebus.py from the intervening #417 refactor; squashed to one commit here). Fail-before: the two error-isolation tests raised RuntimeError out of emit() without the try/except; full suite went 983 (dev) -> 1004 passed with this branch, no regressions. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
TestEventScheduler in test_event_scheduler.py imported EventScheduler
straight from ovos_bus_client.util.scheduler and patched
builtins.open/json.load/json.dump/threading.Thread to assert on that
class's pre-rework internals (es.events, tuple entries, a bare persisted
dict). ovos-bus-client 2.11.1a1 replaced that store with a versioned
envelope (ovos_bus_client/util/scheduled_events/store.py); load() now
does content.get("version") which raises AttributeError against the
mocked json.load returning a string, so all four tests fail on current
ovos-bus-client while the real (unmocked) code path works fine.
The scheduler class and its persistence format are owned by
ovos-bus-client, which carries its own scheduler test suite. ovos-utils
has no business pinning another package's internals; drop the stale
tests rather than rewriting them here. TestEventSchedulerInterface,
which exercises ovos_utils' own deprecated shim, is unaffected and
stays.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…437) OVOS-SESSION-2 §5.1's arrival merge is a once-per-utterance orchestrator-intake fold (core#915), not something every bus consumer repeats on every message it observes. FakeBus.on_message (sync and async) called SessionManager.update on every inbound message, including default-session traffic; for a default session that resolves through spec-tools' full-replace update() using spec defaults for OMITTED fields, so a later message whose carrier simply doesn't restate a field wipes it out of the live store -- the same §2.6 violation fixed on the real client in ovos-bus-client#317. A FakeBus models one bus connection for a test, and a test drives far more default-session traffic through it than one fold per utterance. on_message now only calls update() for a carrier that names a real (non- default) session id, checked via resolve_session_id(carrier) == DEFAULT_SESSION_ID, matching the real MessageBusClient's _take_inbound_ session (ovos-bus-client#317, rebased onto ovos-bus-client's #324 which renamed the resolver from names_the_default to the now-public resolve_session_id). A test that wants the orchestrator's own intake semantics calls SessionManager.fold_inbound explicitly, the same shape core's real intake now uses. No existing test in this repo asserted the old per-message fold behavior, so nothing needed rewriting for meaning; the new tests in test_fakebus_session_fold.py pin the corrected contract directly (fail before this fix: stale/omitted-field observed traffic overwrites the store; pass after) plus a positive pin that explicit fold_inbound still merges field-by-field through the same FakeBus/Message plumbing. resolve_session_id and session_carrier only exist from ovos-bus-client 2.11.4a1 (session_carrier since fold_inbound landed, resolve_session_id since #324 made the resolver public), but pyproject.toml's `extras` still floored ovos_bus_client at >=2.6.2a2. On any install between that floor and 2.11.4a1 the new import raised ImportError, and the bare `except ImportError: pass` this block already had (for the case ovos-bus-client is not installed at all, an optional dependency) silently swallowed it too, disabling ALL named-session tracking in FakeBus rather than failing loudly or working correctly. Fixed two ways: the floor is bumped to >=2.11.4a1 (floor pin only, no upper bound), and the try/except is narrowed to an if/else so only the import statement itself is covered by the "not installed" ImportError guard -- a real error in the block that follows is no longer misread as "not installed". Rebased onto origin/dev's #438, which dropped the stale test_event_scheduler.py failures this branch previously reported as pre-existing and unrelated -- the full suite is fully green with no failures left to carry forward. Full suite: clean origin/dev (6b67265) is 1000 passed / 1 skipped; this branch is 1003 passed / 1 skipped -- the +3 is the new test_fakebus_session_fold.py file in full, nothing else changed status. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…er (#441) OVOS-SESSION-1 §2.5, as merged in architecture dev 198a3c9, requires that on a malformed carrier the messagebus SERVER -- the first consumer on ingress, before fan-out -- MUST NOT crash: it drops the whole message and emits one ovos.session.rejected notice naming the dropped type and reason. A regular client only drops and logs; it does not emit the rejection itself, since it is not the ingress point. ovos-bus-client 2.11.9a1's MessageBusClient already does the client-side drop-and-log half in ovos_bus_client/client/client.py (_take_inbound_session wrapped in try/except MalformedSession, returning before dispatch). A FakeBus is server and client in one process, so it is the correct place for the ovos.session.rejected emit -- there is no separate server process to do it instead. Both FakeBus.on_message and AsyncFakeBus.on_message in ovos_utils/fakebus.py called session_carrier()/Session.from_message() completely unguarded, so any handler that re-emits a Message carrying a non-object context.session (e.g. a skill that drops an utterance but still emits its own ack) crashed every FakeBus-based test and ovoscope's MiniCroft with an uncaught MalformedSession. This implements the full merged §2.5 behaviour (drop + one ovos.session.rejected) in FakeBus/AsyncFakeBus, playing the server's role in the single process a FakeBus models. Both on_message implementations now catch MalformedSession, log a WARNING, and report the drop back to their caller (emit()) instead of raising, so emit() skips delivering the malformed message to any listener. on_message then emits SpecMessage.SESSION_REJECTED (ovos-spec-tools>=1.10.7a2) with data={"msg_type": <dropped type>, "reason": "malformed_carrier"}, carrying the dropped message's utterance_id when present and no session key -- routed through a new _deliver() helper split out of emit() so the rejection is not itself stamped with a default session. pyproject.toml's ovos-spec-tools floor is bumped to >=1.10.7a2 for the SESSION_REJECTED constant. Fail-before: reverting only the ovos_utils/fakebus.py + pyproject.toml hunks (tests kept) makes 3 of 5 new regression tests fail -- the malformed message was still delivered to the "x" listener and no ovos.session.rejected was emitted. Restoring the fix makes all 5 pass. Full suite: 1008 passed, 1 skipped (pre-existing, unrelated), before (aside from the 3 failing new tests) and after. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.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.
Human review requested! This supersedes any earlier open release proposal for this repository.