Bundle core inside the integration

HACS and the manual install copy only custom_components/nestquest, so
the integration shipped with a top-level 'core' package that was never
deployed and failed with ModuleNotFoundError: No module named 'core'.

Move core/ into custom_components/nestquest/core/, repoint the
re-export shims to relative '.core.X' imports, and update tests
(import paths, monkeypatch targets, caplog logger) to the new
'custom_components.nestquest.core.X' location. The integration is now
self-contained: nothing under custom_components/nestquest reaches
outside its own tree, the migrations logger becomes
custom_components.nestquest.core.migrations, and the SQL-leak guard
allowlists point at the new core/ paths.

uv run pytest -q: 1496 passed.
This commit is contained in:
2026-09-21 14:28:49 -04:00
parent 518f8b80ae
commit fb3c6601c1
32 changed files with 41 additions and 41 deletions
+1 -1
View File
@@ -7,4 +7,4 @@ working without touching every import line.
"""
from __future__ import annotations
from core.const import * # noqa: F401,F403
from .core.const import * # noqa: F401,F403
+2 -2
View File
@@ -7,5 +7,5 @@ working.
"""
from __future__ import annotations
from core.dao_children import * # noqa: F401,F403
from core.dao_children import _CONNECTION_LOCKS, _connection_lock # noqa: F401
from .core.dao_children import * # noqa: F401,F403
from .core.dao_children import _CONNECTION_LOCKS, _connection_lock # noqa: F401
+2 -2
View File
@@ -7,5 +7,5 @@ working.
"""
from __future__ import annotations
from core.dao_instances import * # noqa: F401,F403
from core.dao_instances import _resolve_today # noqa: F401
from .core.dao_instances import * # noqa: F401,F403
from .core.dao_instances import _resolve_today # noqa: F401
+1 -1
View File
@@ -7,4 +7,4 @@ working.
"""
from __future__ import annotations
from core.dao_meta import * # noqa: F401,F403
from .core.dao_meta import * # noqa: F401,F403
+1 -1
View File
@@ -7,4 +7,4 @@ working.
"""
from __future__ import annotations
from core.dao_presence import * # noqa: F401,F403
from .core.dao_presence import * # noqa: F401,F403
+2 -2
View File
@@ -7,8 +7,8 @@ inside the integration keep working.
"""
from __future__ import annotations
from core.dao_rules import * # noqa: F401,F403
from core.dao_rules import ( # noqa: F401
from .core.dao_rules import * # noqa: F401,F403
from .core.dao_rules import ( # noqa: F401
_UNSET,
_validate_date,
_validate_time,
+1 -1
View File
@@ -13,7 +13,7 @@ from typing import Any, TypeVar
from homeassistant.core import HomeAssistant
from core.db import ExecutionResult, NestQuestDatabase
from .core.db import ExecutionResult, NestQuestDatabase
__all__ = ["ExecutionResult", "NestQuestDatabase", "make_database"]
+1 -1
View File
@@ -7,4 +7,4 @@ working.
"""
from __future__ import annotations
from core.migrations import * # noqa: F401,F403
from .core.migrations import * # noqa: F401,F403
+2 -2
View File
@@ -7,5 +7,5 @@ working.
"""
from __future__ import annotations
from core.recurrence import * # noqa: F401,F403
from core.recurrence import _month_end, _nth_weekday_of_month # noqa: F401
from .core.recurrence import * # noqa: F401,F403
from .core.recurrence import _month_end, _nth_weekday_of_month # noqa: F401
+1 -1
View File
@@ -7,4 +7,4 @@ working.
"""
from __future__ import annotations
from core.schema import * # noqa: F401,F403
from .core.schema import * # noqa: F401,F403
+3 -3
View File
@@ -12,9 +12,9 @@ from pathlib import Path
from homeassistant.core import HomeAssistant
from core.const import SQLITE_DB_FILENAME
from core.store import async_get_db_path as _core_async_get_db_path
from core.store import _ensure_dir
from .core.const import SQLITE_DB_FILENAME
from .core.store import async_get_db_path as _core_async_get_db_path
from .core.store import _ensure_dir
__all__ = ["async_get_db_path", "_ensure_dir"]
+6 -6
View File
@@ -487,17 +487,17 @@ def test_children_and_admin_sql_lives_only_in_dao_module() -> None:
scan_roots = [package, repo_root / "tests"]
allowed = {
"custom_components/nestquest/dao_children.py", # the DAO itself
"custom_components/nestquest/dao_rules.py", # validates child
"custom_components/nestquest/core/dao_children.py", # the DAO itself
"custom_components/nestquest/core/dao_rules.py", # validates child
# activeness on assignment (FK to children, done-condition
# 'validates that the assigned child is active')
"custom_components/nestquest/dao_presence.py", # validates the
"custom_components/nestquest/core/dao_presence.py", # validates the
# schedule/override child exists before writing (same FK shape
# as dao_rules: one existence SELECT per write)
"custom_components/nestquest/dao_instances.py", # validates the
"custom_components/nestquest/core/dao_instances.py", # validates the
# instance/event child exists before writing (same FK shape)
"custom_components/nestquest/schema.py", # declares the DDL
"custom_components/nestquest/migrations.py", # applies the DDL
"custom_components/nestquest/core/schema.py", # declares the DDL
"custom_components/nestquest/core/migrations.py", # applies the DDL
"tests/test_schema.py", # tests the DDL
"tests/test_migrations.py", # tests migration application
"tests/test_dao_children.py", # this file, scanned separately
+4 -4
View File
@@ -352,7 +352,7 @@ def test_upsert_no_past_check_uses_execution_date_not_call_date(
monkeypatch = pytest.MonkeyPatch()
monkeypatch.setattr(
"core.dao_instances.datetime",
"custom_components.nestquest.core.dao_instances.datetime",
_ShiftingModule(),
)
try:
@@ -362,7 +362,7 @@ def test_upsert_no_past_check_uses_execution_date_not_call_date(
# the lock, the IN-LOCK re-check sees tomorrow and must
# reject the insert — the stale call-date pre-check alone
# would have let it through.
import core.dao_instances as mod
import custom_components.nestquest.core.dao_instances as mod
async def _lock_holder():
# Hold the CONNECTION lock across a transaction until
@@ -1030,8 +1030,8 @@ def test_no_mutation_sql_for_completion_events_anywhere() -> None:
# INSERT statements are stripped before scanning so only genuine
# UPDATE/DELETE/non-DAO-INSERT paths hit the pattern.
insert_exempt = {
"custom_components/nestquest/dao_instances.py",
"custom_components/nestquest/migrations.py", # the rebuild
"custom_components/nestquest/core/dao_instances.py",
"custom_components/nestquest/core/migrations.py", # the rebuild
# migrations re-create the table via INSERT..SELECT (append-
# order preserved); UPDATE/DELETE stays forbidden everywhere
"tests/test_schema.py",
+3 -3
View File
@@ -553,9 +553,9 @@ def test_presence_sql_lives_only_in_dao_module() -> None:
scan_roots = [package, repo_root / "tests"]
allowed = {
"custom_components/nestquest/dao_presence.py", # this DAO
"custom_components/nestquest/schema.py", # declares the DDL
"custom_components/nestquest/migrations.py", # applies the DDL
"custom_components/nestquest/core/dao_presence.py", # this DAO
"custom_components/nestquest/core/schema.py", # declares the DDL
"custom_components/nestquest/core/migrations.py", # applies the DDL
"tests/test_schema.py", # tests the DDL
"tests/test_migrations.py", # tests migration application
"tests/test_dao_presence.py", # this file, scanned separately
+4 -4
View File
@@ -1218,12 +1218,12 @@ def test_rules_and_definitions_sql_lives_only_in_dao_module() -> None:
scan_roots = [package, repo_root / "tests"]
allowed = {
"custom_components/nestquest/dao_rules.py", # this DAO
"custom_components/nestquest/dao_instances.py", # validates the
"custom_components/nestquest/core/dao_rules.py", # this DAO
"custom_components/nestquest/core/dao_instances.py", # validates the
# definition exists + fetches its assignee before generating an
# instance (the instance must snapshot the definition's child)
"custom_components/nestquest/schema.py", # declares the DDL
"custom_components/nestquest/migrations.py", # applies the DDL
"custom_components/nestquest/core/schema.py", # declares the DDL
"custom_components/nestquest/core/migrations.py", # applies the DDL
"tests/test_schema.py", # tests the DDL
"tests/test_migrations.py", # tests migration application
"tests/test_dao_rules.py", # this file, scanned separately
+1 -1
View File
@@ -13,7 +13,7 @@ import pytest
from conftest import executor_for, make_hass
import core.db as db_module
import custom_components.nestquest.core.db as db_module
from custom_components.nestquest.db import NestQuestDatabase
+1 -1
View File
@@ -1039,7 +1039,7 @@ def test_regenerate_for_child_uses_single_anchor_across_midnight(
clock["reads"] += 1
return day0 if clock["reads"] == 1 else day1
import core.dao_instances as dao_instances_module
import custom_components.nestquest.core.dao_instances as dao_instances_module
monkeypatch.setattr(dao_instances_module, "_today", _fake_today)
+2 -2
View File
@@ -707,7 +707,7 @@ def test_noop_run_logs_no_migration(caplog, tmp_path) -> None:
try:
_migrate(database)
caplog.clear()
with caplog.at_level(logging.INFO, logger="core.migrations"):
with caplog.at_level(logging.INFO, logger="custom_components.nestquest.core.migrations"):
_migrate(database)
migrated = [
r for r in caplog.records if "migrated" in r.getMessage().lower()
@@ -722,7 +722,7 @@ def test_noop_run_is_logged_at_debug_level(caplog, tmp_path) -> None:
try:
_migrate(database)
caplog.clear()
with caplog.at_level(logging.DEBUG, logger="core.migrations"):
with caplog.at_level(logging.DEBUG, logger="custom_components.nestquest.core.migrations"):
_migrate(database)
assert any(
f"already at version {len(MIGRATIONS)}" in r.getMessage()
+2 -2
View File
@@ -27,7 +27,7 @@ def test_returns_absolute_path_from_config_path(monkeypatch) -> None:
# Path resolution only: the parent-dir mkdir is a side effect of
# core.store and is exercised elsewhere; suppress it here so the
# absolute-path assertion can run against /config (non-writable).
monkeypatch.setattr("core.store._ensure_dir", lambda path: None)
monkeypatch.setattr("custom_components.nestquest.core.store._ensure_dir", lambda path: None)
hass = _make_hass(Path("/config"), execute=False)
db_path = _run(store.async_get_db_path(hass))
hass.config.path.assert_called_once_with(SQLITE_DB_FILENAME)
@@ -203,7 +203,7 @@ def test_no_file_creation_calls_in_store() -> None:
def test_container_style_path_resolves(monkeypatch) -> None:
monkeypatch.setattr("core.store._ensure_dir", lambda path: None)
monkeypatch.setattr("custom_components.nestquest.core.store._ensure_dir", lambda path: None)
hass = _make_hass(Path("/config"), execute=False)
assert _run(store.async_get_db_path(hass)) == Path("/config") / SQLITE_DB_FILENAME
+1 -1
View File
@@ -313,7 +313,7 @@ def test_definition_business_regeneration_uses_pinned_today_not_host(
business operations must regenerate against the pinned date and keep
that first local day, proving the threaded ``today`` wins.
"""
import core.dao_instances as dao_instances_module
import custom_components.nestquest.core.dao_instances as dao_instances_module
pinned = datetime.date(2026, 1, 14)
host_today = datetime.date(2026, 1, 15)