mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
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.
692 lines
28 KiB
TOML
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"
|