[project]
name = "authentik"
version = "2026.5.0-rc1"
description = ""
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
requires-python = "==3.14.*"
dependencies = [
  "ak-guardian==3.2.0",
  "argon2-cffi==25.1.0",
  "cachetools==7.0.5",
  "channels==4.3.2",
  "cryptography==46.0.7",
  "dacite==1.9.2",
  "deepmerge==2.0",
  "defusedxml==0.7.1",
  "django-channels-postgres",
  "django-countries==8.2.0",
  "django-dramatiq-postgres",
  "django-filter==25.2",
  "django-model-utils==5.0.0",
  "django-pglock==1.8.0",
  "django-pgtrigger==4.17.0",
  "django-postgres-cache",
  "django-postgres-extra==2.0.9",
  "django-prometheus==2.4.1",
  "django-storages[s3]==1.14.6",
  "django-tenants==3.10.1",
  "django==5.2.13",
  "djangoql==0.19.1",
  "djangorestframework==3.17.1",
  "docker==7.1.0",
  "drf-orjson-renderer==1.8.0",
  "drf-spectacular==0.29.0",
  "dumb-init==1.2.5.post1",
  "duo-client==5.6.1",
  "fido2==2.2.0",
  "geoip2==5.2.0",
  "geopy==2.4.1",
  "google-api-python-client==2.194.0",
  "gssapi==1.11.1",
  "gunicorn==25.3.0",
  "jsonpatch==1.33",
  "jwcrypto==1.5.7",
  "kubernetes==35.0.0",
  "ldap3==2.9.1",
  "lxml==6.1.0",
  "msgraph-sdk==1.55.0",
  "opencontainers==0.0.15",
  "packaging==26.1",
  "paramiko==4.0.0",
  "psycopg[c,pool]==3.3.3",
  "pydantic-scim==0.0.8",
  "pydantic==2.13.2",
  "pyjwt==2.11.0",
  "pyrad==2.5.4",
  "python-kadmin-rs==0.7.0",
  "pyyaml==6.0.3",
  "requests-oauthlib==2.0.0",
  "scim2-filter-parser==0.7.0",
  "sentry-sdk==2.58.0",
  "service-identity==24.2.0",
  "setproctitle==1.3.7",
  "structlog==25.5.0",
  "swagger-spec-validator==3.0.4",
  "twilio==9.10.5",
  "ua-parser==1.0.2",
  "unidecode==1.4.0",
  "urllib3<3",
  "uvicorn[standard]==0.44.0",
  "watchdog==6.0.0",
  "webauthn==2.7.1",
  "wsproto==1.3.2",
  "xmlsec==1.3.17",
  "zxcvbn==4.5.0",
]

[dependency-groups]
dev = [
  "aws-cdk-lib==2.250.0",
  "bandit==1.9.4",
  "black==26.3.1",
  "bpython==0.26",
  "colorama==0.4.6",
  "constructs==10.6.0",
  "coverage[toml]==7.13.5",
  "daphne==4.2.1",
  "debugpy==1.8.20",
  "django-stubs[compatible-mypy]==6.0.3",
  "djangorestframework-stubs[compatible-mypy]==3.16.9",
  "drf-jsonschema-serializer==3.0.0",
  "freezegun==1.5.5",
  "importlib-metadata==8.7.1",
  "k5test==0.10.4",
  "lxml-stubs==0.5.1",
  "mypy==1.20.1",
  "pdoc==16.0.0",
  "pytest-django==4.12.0",
  "pytest-flakefinder==1.1.0",
  "pytest-github-actions-annotate-failures==0.4.0",
  "pytest-randomly==4.0.1",
  "pytest-timeout==2.4.0",
  "pytest==9.0.3",
  "requests-mock==1.12.1",
  "ruff==0.15.11",
  "selenium==4.43.0",
  "types-channels==4.3.0.20260408",
  "types-docker==7.1.0.20260409",
  "types-jwcrypto==1.5.7.20260409",
  "types-ldap3==2.9.13.20260408",
  "types-requests==2.33.0.20260408",
  "types-zxcvbn==4.5.0.20260408",
]

[tool.uv]
no-binary-package = [
  # This differs from the no-binary packages in the Dockerfile. This is due to the fact
  # that these packages are built from source for different reasons than cryptography and kadmin.
  # These packages are built from source to link against the libxml2 on the system which is
  # required for functionality and to stay up-to-date on both libraries.
  # The other packages specified in the dockerfile are compiled from source to link against the
  # correct FIPS OpenSSL libraries
  "lxml",
  "xmlsec",
]

[tool.uv.sources]
ak-guardian = { workspace = true }
django-channels-postgres = { workspace = true }
django-dramatiq-postgres = { workspace = true }
django-postgres-cache = { workspace = true }
opencontainers = { git = "https://github.com/vsoch/oci-python", rev = "ceb4fcc090851717a3069d78e85ceb1e86c2740c" }

[tool.uv.workspace]
members = [
  "packages/ak-guardian",
  "packages/django-channels-postgres",
  "packages/django-dramatiq-postgres",
  "packages/django-postgres-cache",
]

[project.scripts]
ak = "lifecycle.ak:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.bandit]
exclude_dirs = ["**/node_modules/**"]

[tool.black]
line-length = 100
target-version = ['py314']
exclude = 'node_modules'

[tool.ruff]
line-length = 100
target-version = "py314"
exclude = ["**/migrations/**", "**/node_modules/**"]

[tool.ruff.lint]
select = [
  "E",   # pycodestyle
  "F",   # Pyflakes
  "I",   # isort
  "UP",  # pyupgrade
  "B",   # flake8-bugbear
  "DJ",  # django
  "PL",  # pylint
  "BLE", # flake8-blind-except
]
ignore = [
  "DJ001",   # Avoid using `null=True` on string-based fields,
  "PLC0415", # `import` should be at the top-level of a file
]

[tool.ruff.lint.pylint]
max-args = 7
max-branches = 18
max-returns = 10

[tool.mypy]
plugins = ["mypy_django_plugin.main", "mypy_drf_plugin.main", "pydantic.mypy"]

[[tool.mypy.overrides]]
module = ["django_tenants.*", "dramatiq.*", "pglock.*"]
follow_untyped_imports = true

[[tool.mypy.overrides]]
module = ["cron_converter.*", "msgpack.*"]
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = [
  "authentik.admin.*",
  "authentik.api.*",
  "authentik.blueprints.*",
  "authentik.brands.*",
  "authentik.core.*",
  "authentik.crypto.*",
  "authentik.endpoints.*",
  "authentik.enterprise.*",
  "authentik.events.*",
  "authentik.flows.*",
  "authentik.lib.*",
  "authentik.outposts.*",
  "authentik.policies.*",
  "authentik.policies.dummy.*",
  "authentik.policies.event_matcher.*",
  "authentik.policies.expiry.*",
  "authentik.policies.expression.*",
  "authentik.policies.geoip.*",
  "authentik.policies.password.*",
  "authentik.policies.reputation.*",
  "authentik.providers.ldap.*",
  "authentik.providers.oauth2.*",
  "authentik.providers.proxy.*",
  "authentik.providers.rac.*",
  "authentik.providers.radius.*",
  "authentik.providers.saml.*",
  "authentik.providers.scim.*",
  "authentik.rbac.*",
  "authentik.recovery.*",
  "authentik.root.*",
  "authentik.sources.kerberos.*",
  "authentik.sources.ldap.*",
  "authentik.sources.oauth.*",
  "authentik.sources.plex.*",
  "authentik.sources.saml.*",
  "authentik.sources.scim.*",
  "authentik.sources.telegram.*",
  "authentik.stages.authenticator_duo.*",
  "authentik.stages.authenticator_email.*",
  "authentik.stages.authenticator_sms.*",
  "authentik.stages.authenticator_static.*",
  "authentik.stages.authenticator_totp.*",
  "authentik.stages.authenticator_validate.*",
  "authentik.stages.authenticator_webauthn.*",
  "authentik.stages.authenticator.*",
  "authentik.stages.captcha.*",
  "authentik.stages.consent.*",
  "authentik.stages.deny.*",
  "authentik.stages.dummy.*",
  "authentik.stages.email.*",
  "authentik.stages.identification.*",
  "authentik.stages.invitation.*",
  "authentik.stages.password.*",
  "authentik.stages.prompt.*",
  "authentik.stages.redirect.*",
  "authentik.stages.user_delete.*",
  "authentik.stages.user_login.*",
  "authentik.stages.user_logout.*",
  "authentik.stages.user_write.*",
  "authentik.tasks.*",
  "authentik.tasks.schedules.*",
  "authentik.tenants.*",
  "guardian.*",
  "lifecycle.*",
  "tests.*",
  "tests.e2e.*",
  "tests.integration.*",
  "tests.openid_conformance.*",
]
ignore_errors = true

[tool.django-stubs]
django_settings_module = "authentik.root.settings"

[tool.coverage.run]
source = ["authentik"]
relative_files = true
omit = [
  "*/asgi.py",
  "manage.py",
  "*/migrations/*",
  "*/management/commands/*",
  "*/apps.py",
  # TODO: Remove this after moving website to docs
  "website/",
  "docs/",
]
concurrency = ["thread", "multiprocessing"]
parallel = true
sigterm = true

[tool.coverage.report]
sort = "Cover"
skip_covered = true
precision = 2
exclude_lines = [
  "pragma: no cover",
  # Don't complain about missing debug-only code:
  "def __unicode__",
  "def __str__",
  "def __repr__",
  "if self.debug",
  "if TYPE_CHECKING",
  # Don't complain if tests don't hit defensive assertion code:
  "raise AssertionError",
  "raise NotImplementedError",
  # Don't complain if non-runnable code isn't run:
  "if 0:",
  "if __name__ == .__main__.:",
]
show_missing = true

[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "authentik.root.settings"
python_files = ["tests.py", "test_*.py", "*_tests.py"]
junit_family = "xunit2"
addopts = "-p authentik.root.test_plugin --junitxml=unittest.xml -vv --full-trace --doctest-modules --import-mode=importlib --ignore=authentik/tasks/setup.py"
filterwarnings = [
  "ignore:defusedxml.lxml is no longer supported and will be removed in a future release.:DeprecationWarning",
  "ignore:SelectableGroups dict interface is deprecated. Use select.:DeprecationWarning",
]
