[project] name = "polar" version = "0.0.0" readme = "README.md" # 3.14 floor: the Vercel build image ships an earlier 3.14 patch. requires-python = ">=3.14" dependencies = [ "certifi>=2026.10.19", "fastapi==0.157.0", "uvicorn[standard]>=0.49.0", "asyncpg>=0.29.0", "alembic>=1.18.5", "sqlalchemy[asyncio]>=2.1.6", "greenlet>=3.5.5", "structlog>=26.1.0", "githubkit==0.16.1", "redis>=5.0.4", "sse-starlette>=3.4.5", "stripe>=14,<15", "pyjwt>=2.13.0", "pydantic>=2.13.4", "sentry-sdk[fastapi,sqlalchemy]>=2.64.0", "posthog>=7.21.1", "python-slugify>=8.0.2", "python-multipart>=0.0.32", "safe-redirect-url>=0.1.1", "httpx-oauth>=0.17.0", "httpx>=0.23.0", "pydantic-settings>=2.14.2", "email-validator>=2.1.0.post1", "python-dateutil>=2.9.0.post0", "pydantic-extra-types>=2.9.0", "sqlalchemy-utils>=0.41.2", "authlib>=1.7.2", "standardwebhooks>=1.0.0", "typer>=0.26.7", "logfire[fastapi,httpx,sqlalchemy,redis]>=5.2.0", "makefun>=1.15.6", "boto3>=1.43.38", "itsdangerous>=2.2.0", "cryptography>=48.0.1", "babel>=2.16.0", "pycountry>=24.6.1", "python-stdnum>=2.1", "ipinfo-db>=0.0.4", "taskipy>=1.10.3", "psycopg2-binary>=2.9.12", "apscheduler>=3.11.3", "plain-client>=0.0.3", "tagflow>=0.7.0", "exponent-server-sdk>=2.1.0", "dramatiq[redis,watch]>=1.17.1", "prometheus-client>=0.25.0", "python-snappy>=0.7.3", "fpdf2>=2.8.3", "arabic-reshaper>=3.0.1", "python-bidi>=0.6.11", "pydantic-ai-slim[openai,typesafe]>=2.2.0", "genai-prices>=0.1.13", "asgi-ratelimit>=0.10.0", "aiocsv>=1.4.1", "alembic-utils>=0.8.8", "clickhouse-connect[async]>=1.7.1", "trafilatura>=2.1.0", "firecrawl-py>=4.31.0", "polar-sdk==1.0.3a4", "anyio==4.15.1", # Potential culprit for Sep 23-24 memory leak "markdown-it-py>=4.2.0", "tzdata>=2026.2", "reauth==0.5.0", "langcodes>=3.5.1", "pypdf>=6.21.2", "google-auth[requests]>=2.38.0", "tld>=0.13.2", "joserfc>=1.7.4", "py-svg-hush>=0.3.0", "dnspython>=2.9.0", ] [dependency-groups] dev = [ "mypy>=2.1.0", "pytest<10", "pytest-sugar>=1.0.0", "coverage>=7.14.1", "pytest-mock>=3.10.0", "ruff>=0.15.16", "types-redis>=4.6.0.20240903", "respx>=0.23.1", "pytest-cov>=5.0.0", "boto3-stubs[s3,sqs,scheduler]>=1.43.26", "freezegun>=1.5.1", "fakeredis[lua]>=2.36.1", "pytest-xdist[psutil]>=3.8.0", "minio>=7.2.9", "locust>=2.44.1", "pydantic-evals>=1.103.0", "gepa>=0.1.0", # Quarantined due to supply chain attack: # * https://futuresearch.ai/blog/litellm-pypi-supply-chain-attack/ # * https://pypi.org/project/litellm/ # "litellm==1.82.3", "pytest-split>=0.11.0", "openapi-kit>=0.3.1", "httpx-ws>=0.9.0", "pytest-codspeed>=5.0.3", ] [tool.taskipy.tasks] emails = { cmd = "cd emails && pnpm i --frozen-lockfile && pnpm run build", help = "build emails" } backoffice = { cmd = "pnpm -C polar/backoffice install --frozen-lockfile && pnpm -C polar/backoffice build", help = "build backoffice CSS and JS" } api = { cmd = "env AUTHLIB_INSECURE_TRANSPORT=true uvicorn polar.app:app --reload --workers 1 --host 127.0.0.1 --port 8000", help = "run api service" } worker = { cmd = "dramatiq -p 1 -t 1 --watch polar -f polar.worker.scheduler:start polar.worker.run", help = "run worker" } mover = { cmd = "python -m polar.event.mover", help = "run staged events mover" } test = { cmd = "POLAR_ENV=testing python -m pytest --cov polar/ --cov-report=term-missing", help = "run all tests" } test_fast = { cmd = "POLAR_ENV=testing python -m pytest -n auto -p no:sugar --no-cov", help = "run all tests, but fast" } loadtest = { cmd = "locust -f load_tests/locustfile.py --host=http://127.0.0.1:8000", help = "run load tests (interactive)" } lint = { cmd = "ruff format . && ruff check --fix . && task lint_ast", help = "run linters with autofix" } lint_check = { cmd = "ruff format --check . && ruff check . && task lint_ast", help = "run ruff linter" } lint_ast = { cmd = "python -m scripts.linters", help = "run all custom AST lint rules" } lint_subquery = { cmd = "python -m scripts.linters --only subquery", help = "flag .subquery() calls that don't narrow columns" } lint_org_scope = { cmd = "python -m scripts.linters --only org-scope", help = "flag inline UserOrganization filters that bypass select_user_org_ids" } lint_types = { cmd = "mypy --num-workers 2 polar scripts tests benchmarks", help = "run mypy type verify" } db_migrate = { cmd = "python -m scripts.db upgrade", help = "run alembic upgrade" } db_recreate = { cmd = "python -m scripts.db recreate", help = "drop and recreate database" } db_reparent = { cmd = "python -m scripts.db reparent", help = "try to auto-fix conflicting migrations" } check_migrations = { cmd = "python -m scripts.check_migrations", help = "check migration heads, filenames, and staged column drops" } tb_deploy = { cmd = "python -m scripts.tinybird", help = "deploy tinybird datasources" } clean = { cmd = "find * -name '*.pyc' -delete && find * -name '__pycache__' -delete", help = "clean up .pyc and __pycache__" } verify_github_app = { cmd = "python -m polar.verify_github_app", help = "verify that the github app is correctly configured" } seeds_load = { cmd = "python -m scripts.seeds_load", help = "load sample/test data into the database" } enable_payments = { cmd = "python -m scripts.enable_payments", help = "enable payments for an organization by slug" } loadtest_setup = { cmd = "python -m scripts.loadtest_setup", help = "setup load test data for event ingestion" } pre_deploy = { cmd = "task db_migrate", help = "Pre-deploy command run by Render" } [tool.pytest.ini_options] markers = [ "auth", "websocket: Use the WebSocket transport for the test client", "api_version(*versions): Run the test for each APIVersion with its Polar-Version client header", "benchmark: performance benchmark, deselected by default (run with -m benchmark)", ] addopts = "-m 'not benchmark'" [tool.coverage.run] concurrency = ["thread", "greenlet"] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "if TYPE_CHECKING:", ] # See: https://github.com/nedbat/coveragepy/issues/831 [tool.ruff] target-version = "py314" [tool.ruff.lint] extend-select = [ "I", "UP", "T20", "PT", "RUF10", ] ignore = [ "F841", "PT012", "B008", "B009", "B010", "BLE001", "PLC0105", "PYI041", "RUF012", "RUF059", "SIM117", "TRY", "UP037", ] [tool.ruff.lint.per-file-ignores] "migrations/*" = [ "F401", # remove unused import ] "scripts/*" = ["T20"] "docker/*" = ["T20"] "tests/**/test_*_benchmark.py" = ["T20"] # benchmarks report their measurements [tool.mypy] ignore_missing_imports = true plugins = ["pydantic.mypy"] warn_redundant_casts = true warn_unused_ignores = true disallow_any_generics = true check_untyped_defs = true no_implicit_reexport = true strict_equality = true disallow_untyped_defs = true skip_cache_mtime_checks = true [tool.pydantic-mypy] init_forbid_extra = true init_typed = true warn_required_dynamic_aliases = false [tool.uv] exclude-newer = "1 week" exclude-newer-package = { reauth = false, polar-sdk = false, certifi = false, brotli = false } [tool.uv.sources] dramatiq = { git = "https://github.com/frankie567/dramatiq", rev = "memory-leak-asyncio-ter" } [tool.vercel] entrypoint = "polar.app:app"