Files
insights/pyproject.toml
zeekay a096ea87c1 Own the error envelope instead of depending on a name we will not spell
The API's error shape came from a third-party package whose module name is
`exceptions_hog`. A rename removed the word and broke the import — and the
import was inside the ERROR HANDLER, so every 4xx came back as an opaque 500
with a traceback, and the 401 branch that advertises OAuth metadata per RFC 9728
had never once run.

Renaming the import back would fix the crash and reintroduce the word. A
distribution's module name is not ours to spell, so the dependency is gone
instead: `insights/envelope.py` is the envelope, drf-exceptions-hog is dropped
from pyproject.toml, and the EXCEPTIONS_HOG settings block with it. It is a page
of code — less than the package, because the package supported a `multiple`
envelope behind a setting we never enabled, and only ever reported the first
failure for us.

Behaviour is unchanged, checked rather than asserted: 14 exception shapes run
through both implementations produce byte-identical bodies, status codes and
Retry-After headers — including nested validation (form__password), the
__all__ / NON_FIELD_ERRORS_KEY case that must report attr:null, and the two
deliberate oddities (`invalid` reported as `invalid_input`; detail read only off
`detail`, never off a bare exception's message, which can carry request values).

Nothing in the new module runs at import time. `insights.exceptions` is imported
during bootstrap, before the app registry is ready, so a module-level gettext or
a touch of DRF's api_settings resolves settings too early and takes the process
down with "AUTH_USER_MODEL refers to model 'insights.User' that has not been
installed". Translations are lazy and DRF settings are read inside the functions
that need them.
2026-08-13 12:06:09 -07:00

692 lines
28 KiB
TOML

[project]
name = "insights"
version = "0.0.0"
requires-python = "==3.13.13" # Pin full version to control Python upgrades explicitly
# Prefer ~= (compat) over >= (floor). Only pin exact versions (==) when necessary.
#
# ~= signals to others (security, devex, dependabot) that deps can be updated within
# that range without pulling the team in. Unbounded >= gives no such signal.
#
dependencies = [
"aioboto3==15.4.0",
"aiohttp==3.13.5",
"aiokafka~=0.12.0",
"anthropic==0.80.0",
"asyncstdlib==3.13.1",
"beautifulsoup4==4.12.3",
"boto3==1.40.49",
"brotli==1.2.0",
"celery==5.6.3",
"celery-redbeat==2.3.3",
"certifi==2026.1.4",
"datastore-connect",
"datastore-driver",
"datastore-pool",
"circular-dict==1.9",
"cryptography==46.0.7",
"dagster==1.10.18",
"dagster-cloud==1.10.18",
"dagster-aws==0.26.18",
"dagster-celery==0.26.18",
"dagster-docker==0.26.18",
"dagster-k8s==0.26.18",
"dagster-pipes==1.10.18",
"dagster-postgres==0.26.18",
"dagster-slack==0.26.18",
"dagster-webserver==1.10.18",
"deltalake[pyarrow]==1.6.1",
"dj-database-url~=3.0.0",
"django~=5.2.0",
"django-axes[ipware]==8.0.0",
"django-cors-headers~=4.8.0",
"django-deprecate-fields==0.1.1",
"django-extensions~=4.1.0",
"django-filter~=25.1",
"django-loginas==0.3.14",
"django-prometheus~=2.4.1",
"django-redis==6.0.0",
"django-structlog[celery]~=9.1.0",
"django-two-factor-auth~=1.18.0",
"djangorestframework~=3.16.1",
"djangorestframework-csv~=3.0.2",
"djangorestframework-dataclasses~=1.4.0",
"dlt==1.18.2",
"dnspython==2.6.1",
"drf-extensions~=0.8.0",
"drf-spectacular~=0.29.0",
"geoip2==4.6.0",
"google-ads==31.2.0",
"google-cloud-bigquery==3.26",
"google-cloud-bigquery-storage==2.32.0",
"google-cloud-iam==2.21",
"google-genai==1.46.0",
"granian[uvloop,reload,pname]==2.7.6",
"grimp~=3.14",
"grpcio~=1.71.0",
"gspread==6.2.1",
"insightsql-parser",
"insightsql-parser-rs", # built from rust/insightsql/parser
"humanize~=4.12.3",
"datastore-orm",
"jsonref==1.1.0",
"kafka-python==2.3.0",
"kombu==5.6.2",
"langchain~=0.3.27",
"langchain-community~=0.3.31",
"langchain-core~=0.3.81",
"langchain-openai~=0.3.35",
"langchain-anthropic~=0.3.22",
"langgraph==0.4.10",
"langgraph-checkpoint==4.1.1",
"lxml==6.0.2",
"lzstring==1.0.4",
"markdown-it-py~=3.0.0",
"mcp~=1.26.0",
"mimesis==5.2.1",
"mistralai==1.6.0",
"more-itertools==9.0.0",
"nanoid==2.0.0",
"natsort==8.4.0",
"nh3==0.3.5",
"numpy~=2.1.0",
"openai==1.109.1",
"openpyxl==3.1.5",
"orjson==3.11.9",
"ormsgpack~=1.12",
"pandas~=2.2.2",
"paramiko~=3.5.0",
"pillow==12.2.0",
"protobuf~=5.29.6",
"hanzo_insights",
"polars==1.37.1",
"psycopg2-binary==2.9.10",
"psycopg[binary]==3.2.4",
"pyarrow==23.0.1",
"pydantic~=2.12.0",
"pypdf==6.10.2",
"pyod==2.0.7",
"pyjwt~=2.12.1",
"pymssql==2.3.5",
"pymysql==1.1.1",
"pymongo==4.13.2",
"pyroscope-io~=0.8.8",
"croniter~=6.0.0",
"python-dateutil~=2.9.0",
"python-docx==1.2.0",
"python-snappy~=0.7.3",
"python3-saml==1.16.0",
"pytz==2023.3",
"rapidfuzz==3.14.5",
"redis==5.3.1",
"requests~=2.33.0",
"retry==0.9.2",
"s3fs==2025.9.0",
"scikit-learn~=1.7.0",
"semantic-version==2.8.5",
"simple-salesforce~=1.12.9",
"slack-sdk==3.42.0",
"snowflake-connector-python==4.3.0",
"social-auth-app-django~=5.8.0",
"social-auth-core~=4.8.7",
"django-scim2==0.19.0",
"sqlparse==0.5.4",
"sshtunnel==0.4.0",
"stripe==12.2.0",
"structlog==25.5.0",
"temporalio==1.24.0",
"tenacity==9.1.4",
"tiktoken==0.9.*",
"tldextract~=5.1",
"trafilatura==2.0.0",
"types-cachetools==5.5.0.20240820",
"css-inline==0.18.0",
"click~=8.0",
"umap-learn~=0.5.0",
"tbb==2022.3.1; sys_platform == 'linux' and platform_machine == 'x86_64'",
"whitenoise~=6.10.0",
"xgboost==3.2.0",
"xmlsec==1.3.17",
"zstd==1.5.7.2",
"pyyaml~=6.0.2",
"chdb==3.3.0",
"elevenlabs~=2.1.0",
"django-oauth-toolkit~=3.2.0",
"azure-ai-projects~=1.0.0b11",
"azure-ai-inference~=1.0.0b9",
"azure-storage-blob~=12.27.0",
"opentelemetry-sdk==1.33.1",
"opentelemetry-instrumentation-aiokafka==0.54b1",
"opentelemetry-instrumentation-aiohttp-client==0.54b1",
"opentelemetry-instrumentation-asgi==0.54b1",
"opentelemetry-instrumentation-celery==0.54b1",
"opentelemetry-instrumentation-django==0.54b1",
"opentelemetry-instrumentation-kafka-python==0.54b1",
"opentelemetry-instrumentation-psycopg==0.54b1",
"opentelemetry-instrumentation-redis==0.54b1",
"opentelemetry-instrumentation-requests==0.54b1",
"opentelemetry-exporter-otlp-proto-grpc==1.33.1",
"asgiref==3.11.0",
"user-agents~=2.2.0",
"django-admin-inline-paginator===0.4.0",
"fastavro~=1.12.0",
"pydantic-avro~=0.9.0",
"playwright~=1.60.0",
"linkedin-api-client~=0.3.0",
"glom~=24.11.0",
"modal~=1.5.0",
"databricks-sql-connector~=4.3.0",
"databricks-sdk~=0.118.0",
"free-email-domains~=1.0.1",
"disposable-email-domains~=0.0.140",
"pymediainfo~=7.0.1",
"cachetools~=5.5.2",
"gitpython~=3.1.44",
"duckdb~=1.5.2",
"bingads~=13.0.26",
"markdown-to-mrkdwn~=0.2.0",
"webauthn>=2.1.0,<2.3.0",
"botocore[crt]~=1.40.49",
"defusedxml~=0.7.1",
"confluent-kafka~=2.13",
"blake3>=1.0.8,<2",
"pixelhog~=1.2.0",
"google-re2>=1.1.20251105,<2",
"scrubadub~=2.0.1",
"braintrust-core>=0.0.59,<1",
"wrapt==1.17.3",
"limits==5.8.0",
"deltalite==0.1.3",
]
[dependency-groups]
dev = [
"aioresponses==0.7.6",
"autoevals==0.2.0",
"boto3-stubs[s3]~=1.38.30",
"browser-cookie3~=0.20",
"braintrust==0.2.4",
"braintrust-langchain==0.0.4",
"dagster-dg-cli~=1.10.18",
"datamodel-code-generator~=0.36.0",
"debugpy~=1.8.0",
"import-linter~=2.11",
"deepdiff~=8.6.1",
"django-linear-migrations~=2.17.0",
"django-stubs~=5.2.0",
"djangorestframework-stubs~=3.16.0",
"faker==17.5.0",
"fakeredis[lua]==2.23.3",
"hypothesis~=6.151.9",
"pytest-rerunfailures~=16.1",
"freezegun==1.5.5",
"grpcio-tools~=1.71.0",
"protoletariat~=3.3.10",
"ipython~=9.3.0",
"mypy~=2.1.0",
"mypy-extensions~=1.1.0",
"ty==0.0.29",
# openapi-spec-validator needed for prance as a validation backend
"openapi-spec-validator==0.7.1",
"orjson==3.11.9",
"packaging==24.1",
"parameterized==0.9.0",
"prance==23.6.21.0",
"pyarrow==23.0.1",
"pyarrow-stubs==20.0.0.20260625",
"pytest~=8.4.2",
"pytest-asyncio~=1.3.0",
"pytest-aiohttp~=1.1.0",
"pytest-cov~=7.0.0",
"pytest-django~=4.11.1",
"pytest-durations~=1.6.2",
"pytest-env~=1.2.0",
"pytest-icdiff~=0.9",
"pytest-mock~=3.15.1",
"pytest-split~=0.11.0", # EXPERIMENT: git-pinned below to the optimal_chunks fork
"pytest-timeout~=2.4.0",
"pytest-xdist~=3.8.0",
"python-dateutil~=2.9.0",
"requests-mock~=1.12.1",
"responses==0.23.1",
"ruff~=0.15.20",
"stpyv8==13.1.201.22; sys_platform != 'linux' or (platform_machine != 'aarch64' and platform_machine != 'arm64')",
"syrupy~=4.9.1",
"tach~=0.34",
"types-aioboto3[s3]~=14.3.0",
"types-aiobotocore~=2.22.0",
"types-boto3[essential,ses,sesv2]==1.40.49",
"types-croniter~=6.0.0",
"types-markdown==3.3.9",
"types-paramiko==3.4.0.20240423",
"types-pymysql==1.1.0.20240524",
"types-python-dateutil~=2.9.0",
"types-pytz==2023.3",
"types-pyyaml==6.0.1",
"types-redis==4.6.0.20241004",
# >= 2.31.0.7 versions of types-requests require urllib>=2, which is incompatible with our dependencies
"types-requests==2.31.0.6",
"types-retry==0.9.9.4",
"types-tzlocal~=5.1.0.1",
"pywatchman~=3.0",
"watchdog==5.0.3",
"testcontainers[mysql]~=4.14",
# Developer CLI
"insightscli",
# Distributed owners.yaml resolver — installed so insightscli's owners:* commands and
# the pr-approval gate can `import insights_owners` from the main venv.
"insights-owners",
"libcst>=1.8.6,<2.0.0",
]
sentiment = [
"optimum[onnxruntime]~=1.25.0",
"torch~=2.8.0",
]
insightsql-parser-parity = [
"shrinkray==26.4.14.0",
]
[tool.uv]
required-version = ">=0.10.2"
add-bounds = "major"
exclude-newer = "7 days"
exclude-newer-package = { insightsql-parser = false, insightsql-parser-rs = false, deltalite = false, hanzo_insights = false, django = false, modal = false, google-ads = false }
[tool.uv.workspace]
members = ["tools/insightscli", "tools/owners"]
[tool.uv.sources]
insightscli = { workspace = true }
insights-owners = { workspace = true }
# github.com/hanzo-ds/orm answers 404 to anyone without a GitHub credential, and the
# image build carries none — it mounts FORGE_TOKEN, for git.hanzo.ai. So this address
# could only ever resolve from a warm uv cache, and the first build on a cold one died
# on "could not read Username for 'https://github.com'". The forge carries the same
# commit, cloneable without a credential at all. Its three siblings below are still
# reachable on github.com and stay where they are until they are not.
datastore-orm = { git = "https://git.hanzo.ai/hanzoai/datastore-orm", rev = "56c46e4bc6a424fd68b12df2afbacea4090b2dbb" }
datastore-driver = { git = "https://github.com/hanzo-ds/driver", rev = "119df3f" }
datastore-connect = { git = "https://github.com/hanzo-ds/connect", rev = "ccbd4b5" }
datastore-pool = { git = "https://github.com/hanzo-ds/pool", rev = "7260d56" }
insightsql-parser = { path = "common/insightsql_parser" }
insightsql-parser-rs = { path = "rust/insightsql/parser" }
# The forge is where this SDK lives. The github.com/hanzoai mirror is archived and
# therefore frozen a commit behind, and uv resolves a github.com URL through the
# GitHub API rather than git — so a url.insteadOf redirect cannot reach past it and
# the build would silently keep resolving the stale commit. Naming the forge
# directly is the only address that answers with the code that exists. The forge
# needs a credential: the image build mounts FORGE_TOKEN (see Dockerfile), the same
# way every other Hanzo image consumes our own packages.
hanzo_insights = { git = "https://git.hanzo.ai/hanzoai/insights-python", branch = "main" }
# pytest-split: pinned to Insights's fork (immutable tag) for the order-preserving
# optimal_chunks split. Fork: Insights/pytest-split (of jerry-git/pytest-split).
# To pull upstream fixes: rebase the fork's feat branch onto upstream, cut the next
# v0.11.0-insights.N tag, and bump this pin.
pytest-split = { git = "https://github.com/PostHog/pytest-split", tag = "v0.11.0-posthog.6" }
[tool.isort]
profile = "black"
[tool.ruff]
line-length = 120
force-exclude = true
exclude = [
".git",
"./.semgrep",
"./nodejs/node_modules/",
"./plugins/node_modules/",
"./env",
"./insights/insightsql/grammar",
"products/*/skills/*/scripts",
# imported desktop app; Biome-linted by its own desktop-* CI
"products/desktop",
]
[tool.ruff.lint]
ignore = [
"B017", # ~38: assertRaises(Exception) too broad
"B904", # ~488: raise without from in except
"B905", # ~153: zip() without strict=
"C901", # ~527: function too complex
"E501", # ~4697: line too long
"E722", # ~62: bare except
"E731", # ~185: lambda assignment
"F541", # ~1139: f-string missing placeholders
"UP007", # ~377: Union[X,Y] → X | Y (auto-fixable)
"UP032", # ~175: use f-string (auto-fixable)
"UP045", # ~3064: Optional[X] → X | None (auto-fixable)
"UP046", # ~39: generic class → PEP 695 (unsafe)
"UP047", # ~37: generic func → PEP 695 (unsafe)
"UP042", # ~28: str+Enum → StrEnum (unsafe: changes str()/format() output)
]
select = [
"B",
"C4",
"C9",
"DJ012",
"E",
"F",
"I",
"PLC0415",
"RUF005",
"RUF013",
"RUF015",
"RUF019",
"T100",
"T2",
"TID253",
"TRY201",
"TRY400",
"UP",
"W",
]
[tool.ruff.lint.per-file-ignores]
# protoletariat generates .pyi stubs with re-exports (e.g. `from . import person_pb2`)
# that type checkers need to resolve the package structure.
# protobuf 5.x _pb2.py files have side-effect-only imports that load dependency
# descriptors into the pool before AddSerializedFile() — ruff must not strip them.
"insights/personinsights_client/proto/generated/**/*.pyi" = ["F401"]
"insights/personinsights_client/proto/generated/**/*_pb2.py" = ["F401", "E402"]
# deltalite's test suite bootstraps its harness/planner via sys.path (the package is a
# maturin-built extension living under rust/, not part of the Django app), so imports
# are necessarily lazy / post-path-insert (PLC0415, E402); the planner's small CLI
# prints its report (T201).
"rust/deltalite/python/**" = ["PLC0415", "E402", "T201"]
# Django settings aggregator: star imports are how settings modules merge into one namespace.
# Not portable to explicit re-exports without losing the settings-loading semantics.
"insights/settings/**" = ["F403", "F405"]
# PLC0415 exemptions. products/ and services/llm-gateway/ have their own ruff configs.
# Run `ruff check <path> --select PLC0415 --statistics` before removing an entry.
".github/**" = ["PLC0415"]
"bin/**" = ["PLC0415"]
"common/**" = ["PLC0415"]
"ee/**" = ["PLC0415"]
"manage.py" = ["PLC0415"]
"insights/**" = ["PLC0415"]
"scripts/**" = ["PLC0415"]
"services/stripe-mock/**" = ["PLC0415"]
"tools/**" = ["PLC0415"]
[tool.ruff.lint.flake8-tidy-imports]
# Heavy SDKs that noticeably slow startup. They must be imported lazily (inside the
# function that uses them), not at module top, so they stay off the Django app-load path.
# Only modules with a single, already-lazy call site are listed — vendor source/destination
# leaf modules legitimately import their own SDK at module level and are loaded on demand.
banned-module-level-imports = ["elevenlabs", "matplotlib", "trafilatura"]
[tool.ruff.lint.mccabe]
max-complexity = 10
[tool.ruff.lint.isort]
combine-as-imports = true
force-wrap-aliases = true
length-sort-straight = true
split-on-trailing-comma = false
section-order = [
"future",
"standard-library",
"testing",
"django",
"third-party",
"insights.schema",
"insights.insightsql",
"insights",
"products",
"first-party",
"local-folder",
]
[tool.ruff.lint.isort.sections]
"django" = ["django"] # Group all Django imports into a separate section.
"testing" = [ # Group all testing imports into a separate section.
"freezegun",
"pytest",
"unittest",
"insights.test.base",
]
# Separate groups for our own stuff
"insights.schema" = ["insights.schema"]
"insights.insightsql" = ["insights.insightsql"]
"insights" = ["insights"]
"products" = ["products"]
[tool.dagster]
python_version = "3.13"
[tool.mypy]
python_version = "3.13"
plugins = [
"mypy_django_plugin.main",
"mypy_drf_plugin.main",
"pydantic.mypy",
]
strict_optional = true
no_implicit_optional = true
warn_unused_ignores = true
check_untyped_defs = true
warn_unreachable = true
strict_equality = true
ignore_missing_imports = true
exclude_gitignore = true
# conftest.py: the root pytest bootstrap collides with services/stripe-mock/tests/conftest.py
# (both resolve to top-level module "conftest")
# rust/deltalite/python: pytest suite for the maturin-built extension; nothing Django
# imports it and it has its own CI (ci-deltalite-python.yml).
exclude = ["^(bin/.*|tools/.*|rust/deltalite/python/.*|products/desktop/.*|products/[^/]+/skills/[^/]+/scripts/.*|gunicorn\\.config\\.py|manage\\.py|conftest\\.py)$"]
[[tool.mypy.overrides]]
module = "bin.*"
ignore_errors = true
[[tool.mypy.overrides]]
module = "insights.insightsql.grammar.*"
ignore_errors = true
[[tool.mypy.overrides]]
module = "insights.tasks.*"
disallow_untyped_defs = true
check_untyped_defs = true
[tool.django-stubs]
django_settings_module = "insights.settings"
[tool.pyright]
venvPath = ".flox/cache"
venv = "venv"
pythonVersion = "3.13"
[tool.ty.environment]
python-version = "3.13"
[tool.ty.src]
exclude = [
"bin/**",
"manage.py",
"insights/insightsql/grammar/**",
"insights/personinsights_client/proto/generated/**",
"insights/schema.py",
"insights/schema_enums.py",
"insights/temporal/proxy_service/proto/**",
"products/warehouse_sources/backend/temporal/data_imports/sources/generated_configs/**",
"rust/deltalite/python/**",
]
# ---------------------------------------------------------------------------
# import-linter: enforce internal product architecture
# ---------------------------------------------------------------------------
# Ensures products follow the facade pattern internally:
# presentation → facade → logic/models (not shortcuts).
# Complements tach (inter-module boundaries) with intra-product enforcement.
[tool.importlinter]
root_packages = ["products"]
# Presentation must only reach product internals through the facade.
# Forbid all backend imports, then allowlist facade + self-imports.
# This catches any new internal modules (cache.py, helpers.py, etc.)
# automatically — no blocklist to maintain.
[[tool.importlinter.contracts]]
name = "presentation must use facade"
type = "forbidden"
source_modules = ["products.*.backend.presentation"]
forbidden_modules = [
"products.*.backend",
]
allow_indirect_imports = true
ignore_imports = [
# Allowed: presentation can import facade and itself
"products.**.backend.presentation.** -> products.**.backend.facade.**",
"products.**.backend.presentation.** -> products.**.backend.presentation.**",
# TODO: existing violations — fix by routing through facade
"products.mcp_analytics.backend.presentation.views -> products.mcp_analytics.backend.logic",
"products.mcp_analytics.backend.presentation.views -> products.mcp_analytics.backend.models",
"products.mcp_analytics.backend.presentation.serializers -> products.mcp_analytics.backend.models",
"products.mcp_store.backend.presentation.agent_views -> products.mcp_store.backend.agents",
"products.mcp_store.backend.presentation.agent_views -> products.mcp_store.backend.gateway",
"products.mcp_store.backend.presentation.agent_views -> products.mcp_store.backend.models",
"products.mcp_store.backend.presentation.agent_views -> products.mcp_store.backend.policy",
"products.mcp_store.backend.presentation.agent_views -> products.mcp_store.backend.proxy",
"products.mcp_store.backend.presentation.gateway_views -> products.mcp_store.backend.agents",
"products.mcp_store.backend.presentation.gateway_views -> products.mcp_store.backend.gateway",
"products.mcp_store.backend.presentation.gateway_views -> products.mcp_store.backend.models",
"products.mcp_store.backend.presentation.gateway_views -> products.mcp_store.backend.policy",
"products.mcp_store.backend.presentation.views -> products.mcp_store.backend.agents",
"products.mcp_store.backend.presentation.views -> products.mcp_store.backend.gateway",
"products.mcp_store.backend.presentation.views -> products.mcp_store.backend.models",
"products.mcp_store.backend.presentation.views -> products.mcp_store.backend.oauth",
"products.mcp_store.backend.presentation.views -> products.mcp_store.backend.policy",
"products.canvas.backend.presentation.serializers -> products.canvas.backend.artifacts",
"products.canvas.backend.presentation.serializers -> products.canvas.backend.contract",
"products.canvas.backend.presentation.serializers -> products.canvas.backend.models",
"products.canvas.backend.presentation.views -> products.canvas.backend.build_service",
"products.canvas.backend.presentation.views -> products.canvas.backend.models",
"products.canvas.backend.presentation.views -> products.canvas.backend.source",
"products.mcp_store.backend.presentation.views -> products.mcp_store.backend.proxy",
"products.mcp_store.backend.presentation.views -> products.mcp_store.backend.tasks",
"products.mcp_store.backend.presentation.views -> products.mcp_store.backend.tools",
"products.notifications.backend.presentation.views -> products.notifications.backend.cache",
"products.notifications.backend.presentation.views -> products.notifications.backend.models",
"products.tracing.backend.presentation.views -> products.tracing.backend.logic",
"products.tracing.backend.presentation.views -> products.tracing.backend.sparkline_query_runner",
"products.tracing.backend.presentation.views -> products.tracing.backend.has_spans_query_runner",
# Saved-views ModelViewSet needs the model class for its serializer; same accepted pattern as
# logs' views_api. TODO: route through facade with the broader presentation cleanup.
"products.tracing.backend.presentation.views_api -> products.tracing.backend.models",
"products.user_interviews.backend.presentation.views -> products.user_interviews.backend.models",
"products.user_interviews.backend.presentation.views -> products.user_interviews.backend.invite_email",
"products.user_interviews.backend.presentation.webhooks -> products.user_interviews.backend.models",
# logs presentation isolation (temporary): the moved viewsets still reach in-product
# internals directly. Removed by the follow-up that thins each viewset to go through
# the facade.
"products.logs.backend.presentation.views.api -> products.logs.backend.column_expressions",
"products.logs.backend.presentation.views.api -> products.logs.backend.count_query_runner",
"products.logs.backend.presentation.views.api -> products.logs.backend.count_ranges_query_runner",
"products.logs.backend.presentation.views.api -> products.logs.backend.group_by_query_runner",
"products.logs.backend.presentation.views.api -> products.logs.backend.has_logs_query_runner",
"products.logs.backend.presentation.views.api -> products.logs.backend.log_attributes_query_runner",
"products.logs.backend.presentation.views.api -> products.logs.backend.log_facet_values_query_runner",
"products.logs.backend.presentation.views.api -> products.logs.backend.log_values_query_runner",
"products.logs.backend.presentation.views.api -> products.logs.backend.logs_query_runner",
"products.logs.backend.presentation.views.api -> products.logs.backend.pattern_diff",
"products.logs.backend.presentation.views.api -> products.logs.backend.patterns_query_runner",
"products.logs.backend.presentation.views.api -> products.logs.backend.services_query_runner",
"products.logs.backend.presentation.views.api -> products.logs.backend.sparkline_query_runner",
"products.logs.backend.presentation.views.alerts_api -> products.logs.backend.alert_check_query",
"products.logs.backend.presentation.views.alerts_api -> products.logs.backend.alert_destinations",
"products.logs.backend.presentation.views.alerts_api -> products.logs.backend.alert_state_machine",
"products.logs.backend.presentation.views.alerts_api -> products.logs.backend.models",
"products.logs.backend.presentation.views.metric_rules_api -> products.logs.backend.models",
"products.logs.backend.presentation.views.retention_api -> products.logs.backend.models",
"products.logs.backend.presentation.views.sampling_api -> products.logs.backend.models",
"products.logs.backend.presentation.views.views_api -> products.logs.backend.models",
# logs presentation isolation (temporary): cross-product imports the moved viewsets
# still make directly. These resolve once cdp/exports expose the needed facades, so
# they outlive the in-product cleanup above.
"products.logs.backend.presentation.views.api -> products.exports.backend.models.exported_asset",
"products.logs.backend.presentation.views.alerts_api -> products.cdp.backend.models.insights_functions.insights_function",
# TODO: notebooks presentation wave — the moved NotebookViewSet still reaches in-product
# internals (collab, kernel runtime, query validation, models) and the tasks sandbox
# directly. Thin it to parse -> facade -> serialize and delete each entry.
# backend:contract-check stays off until this list is empty.
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.activity_logging",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.analytics",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.collab",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.collab_stream",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.kernel_runtime",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.markdown_collab",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.models",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.presence",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.python_analysis",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.query_validation",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.sql_v2",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.sql_v2_direct",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.sql_v2_references",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.sql_v2_runs",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.sql_v2_serializers",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.sql_v2_state",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.temporal.client",
"products.notebooks.backend.presentation.views.notebook -> products.notebooks.backend.temporal.sql_v2",
# TODO: data_warehouse presentation wave — the structural move landed the viewsets under
# presentation/views/ but they still reach in-product internals and sibling products
# (warehouse_sources, data_modeling, data_tools, cdp, revenue_analytics, batch_exports)
# directly. Thin each viewset to parse -> facade -> serialize and delete each entry.
# backend:contract-check stays off until this list is empty.
]
[tool.ty.rules]
# Import resolution - ty can't always find all modules/stubs
unresolved-import = "ignore"
# Django-related rules - ty doesn't support Django's metaprogramming yet
# See: https://github.com/astral-sh/ty/issues/291
unresolved-attribute = "ignore" # Django model fields, relationships, auto-generated attributes
invalid-argument-type = "ignore" # Django querysets, managers, form fields
unknown-argument = "ignore" # Django kwargs, queryset filters, manager methods
invalid-base = "ignore" # Django model metaclasses (e.g., models.Model)
missing-argument = "ignore" # Django auto-generated __init__, managers
too-many-positional-arguments = "ignore" # Django metaprogramming signatures
# Dual mypy+ty — mypy still runs, so its artifacts are legitimate
redundant-cast = "ignore" # casts needed by mypy that ty resolves
unused-type-ignore-comment = "ignore" # type: ignore comments required by mypy
# Django lazy imports and settings indirection
possibly-missing-submodule = "ignore"
# DRF serializer pattern — validate/update/create overrides are idiomatic
invalid-method-override = "ignore"
# Conditional base classes (e.g. EnterpriseEventQuery | EventQuery pattern)
unsupported-base = "ignore"
# Protobuf generated code + Django lazy references
unresolved-reference = "ignore"
# Third-party library overload stubs (Anthropic SDK, Django, re, etc.)
no-matching-overload = "ignore"
# Framework metaclass magic — ty can't resolve dynamic callables
call-top-callable = "ignore"
call-non-callable = "ignore"
# Django querysets typed as `object` — ty can't narrow through managers
not-iterable = "ignore"
# `in` operator on Django/framework `object` types
unsupported-operator = "ignore"
# Subscript on `object`/`None` from unresolved Django types
not-subscriptable = "ignore"
# mypy already catches deprecations; ty is a fast preflight, not the authority
deprecated = "ignore"
# ty disagrees with mypy on some existing code — baselined, not suppressed.
# These rules catch real bugs in new code, so they stay enabled.
# invalid-type-form is the exception: generated code + ty limitations on valid patterns.
invalid-type-form = "ignore"