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 __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 __future__ import annotations
|
||||||
|
|
||||||
from core.dao_children import * # noqa: F401,F403
|
from .core.dao_children import * # noqa: F401,F403
|
||||||
from core.dao_children import _CONNECTION_LOCKS, _connection_lock # noqa: F401
|
from .core.dao_children import _CONNECTION_LOCKS, _connection_lock # noqa: F401
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ working.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from core.dao_instances import * # noqa: F401,F403
|
from .core.dao_instances import * # noqa: F401,F403
|
||||||
from core.dao_instances import _resolve_today # noqa: F401
|
from .core.dao_instances import _resolve_today # noqa: F401
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ working.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
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 __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 __future__ import annotations
|
||||||
|
|
||||||
from core.dao_rules import * # noqa: F401,F403
|
from .core.dao_rules import * # noqa: F401,F403
|
||||||
from core.dao_rules import ( # noqa: F401
|
from .core.dao_rules import ( # noqa: F401
|
||||||
_UNSET,
|
_UNSET,
|
||||||
_validate_date,
|
_validate_date,
|
||||||
_validate_time,
|
_validate_time,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from typing import Any, TypeVar
|
|||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from core.db import ExecutionResult, NestQuestDatabase
|
from .core.db import ExecutionResult, NestQuestDatabase
|
||||||
|
|
||||||
__all__ = ["ExecutionResult", "NestQuestDatabase", "make_database"]
|
__all__ = ["ExecutionResult", "NestQuestDatabase", "make_database"]
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ working.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
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 __future__ import annotations
|
||||||
|
|
||||||
from core.recurrence import * # noqa: F401,F403
|
from .core.recurrence import * # noqa: F401,F403
|
||||||
from core.recurrence import _month_end, _nth_weekday_of_month # noqa: F401
|
from .core.recurrence import _month_end, _nth_weekday_of_month # noqa: F401
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ working.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
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 homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from core.const import SQLITE_DB_FILENAME
|
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 async_get_db_path as _core_async_get_db_path
|
||||||
from core.store import _ensure_dir
|
from .core.store import _ensure_dir
|
||||||
|
|
||||||
__all__ = ["async_get_db_path", "_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"]
|
scan_roots = [package, repo_root / "tests"]
|
||||||
|
|
||||||
allowed = {
|
allowed = {
|
||||||
"custom_components/nestquest/dao_children.py", # the DAO itself
|
"custom_components/nestquest/core/dao_children.py", # the DAO itself
|
||||||
"custom_components/nestquest/dao_rules.py", # validates child
|
"custom_components/nestquest/core/dao_rules.py", # validates child
|
||||||
# activeness on assignment (FK to children, done-condition
|
# activeness on assignment (FK to children, done-condition
|
||||||
# 'validates that the assigned child is active')
|
# '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
|
# schedule/override child exists before writing (same FK shape
|
||||||
# as dao_rules: one existence SELECT per write)
|
# 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)
|
# instance/event child exists before writing (same FK shape)
|
||||||
"custom_components/nestquest/schema.py", # declares the DDL
|
"custom_components/nestquest/core/schema.py", # declares the DDL
|
||||||
"custom_components/nestquest/migrations.py", # applies the DDL
|
"custom_components/nestquest/core/migrations.py", # applies the DDL
|
||||||
"tests/test_schema.py", # tests the DDL
|
"tests/test_schema.py", # tests the DDL
|
||||||
"tests/test_migrations.py", # tests migration application
|
"tests/test_migrations.py", # tests migration application
|
||||||
"tests/test_dao_children.py", # this file, scanned separately
|
"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 = pytest.MonkeyPatch()
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"core.dao_instances.datetime",
|
"custom_components.nestquest.core.dao_instances.datetime",
|
||||||
_ShiftingModule(),
|
_ShiftingModule(),
|
||||||
)
|
)
|
||||||
try:
|
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
|
# the lock, the IN-LOCK re-check sees tomorrow and must
|
||||||
# reject the insert — the stale call-date pre-check alone
|
# reject the insert — the stale call-date pre-check alone
|
||||||
# would have let it through.
|
# would have let it through.
|
||||||
import core.dao_instances as mod
|
import custom_components.nestquest.core.dao_instances as mod
|
||||||
|
|
||||||
async def _lock_holder():
|
async def _lock_holder():
|
||||||
# Hold the CONNECTION lock across a transaction until
|
# 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
|
# INSERT statements are stripped before scanning so only genuine
|
||||||
# UPDATE/DELETE/non-DAO-INSERT paths hit the pattern.
|
# UPDATE/DELETE/non-DAO-INSERT paths hit the pattern.
|
||||||
insert_exempt = {
|
insert_exempt = {
|
||||||
"custom_components/nestquest/dao_instances.py",
|
"custom_components/nestquest/core/dao_instances.py",
|
||||||
"custom_components/nestquest/migrations.py", # the rebuild
|
"custom_components/nestquest/core/migrations.py", # the rebuild
|
||||||
# migrations re-create the table via INSERT..SELECT (append-
|
# migrations re-create the table via INSERT..SELECT (append-
|
||||||
# order preserved); UPDATE/DELETE stays forbidden everywhere
|
# order preserved); UPDATE/DELETE stays forbidden everywhere
|
||||||
"tests/test_schema.py",
|
"tests/test_schema.py",
|
||||||
|
|||||||
@@ -553,9 +553,9 @@ def test_presence_sql_lives_only_in_dao_module() -> None:
|
|||||||
scan_roots = [package, repo_root / "tests"]
|
scan_roots = [package, repo_root / "tests"]
|
||||||
|
|
||||||
allowed = {
|
allowed = {
|
||||||
"custom_components/nestquest/dao_presence.py", # this DAO
|
"custom_components/nestquest/core/dao_presence.py", # this DAO
|
||||||
"custom_components/nestquest/schema.py", # declares the DDL
|
"custom_components/nestquest/core/schema.py", # declares the DDL
|
||||||
"custom_components/nestquest/migrations.py", # applies the DDL
|
"custom_components/nestquest/core/migrations.py", # applies the DDL
|
||||||
"tests/test_schema.py", # tests the DDL
|
"tests/test_schema.py", # tests the DDL
|
||||||
"tests/test_migrations.py", # tests migration application
|
"tests/test_migrations.py", # tests migration application
|
||||||
"tests/test_dao_presence.py", # this file, scanned separately
|
"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"]
|
scan_roots = [package, repo_root / "tests"]
|
||||||
|
|
||||||
allowed = {
|
allowed = {
|
||||||
"custom_components/nestquest/dao_rules.py", # this DAO
|
"custom_components/nestquest/core/dao_rules.py", # this DAO
|
||||||
"custom_components/nestquest/dao_instances.py", # validates the
|
"custom_components/nestquest/core/dao_instances.py", # validates the
|
||||||
# definition exists + fetches its assignee before generating an
|
# definition exists + fetches its assignee before generating an
|
||||||
# instance (the instance must snapshot the definition's child)
|
# instance (the instance must snapshot the definition's child)
|
||||||
"custom_components/nestquest/schema.py", # declares the DDL
|
"custom_components/nestquest/core/schema.py", # declares the DDL
|
||||||
"custom_components/nestquest/migrations.py", # applies the DDL
|
"custom_components/nestquest/core/migrations.py", # applies the DDL
|
||||||
"tests/test_schema.py", # tests the DDL
|
"tests/test_schema.py", # tests the DDL
|
||||||
"tests/test_migrations.py", # tests migration application
|
"tests/test_migrations.py", # tests migration application
|
||||||
"tests/test_dao_rules.py", # this file, scanned separately
|
"tests/test_dao_rules.py", # this file, scanned separately
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ import pytest
|
|||||||
|
|
||||||
from conftest import executor_for, make_hass
|
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
|
from custom_components.nestquest.db import NestQuestDatabase
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1039,7 +1039,7 @@ def test_regenerate_for_child_uses_single_anchor_across_midnight(
|
|||||||
clock["reads"] += 1
|
clock["reads"] += 1
|
||||||
return day0 if clock["reads"] == 1 else day1
|
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)
|
monkeypatch.setattr(dao_instances_module, "_today", _fake_today)
|
||||||
|
|
||||||
|
|||||||
@@ -707,7 +707,7 @@ def test_noop_run_logs_no_migration(caplog, tmp_path) -> None:
|
|||||||
try:
|
try:
|
||||||
_migrate(database)
|
_migrate(database)
|
||||||
caplog.clear()
|
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)
|
_migrate(database)
|
||||||
migrated = [
|
migrated = [
|
||||||
r for r in caplog.records if "migrated" in r.getMessage().lower()
|
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:
|
try:
|
||||||
_migrate(database)
|
_migrate(database)
|
||||||
caplog.clear()
|
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)
|
_migrate(database)
|
||||||
assert any(
|
assert any(
|
||||||
f"already at version {len(MIGRATIONS)}" in r.getMessage()
|
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
|
# Path resolution only: the parent-dir mkdir is a side effect of
|
||||||
# core.store and is exercised elsewhere; suppress it here so the
|
# core.store and is exercised elsewhere; suppress it here so the
|
||||||
# absolute-path assertion can run against /config (non-writable).
|
# 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)
|
hass = _make_hass(Path("/config"), execute=False)
|
||||||
db_path = _run(store.async_get_db_path(hass))
|
db_path = _run(store.async_get_db_path(hass))
|
||||||
hass.config.path.assert_called_once_with(SQLITE_DB_FILENAME)
|
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:
|
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)
|
hass = _make_hass(Path("/config"), execute=False)
|
||||||
assert _run(store.async_get_db_path(hass)) == Path("/config") / SQLITE_DB_FILENAME
|
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
|
business operations must regenerate against the pinned date and keep
|
||||||
that first local day, proving the threaded ``today`` wins.
|
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)
|
pinned = datetime.date(2026, 1, 14)
|
||||||
host_today = datetime.date(2026, 1, 15)
|
host_today = datetime.date(2026, 1, 15)
|
||||||
|
|||||||
Reference in New Issue
Block a user