Skip to content

refactor(hogql): defer printer boundary loads and guard the full compile pipeline - #72352

Merged
georgemunyoro merged 3 commits into
masterfrom
posthog-code/hogql-seam1-printer-boundary-sweep
Jul 21, 2026
Merged

refactor(hogql): defer printer boundary loads and guard the full compile pipeline#72352
georgemunyoro merged 3 commits into
masterfrom
posthog-code/hogql-seam1-printer-boundary-sweep

Conversation

@georgemunyoro

Copy link
Copy Markdown
Contributor

Problem

Final step of making the HogQL compile pipeline importable without django.setup() (previously: #71112, #71568, #71913, #71960, #72271). After #72271 freed the transform layer, exactly two module-level Django imports remained in the whole printer package, both in printer/utils.py: the property-level access-control load (get_restricted_properties_for_team) and the events-retention floor (events_retention_months_for_team). Because printer/__init__.py imports utils, those two lines kept every printer module - and transitively clickhouse_property_resolution - coupled to Django at import time.

Changes

  • printer/utils.py defers both imports to their call sites inside prepare_ast_for_printing, with the same "Deferred:" boundary comments used by Database.create_for and load_property_metadata. Both loads were already conditional (restricted_properties is None guard; apply_events_retention_floor flag), so the function-local imports change nothing about when they run.
  • With that, the whole printer package (printer/__init__ pulls in every dialect printer plus utils) and clickhouse_property_resolution import Django-free and joined DJANGO_FREE_MODULES in the guard test. The list orders the printer before clickhouse_property_resolution because of a pre-existing import-order cycle (cpr imports printer.base/printer.clickhouse, which triggers the package init, which imports utils, which imports cpr back) - it resolves whenever the printer package initializes first, which every production path does. Untangling that cycle is a separable cleanup, not attempted here.
  • The guard test's bare-interpreter child now compiles a real query end to end - parse → resolve → prepare → print to ClickHouse SQL - with every Django-side load either pre-seeded on the context (database catalog, restricted properties, events-schema pin, retention opt-out) or stubbed at its one declared boundary (load_property_metadata). The query uses an unaliased count(), which exercises the resolver's lazy printer import for implicit-alias derivation - the last known "needs Django until Seam 1 lands" caveat, now covered Django-free.

Note

This PR is stacked on #72271 and should merge after it.

How did you test this code?

Automated only (no manual testing):

  • test_no_django_imports.py - the extended guard. The new end-to-end compile assertion catches a regression no existing test does: every printer test runs under pytest-django, so a reintroduced module-level Django import (or an ORM call on the compile path outside the declared loader boundaries) would previously only surface in production tooling contexts; now it fails this test with AppRegistryNotReady. It reuses the existing child subprocess - no new process, no DB, and the only mock is the declared Django boundary.
  • Behavioral sweep: posthog/hogql/printer/test/, test_property_types.py, test_property_types_dmat.py, test_property_skip_indexes.py, and products/access_control/backend/tests/test_property_access_control.py (exercises the deferred access-control load through the real path) - 1172 passed, all snapshots unchanged.
  • Repo-wide uv run mypy --cache-fine-grained . - clean apart from the known pre-existing personhog_client/converters.py unused-ignore that reproduces on clean master.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

No user-facing, API, or config changes - internal refactor only.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Authored with PostHog Code (Claude). Step 4 of 4 in the Seam 1 sequence: (1) relocate pure constants (#71913), (2) PropertyMetadata bundle + loader (#71960), (3) materialized-column map into the bundle (#72271), (4) this PR. Skills invoked: /writing-tests (for the guard-test extension). Decisions: deferred imports at the call sites rather than extracting a separate loader module, because prepare_ast_for_printing already is the Django boundary for these per-query loads (same rationale as Database.create_for); verified before editing that no mock.patch site targets either function as a printer.utils attribute, so the deferral breaks no tests; left the cpr↔printer import-order cycle in place (pre-existing, resolves under production import order) and documented it in the guard list rather than restructuring the printer package in a byte-identical refactor series; in the child compile, runtime django.conf.settings reads were confirmed unreachable (they only fire for channel-lookup and currency functions, not a plain property query).


Created with PostHog Code

@georgemunyoro georgemunyoro self-assigned this Jul 20, 2026
@trunk-io

trunk-io Bot commented Jul 20, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@georgemunyoro
georgemunyoro marked this pull request as ready for review July 21, 2026 12:01
@georgemunyoro
georgemunyoro requested a review from a team as a code owner July 21, 2026 12:01
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team July 21, 2026 12:02
Comment thread posthog/hogql/test/test_no_django_imports.py
@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
posthog/hogql/test/test_no_django_imports.py:44-45
**Import Guard Uses Warmed Package**

The loop imports `posthog.hogql.printer` before `clickhouse_property_resolution`, so the latter is checked only after its cyclic printer dependencies are initialized. A standalone process that imports `clickhouse_property_resolution` first can still fail during this cycle while the guard passes, leaving the claimed direct import path uncovered.

Reviews (1): Last reviewed commit: "refactor(hogql): defer printer boundary ..." | Re-trigger Greptile

Comment thread posthog/hogql/test/test_no_django_imports.py Outdated
@georgemunyoro
georgemunyoro force-pushed the posthog-code/hogql-seam1-printer-boundary-sweep branch from 15cb459 to 5ef1f08 Compare July 21, 2026 12:17
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Playwright — all passed

All tests passed.

View test results →

Backend coverage — all changed backend lines covered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ████████████████████ 100.0% (5 / 5)

All changed backend lines are covered ✅

Per-product line coverage (touched products)
Product Coverage Lines
platform_features ██░░░░░░░░░░░░░░░░░░ 12.1% 7 / 58
batch_exports ████████░░░░░░░░░░░░ 39.6% 8,414 / 21,244
demo ███████████░░░░░░░░░ 56.2% 1,497 / 2,663
warehouse_sources_queue ████████████░░░░░░░░ 59.2% 148 / 250
tasks █████████████░░░░░░░ 66.4% 26,566 / 40,009
data_tools ██████████████░░░░░░ 70.0% 63 / 90
ai_gateway ███████████████░░░░░ 75.0% 9 / 12
signals ████████████████░░░░ 79.0% 20,089 / 25,420
data_modeling ████████████████░░░░ 80.0% 4,834 / 6,045
cdp ████████████████░░░░ 80.7% 3,118 / 3,864
wizard █████████████████░░░ 84.8% 935 / 1,102
notebooks █████████████████░░░ 85.3% 7,266 / 8,522
agent_platform █████████████████░░░ 86.4% 3,807 / 4,405
actions █████████████████░░░ 86.6% 717 / 828
cohorts █████████████████░░░ 87.0% 4,303 / 4,945
product_tours ██████████████████░░ 87.9% 1,303 / 1,482
exports ██████████████████░░ 88.4% 6,943 / 7,853
business_knowledge ██████████████████░░ 89.0% 4,391 / 4,936
conversations ██████████████████░░ 89.1% 16,431 / 18,431
dashboards ██████████████████░░ 89.3% 5,928 / 6,640
visual_review ██████████████████░░ 89.4% 5,818 / 6,505
engineering_analytics ██████████████████░░ 89.5% 5,507 / 6,154
mcp_analytics ██████████████████░░ 89.7% 2,663 / 2,969
alerts ██████████████████░░ 89.9% 4,054 / 4,508
error_tracking ██████████████████░░ 90.1% 9,761 / 10,832
early_access_features ██████████████████░░ 90.1% 1,031 / 1,144
streamlit_apps ██████████████████░░ 90.4% 2,501 / 2,767
slack_app ██████████████████░░ 90.6% 8,989 / 9,926
links ██████████████████░░ 90.6% 183 / 202
marketing_analytics ██████████████████░░ 90.8% 11,514 / 12,684
stamphog ██████████████████░░ 91.0% 3,993 / 4,387
product_analytics ██████████████████░░ 91.3% 5,786 / 6,337
mcp_store ██████████████████░░ 91.9% 4,257 / 4,634
data_warehouse ███████████████████░ 92.6% 19,145 / 20,683
ai_observability ███████████████████░ 92.7% 14,758 / 15,926
notifications ███████████████████░ 92.7% 1,031 / 1,112
web_analytics ███████████████████░ 92.9% 13,853 / 14,913
workflows ███████████████████░ 92.9% 5,648 / 6,077
surveys ███████████████████░ 93.0% 5,724 / 6,157
posthog_ai ███████████████████░ 93.2% 1,325 / 1,421
approvals ███████████████████░ 93.3% 3,395 / 3,640
reminders ███████████████████░ 93.4% 468 / 501
managed_migrations ███████████████████░ 93.8% 1,220 / 1,300
tracing ███████████████████░ 93.9% 2,635 / 2,805
legal_documents ███████████████████░ 94.1% 1,568 / 1,667
endpoints ███████████████████░ 94.1% 8,606 / 9,143
messaging ███████████████████░ 94.3% 2,682 / 2,845
revenue_analytics ███████████████████░ 94.5% 3,598 / 3,809
skills ███████████████████░ 94.5% 2,893 / 3,061
review_hog ███████████████████░ 94.6% 6,808 / 7,193
logs ███████████████████░ 95.4% 9,935 / 10,416
experiments ███████████████████░ 95.7% 24,469 / 25,569
replay_vision ███████████████████░ 95.8% 13,874 / 14,481
growth ███████████████████░ 95.8% 2,837 / 2,960
annotations ███████████████████░ 96.2% 732 / 761
feature_flags ███████████████████░ 96.3% 16,311 / 16,940
user_interviews ███████████████████░ 96.5% 2,639 / 2,735
warehouse_sources ███████████████████░ 96.7% 291,442 / 301,346
access_control ███████████████████░ 96.8% 851 / 879
customer_analytics ███████████████████░ 97.3% 7,780 / 7,999
data_catalog ███████████████████░ 97.5% 2,346 / 2,407
analytics_platform ████████████████████ 98.0% 2,102 / 2,145
metrics ████████████████████ 98.2% 2,491 / 2,536
pulse ████████████████████ 98.4% 2,017 / 2,049
live_debugger ████████████████████ 99.2% 613 / 618
field_notes ████████████████████ 99.4% 158 / 159

Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.

Base automatically changed from posthog-code/hogql-seam1-matcol-in-bundle to master July 21, 2026 15:51
…ile pipeline

Generated-By: PostHog Code
Task-Id: e47d9107-4fc1-40d6-8ec6-6cd1be8ea6b0
Generated-By: PostHog Code
Task-Id: e47d9107-4fc1-40d6-8ec6-6cd1be8ea6b0
… printer import

Generated-By: PostHog Code
Task-Id: e47d9107-4fc1-40d6-8ec6-6cd1be8ea6b0
@georgemunyoro
georgemunyoro force-pushed the posthog-code/hogql-seam1-printer-boundary-sweep branch from 345023a to 3e1c58e Compare July 21, 2026 15:59
Copilot AI review requested due to automatic review settings July 21, 2026 15:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes the “Seam 1” work to make the HogQL printer layer importable without requiring django.setup() by deferring the last remaining Django-coupled imports in printer/utils.py to the existing “prepare” boundary, and strengthens the Django-free guard test with an end-to-end compile in a bare interpreter.

Changes:

  • Deferred Django-side loads in prepare_ast_for_printing (restricted property access control + events retention floor), and deferred clickhouse_property_resolution to break the import cycle and keep the printer package importable without Django.
  • Ensured access-control cache invalidation signal receivers are always connected via AppConfig.ready() (no longer relying on incidental imports).
  • Expanded test_no_django_imports to validate a full parse → resolve → prepare → print pipeline in a subprocess with Django settings unset, stubbing only declared loader boundaries.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
products/access_control/backend/apps.py Hooks access-control receiver registration into AppConfig.ready() so cache invalidation continues to work even when printer imports are deferred.
posthog/temporal/messaging/test/test_hogql_compile.py Updates patch target to the defining module, since the printer now imports the function at call time.
posthog/hogql/test/test_no_django_imports.py Extends the Django-free guard to cover cold-start import order and a full end-to-end compile in a bare interpreter.
posthog/hogql/printer/utils.py Moves remaining module-level Django imports (and the cpr import) behind the prepare boundary to keep printer imports Django-free.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@georgemunyoro
georgemunyoro merged commit 0f183c1 into master Jul 21, 2026
250 checks passed
@georgemunyoro
georgemunyoro deleted the posthog-code/hogql-seam1-printer-boundary-sweep branch July 21, 2026 17:03
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-21 17:45 UTC Run
prod-us ✅ Deployed 2026-07-21 18:05 UTC Run
prod-eu ✅ Deployed 2026-07-21 18:09 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants