content: [project]
  authors = [
      {email = "fedor@borshev.com", name = "Fedor Borshev"},
  ]
  dependencies = [
      "babel>=2.12.1,<3.0.0",
      "bcrypt>=5.0.0,<5.1.0",
      "bleach>=6.0.0,<7.0.0",
      "boto3>=1.28.48,<2.0.0",
      "celery==5.5.3",
      "cmarkgfm>=2024.0.0,<2025.0.0",
      "dj-rest-auth>=7.0.0,<8.0.0",
      "django-admin-sortable2>=2.2.4,<3.0.0",
      "django-anymail>=13.0.0,<14.0.0",
      "django-axes>=8.0.0,<9.0.0",
      "django-behaviors>=0.5.1,<1.0.0",
      "django-cachalot>=2.6.1,<3.0.0",
      "django-cors-headers>=4.2.0,<5.0.0",
      "django-debug-toolbar>=6.0.0,<7.0.0",
      "django-environ>=0.12.0,<1.0.0",
      "django-filter>=25.1,<25.2",
      "django-healthchecks>=1.5.0,<2.0.0",
      "django-ipware>=7.0.0,<8.0.0",
      "django-prettyjson>=0.4.1,<1.0.0",
      "django-silk>=5.3.2,<6.0.0",
      "django-split-settings>=1.2.0,<2.0.0",
      "django-storages>=1.14,<2.0",
      "django-tree-queries>=0.20.0,<1.0.0",
      "django>=4.2.5,<5.0.0",
      "djangorestframework>=3.14.0,<4.0.0",
      "drf-jwt>=1.19.2,<2.0.0",
      "drf-spectacular[sidecar]>=0.28.0,<1.0.0",
      "emoji>=2.8.0,<3.0.0",
      "geoip2fast>=1.2.2,<2.0.0",
      "httpx[http2]>=0.27.0,<1.0.0",
      "pillow>=11.3.0,<12.0.0",
      "psycopg[binary]>=3.2.9,<4.0.0",
      "python-benedict>=0.34.0,<1.0.0",
      "redis>=6.0.0,<7.0.0",
      "retry>=0.9.2,<1.0.0",
      "sentry-sdk>=2.0.0,<3.0.0",
      "shortuuid>=1.0.11,<2.0.0",
      "simplejson>=3.19.1,<4.0.0",
      "stripe>=12.0.0,<13.0.0",
      "tqdm>=4.67.1,<5.0.0",
      "whitenoise>=6.5.0,<7.0.0",
  ]
  description = ""
  name = "education-backend"
  readme = "README.md"
  requires-python = "~=3.13.0"
  version = "2025.08.29"
  
  [project.optional-dependencies]
  dev = [
      "boto3-stubs>=1.28.48,<2.0.0",
      "django-stubs>=4.2.6,<5.0.0",
      "djangorestframework-stubs>=3.14.4,<4.0.0",
      "ipython>=9.0.0,<10.0.0",
      "jedi>=0.19.0,<1.0.0",
      "mixer>=7.2.2,<8.0.0",
      "mypy>=1.6.1,<2.0.0",
      "pymarkdownlnt>=0.9.13.4,<1.0.0",
      "pytest-cov>=7.0.0,<7.1.0",
      "pytest-deadfixtures>=2.2.1,<3.0.0",
      "pytest-django>=4.5.2,<5.0.0",
      "pytest-env>=1.0.1,<2.0.0",
      "pytest-freezer>=0.4.9,<1.0.0",
      "pytest-mock>=3.11.1,<4.0.0",
      "pytest-randomly>=4.0.0,<4.1.0",
      "pytest-repeat>=0.9.1,<1.0.0",
      "pytest-xdist>=3.3.1,<4.0.0",
      "requests-mock>=1.11.0,<2.0.0",
      "respx>=0.22.0,<1.0.0",
      "rope>=1.10.0,<2.0.0",
      "ruff>=0.12.0,<1.0.0",
      "toml-sort>=0.24.0,<1.0.0",
      "types-babel>=2.11.0.15,<3.0.0",
      "types-bleach>=6.0.0.4,<7.0.0",
      "types-freezegun>=1.1.10,<2.0.0",
      "types-markdown>=3.4.2.10,<4.0.0",
      "types-pillow>=10.0.0.3,<11.0.0",
      "types-requests>=2.31.0.2,<3.0.0",
      "types-retry>=0.9.9.4,<1.0.0",
      "types-simplejson>=3.19.0.2,<4.0.0",
      "types-stripe>=3.5.2.14,<4.0.0",
  ]
  
  [tool.pymarkdown.plugins.md013]
  enabled = false
  
  [tool.pymarkdown.plugins.md045]
  enabled = false
  
  [tool.pytest.ini_options]
  DJANGO_SETTINGS_MODULE = "core.settings"
  addopts = "--reuse-db"
  env = [
      "CELERY_ALWAYS_EAGER = on",
      "CI = 1",
      "DEFAULT_FILE_STORAGE = django.core.files.storage.memory.InMemoryStorage",
      "DISABLE_HOMEWORK_PERMISSIONS_CHECKING = on",
      "DISABLE_NEW_ANSWER_NOTIFICATIONS = on",
      "DISABLE_THROTTLING = on",
      "RELEASE = CI-testing",
  ]
  filterwarnings = [
  ]
  markers = [
      "auditlog: enable admin logging",
      "dashamail: enable automatic dashamail subscription (may have to patch some code to avoid actual internet connection",
      "freeze_time: freezing time marker (pytest-freezegun does not register it)",
      "single_thread: marked tests should be ran only in single thread",
      "slow: tests with inentional slow run",
      "user_tags_rebuild: enable automatic user tags rebuilding where it supposed to happen in the production app (sloow)",
  ]
  python_files = "test*.py"
  
  [tool.ruff]
  line-length = 160
  target-version = "py313"
  
  [tool.ruff.lint]
  ignore = [
      "A001",  # variable `{}` is shadowing a Python builtin
      "A002",  # argument `{}` is shadowing a Python builtin
      "A003",  # class attribute `{}` is shadowing a Python builtin
      "A005",  # Module `{}` shadows a Python standard-library module
      "ANN",  # let mypy handle it
      "ARG002",  # unused method argument: `{}`
      "ARG005",  # unused lambda argument: `{}`
      "B018",  # found useless expression. Either assign it to a variable or remove it
      "B904",  # within an `except` clause, raise exceptions with [...]
      "B904",  # use `raise from` to specify exception cause
      "C408",  # unnecessary `dict` call (rewrite as a literal)
      "COM812",  # trailing comma missing
      "COM819",  # trailing comma prohibited
      "D100",  # missing docstring in public module
      "D101",  # missing docstring in public class
      "D102",  # missing docstring in public method
      "D103",  # missing docstring in public function
      "D104",  # missing docstring in public package
      "D105",  # missing docstring in magic method
      "D106",  # missing docstring in public nested class
      "D107",  # missing docstring in `__init__`
      "D200",  # one-line docstring should fit on one line
      "D202",  # no blank lines allowed after function docstring (found {})
      "D203",  # 1 blank line required before class docstring
      "D205",  # 1 blank line required between summary line and description
      "D209",  # multi-line docstring closing quotes should be on a separate line
      "D210",  # no whitespaces allowed surrounding docstring text
      "D212",  # multi-line docstring summary should start at the first line
      "D213",  # multi-line docstring summary should start at the second line
      "D400",  # first line should end with a period
      "D401",  # first line of docstring should be in imperative mood: "{}"
      "D404",  # first word of the docstring should not be "This"
      "D415",  # first line should end with a period, question mark, or exclamation point
      "DJ007",  # do not use `__all__` with `ModelForm`, use `fields` instead
      "DTZ001",  # the use of `datetime.datetime()` without `tzinfo` argument is not allowed
      "E501",  # line too long ({} > {})
      "EM101",  # exception must not use a string literal, assign to variable first
      "EM102",  # expection must not use an f-string literal, assign to variable first
      "F811",  # redefinition of unused `{}` from line {}
      "FBT001",  # boolean-typed position argument in function definition
      "FBT002",  # boolean default position argument in function definition
      "FBT003",  # boolean positional value in function call
      "INP001",  # file `{}` is part of an implicit namespace package. Add an `__init__.py`
      "INT001",  # f-string is resolved before function call; consider `_("string %s") % arg`
      "ISC001",  # implicitly concatenated string literals on one line
      "N802",  # function name `{}` should be lowercase
      "N803",  # argument name `{}` should be lowercase
      "N804",  # first argument of a class method should be named `cls`
      "N806",  # variable `{}` in function should be lowercase
      "N812",  # lowercase `{}` imported as non-lowercase `{}`
      "N818",  # exception name `{}` should be named with an Error suffix
      "N999",  # invalid module name: '{}'
      "PERF401",  # use a list comprehension to create a transformed list
      "PGH003",  # use specific rule codes when ignoring type issues
      "PGH004",  # use specific rule codes when using `noqa`
      "PLC0415",  # import should be at the top of the file
      "PLR0913",  # too many arguments in function definition ({} > {})
      "PLR2004",  # magic value used in comparison, consider replacing {} with constant variable
      "PLR5501",  # use `elif` instead of `else` then `if` to reduce indentation
      "PLW0603",  # using the global statement to update `{}` is discouraged
      "PLW2901",  # `for` loop variable `{}` overwritten by assignment target
      "PT001",  # use `@pytest.fixture()` over `@pytest.fixture`
      "PT006",  # wrong name(s) type in `@pytest.mark.parametrize`, expected `{}`
      "PTH118",  # `os.path.join()` should be replaced by `Path` with `/` operator
      "PTH119",  # `os.path.basename()` should be replaced by `Path.name`
      "PTH120",  # `os.path.dirname()` should be replaced by `Path.parent`
      "PTH122",  # `os.path.splitext()` should be replaced by `Path.suffix`, [...]
      "PTH123",  # `open()` should be replaced by `Path.open()`
      "Q000",  # single quotes found but double quotes preferred
      "RET501",  # do not explicitly `return None` in function if it is the only possible return value
      "RET502",  # do not implicitly `return None` in function able to return non-`None` value
      "RET503",  # missing explicit `return` at the end of function able to return non-`None` value
      "RET504",  # unnecessary assignment to `{}` before `return` statement
      "RET505",  # unnecessary `else` after `return` statement
      "RSE102",  # unnecessary parentheses on raised exception
      "RUF001",  # string contains ambiguous `{}` [...]
      "RUF002",  # docstring contains ambiguous `{}` [...]
      "RUF005",  # consider iterable unpacking instead of concatenation"
      "RUF009",  # do not perform function call `{}` in dataclass defaults
      "RUF012",  # mutable class attributes should be annotated with `typing.ClassVar`
      "RUF015",  # prefer next({iterable}) over single element slice
      "RUF100",  # unused `noqa` directive (unknown: `{}`)
      "S101",  # use of `assert` detected
      "S105",  # possible hardcoded password assigned to: "{}"
      "S105",  # possible hardcoded password assigned to: "{}"
      "S106",  # possible hardcoded password assigned to argument: "{}"
      "S308",  # use of `mark_safe` may expose cross-site scripting vulnerabilities
      "S311",  # standart pseudo-random generators are not suitable for cryptographic purposes
      "S324",  # probable use of insecure hash functions in `{}`: `{}`
      "SIM102",  # use a single `if` statement instead of nested `if` statements
      "SIM108",  # use ternary operator `{}` instead of `if`-`else`-block
      "SIM300",  # yoda conditions are discouraged, use `{}` instead
      "SLF001",  # private member accessed: `{}`
      "TC001",  # move application import `{}` into a type-checking block
      "TC002",  # move third-party import `{}` into a type-checking block
      "TC003",  # move standart library import `{}` into a type-checking block
      "TRY003",  # avoid specifying long messages outside the exception class
      "TRY300",  # consider moving this statement to an `else` block
      "UP006",  # use `{}` instead of `{}` for type annotation
      "UP015",  # unnecessary open mode parameters
      "UP017",  # use `datetime.UTC` alias
      "UP035",  # `{}` is deprecated, use `{}` instead
  ]
  select = ["ALL"]
  
  [tool.ruff.lint.flake8-tidy-imports]
  ban-relative-imports = "all"
  
  [tool.ruff.lint.isort]
  known-local-folder = ["apps", "core"]
  
  [tool.ruff.lint.per-file-ignores]
  "*/factory.py" = [
      "ANN",  # flake8-annotations
  ]
  "*/fixtures.py" = [
      "ANN",  # flake8-annotations
  ]
  "*/fixtures/*" = [
      "ANN",  # flake8-annotations
  ]
  "*/management/*" = [
      "ANN",  # flake8-annotations
  ]
  "*/migrations/*" = [
      "ANN",  # flake8-annotations
  ]
  "*/tests/*" = [
      "ANN",  # flake8-annotations
  ]
  "src/core/conf/*" = [
      "ANN",  # flake8-annotations
  ]
  "src/core/test/api_client.py" = [
      "ANN",  # flake8-annotations
  ]
  
  [tool.tomlsort]
  all = true
  in_place = true
  sort_first = ["tool.poetry"]
  spaces_before_inline_comment = 2
  spaces_indent_inline_array = 4
  trailing_comma_inline_array = true
  