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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@ working.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from core.migrations import * # noqa: F401,F403
|
||||
from .core.migrations import * # noqa: F401,F403
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,4 +7,4 @@ working.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from core.schema import * # noqa: F401,F403
|
||||
from .core.schema import * # noqa: F401,F403
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user