mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
185 lines
8.4 KiB
Python
185 lines
8.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tests for find_python_dependencies.py
|
|
"""
|
|
|
|
import unittest
|
|
|
|
from parameterized import parameterized
|
|
|
|
from bin.find_python_dependencies import (
|
|
LOCAL_PACKAGES,
|
|
REPO_ROOT,
|
|
build_import_graph,
|
|
check_if_changes_affect_entrypoint,
|
|
find_all_dependency_files,
|
|
module_to_file,
|
|
)
|
|
|
|
|
|
class TestFindPythonDependencies(unittest.TestCase):
|
|
graph = None
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.graph = build_import_graph(LOCAL_PACKAGES)
|
|
|
|
@parameterized.expand(
|
|
[
|
|
("simple_module", "insights.utils", "insights/utils.py"),
|
|
(
|
|
"package_module",
|
|
"products.exports.backend.temporal.subscriptions",
|
|
"products/exports/backend/temporal/subscriptions/__init__.py",
|
|
),
|
|
("nested_module", "insights.insightsql_queries.query_runner", "insights/insightsql_queries/query_runner.py"),
|
|
("nonexistent_module", "insights.nonexistent.module", None),
|
|
("ee_module", "ee.tasks.subscriptions.subscription_utils", "ee/tasks/subscriptions/subscription_utils.py"),
|
|
]
|
|
)
|
|
def test_module_to_file(self, _name, module, expected_file):
|
|
self.assertEqual(module_to_file(module), expected_file)
|
|
|
|
def test_returns_only_python_file_paths(self):
|
|
files = find_all_dependency_files(self.graph, "products.exports.backend.temporal.subscriptions")
|
|
self.assertIsInstance(files, set)
|
|
for f in files:
|
|
self.assertTrue(f.endswith(".py"), f"Expected .py file, got {f}")
|
|
|
|
@parameterized.expand(
|
|
[
|
|
# Dependencies - should be included
|
|
("The utils file (e.g. caching key)", "insights/utils.py", True),
|
|
("The underlying query runner", "insights/insightsql_queries/query_runner.py", True),
|
|
# Non-dependencies - should NOT be included
|
|
("API endpoint that calls the worker", "ee/api/subscription.py", False),
|
|
("Schedule config that starts workflows", "insights/temporal/schedule.py", False),
|
|
("Unrelated admin module", "insights/admin/__init__.py", False),
|
|
]
|
|
)
|
|
def test_file_inclusion(self, _name, file_path, should_be_included):
|
|
files = find_all_dependency_files(self.graph, "products.exports.backend.temporal.subscriptions")
|
|
if should_be_included:
|
|
self.assertIn(file_path, files)
|
|
else:
|
|
self.assertNotIn(file_path, files)
|
|
|
|
@parameterized.expand(
|
|
[
|
|
# Direct dependencies - should trigger rebuild
|
|
("entrypoint_init", "products/exports/backend/temporal/subscriptions/__init__.py", True),
|
|
("entrypoint_activities", "products/exports/backend/temporal/subscriptions/activities.py", True),
|
|
("entrypoint_workflows", "products/exports/backend/temporal/subscriptions/workflows.py", True),
|
|
# Transitive dependencies (the bug that caused issue https://github.com/Insights/insights/pull/42307) - should trigger rebuild
|
|
("transitive_utils", "insights/utils.py", True),
|
|
("transitive_query_runner", "insights/insightsql_queries/query_runner.py", True),
|
|
# Export-related files - should trigger rebuild
|
|
("exporter", "insights/tasks/exporter.py", True),
|
|
("image_exporter", "products/exports/backend/tasks/image_exporter.py", True),
|
|
("subscription_utils", "ee/tasks/subscriptions/subscription_utils.py", True),
|
|
# Files that should NOT affect the worker
|
|
("api_endpoint", "ee/api/subscription.py", False),
|
|
("schedule_config", "insights/temporal/schedule.py", False),
|
|
("frontend_code", "frontend/src/test.tsx", False),
|
|
("rust_code", "rust/some_file.rs", False),
|
|
("non_python", "pyproject.toml", False),
|
|
]
|
|
)
|
|
def test_change_detection(self, _name, changed_file, should_be_affected):
|
|
affected, _matching = check_if_changes_affect_entrypoint(
|
|
self.graph,
|
|
"products.exports.backend.temporal.subscriptions",
|
|
[changed_file],
|
|
)
|
|
self.assertEqual(
|
|
affected,
|
|
should_be_affected,
|
|
f"Expected {changed_file} to {'affect' if should_be_affected else 'NOT affect'} "
|
|
f"the worker, but got affected={affected}",
|
|
)
|
|
|
|
def test_multiple_changes_one_affects(self):
|
|
affected, matching = check_if_changes_affect_entrypoint(
|
|
self.graph,
|
|
"products.exports.backend.temporal.subscriptions",
|
|
["frontend/test.tsx", "insights/utils.py", "README.md"],
|
|
)
|
|
self.assertTrue(affected)
|
|
self.assertEqual(matching, ["insights/utils.py"])
|
|
|
|
def test_multiple_changes_none_affect(self):
|
|
affected, matching = check_if_changes_affect_entrypoint(
|
|
self.graph,
|
|
"products.exports.backend.temporal.subscriptions",
|
|
["frontend/test.tsx", "README.md", "rust/main.rs"],
|
|
)
|
|
self.assertFalse(affected)
|
|
self.assertEqual(matching, [])
|
|
|
|
def test_returns_sorted_matching_files(self):
|
|
_affected, matching = check_if_changes_affect_entrypoint(
|
|
self.graph,
|
|
"products.exports.backend.temporal.subscriptions",
|
|
["insights/utils.py", "insights/insightsql_queries/query_runner.py", "ee/models/license.py"],
|
|
)
|
|
self.assertGreater(len(matching), 1, "Need multiple matches to verify sorting")
|
|
self.assertEqual(matching, sorted(matching))
|
|
|
|
@parameterized.expand(
|
|
[
|
|
# Entrypoint - should trigger rebuild
|
|
("entrypoint_init", "products/exports/backend/temporal/subscriptions/__init__.py", True),
|
|
("entrypoint_activities", "products/exports/backend/temporal/subscriptions/activities.py", True),
|
|
("entrypoint_workflows", "products/exports/backend/temporal/subscriptions/workflows.py", True),
|
|
# insights/temporal/common - should trigger rebuild
|
|
("temporal_common_base", "insights/temporal/common/base.py", True),
|
|
("temporal_common_client", "insights/temporal/common/client.py", True),
|
|
# insights/tasks/exporter.py - should trigger rebuild
|
|
("exporter", "insights/tasks/exporter.py", True),
|
|
# products/exports/backend/tasks/ - should trigger rebuild
|
|
("image_exporter", "products/exports/backend/tasks/image_exporter.py", True),
|
|
("csv_exporter", "products/exports/backend/tasks/csv_exporter.py", True),
|
|
# ee/tasks/subscriptions/ - should trigger rebuild
|
|
("subscription_utils", "ee/tasks/subscriptions/subscription_utils.py", True),
|
|
("email_subscriptions", "ee/tasks/subscriptions/email_subscriptions.py", True),
|
|
# Transitive dependencies - should trigger rebuild
|
|
("utils", "insights/utils.py", True),
|
|
("query_runner", "insights/insightsql_queries/query_runner.py", True),
|
|
# Non-dependencies - should NOT trigger rebuild
|
|
("api_endpoint", "ee/api/subscription.py", False),
|
|
("schedule_config", "insights/temporal/schedule.py", False),
|
|
("admin", "insights/admin/admins/batch_imports.py", False),
|
|
("tests", "insights/test/test_utils.py", False),
|
|
]
|
|
)
|
|
def test_analytics_platform_worker_file_triggers_rebuild(self, _name, changed_file, should_trigger):
|
|
affected, _ = check_if_changes_affect_entrypoint(
|
|
self.graph,
|
|
"products.exports.backend.temporal.subscriptions",
|
|
[changed_file],
|
|
)
|
|
if should_trigger:
|
|
self.assertTrue(affected, f"{changed_file} should trigger a rebuild but was not detected")
|
|
else:
|
|
self.assertFalse(affected, f"{changed_file} should NOT trigger a rebuild but was detected")
|
|
|
|
@parameterized.expand(
|
|
[
|
|
("insights", "insights"),
|
|
("ee", "ee"),
|
|
]
|
|
)
|
|
def test_graph_contains_package_modules(self, _name, package):
|
|
modules = self.graph.find_children(package)
|
|
self.assertGreater(len(modules), 0)
|
|
|
|
@parameterized.expand([(pkg,) for pkg in LOCAL_PACKAGES])
|
|
def test_package_exists(self, pkg):
|
|
pkg_path = REPO_ROOT / pkg
|
|
self.assertTrue(pkg_path.exists(), f"Package {pkg} configured but directory doesn't exist")
|
|
self.assertTrue((pkg_path / "__init__.py").exists(), f"Package {pkg} missing __init__.py")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|