[tool.black]
line-length = 120

[tool.isort]
profile = "black"
line_length = 120
skip = [".eggs", ".git", ".hg", ".mypy_cache", ".tox", ".venv", "venv", "_build", "buck-out", "build", "dist", "migrations"]

[tool.pylint.messages_control]
disable = [
    "C0111",  # missing-docstring
    "C0103",  # invalid-name
    "R0903",  # too-few-public-methods
    "R0913",  # too-many-arguments
]

[tool.pylint.format]
max-line-length = 120

[tool.bandit]
exclude_dirs = ["tests", "migrations", "venv", ".venv"]
skips = ["B101"]  # Skip assert_used test

[tool.coverage.run]
source = ["app"]
omit = [
    "*/tests/*",
    "*/test_*.py",
    "*/__pycache__/*",
    "*/venv/*",
    "*/env/*",
    "*/migrations/*",
    "app/utils/pdf_generator.py",
    "app/utils/pdf_generator_fallback.py",
]

[tool.coverage.report]
precision = 2
show_missing = true
skip_covered = false
exclude_lines = [
    "pragma: no cover",
    "def __repr__",
    "raise AssertionError",
    "raise NotImplementedError",
    "if __name__ == .__main__.:",
    "if TYPE_CHECKING:",
    "@abstractmethod",
]

[tool.mypy]
python_version = "3.11"
# warn_return_any is intentionally OFF: SQLAlchemy 1.x-style ``Model.query`` and
# ``.first()`` / ``.all()`` return ``Any``, so wrapping them in a typed return
# yields hundreds of false positives until/unless the codebase migrates to
# SQLAlchemy 2.x ``Mapped[X]`` annotations.
warn_return_any = false
warn_unused_configs = true
disallow_untyped_defs = false
ignore_missing_imports = true
# Restore pre-mypy-0.991 implicit Optional so ``def foo(x: str = None)`` is not
# flagged. The codebase uses this Flask-style idiom in many places.
implicit_optional = true
disable_error_code = [
    # ``if SomeClass:`` truthy-class checks are used as import-guards across
    # routes/services; their meaning is intentional.
    "truthy-function",
    "truthy-bool",
]
exclude = [
    "migrations/",
    "tests/",
    "venv/",
    ".venv/",
]

# Flask-SQLAlchemy models defined as ``class Foo(db.Model)`` are not understood
# by mypy without a dedicated plugin. Skip them rather than chase ~150 false
# positives of "Name 'db.Model' is not defined".
[[tool.mypy.overrides]]
module = "app.models.*"
ignore_errors = true

# Repositories use ``TypeVar("ModelType")`` which mypy cannot bind to the
# Flask-SQLAlchemy ``.query`` / ``joinedload(rel)`` runtime API. The patterns
# are correct at runtime; suppress the documented limitation.
[[tool.mypy.overrides]]
module = "app.repositories.*"
disable_error_code = ["arg-type", "attr-defined"]

# Generic base service uses ``RepositoryType`` TypeVar; same limitation as
# repositories — methods exist at runtime via the bound class.
[[tool.mypy.overrides]]
module = "app.services.base_crud_service"
disable_error_code = ["attr-defined"]

# ``Query.paginate`` is added by Flask-SQLAlchemy and is missing from the
# SQLAlchemy stubs. Suppress this single attr-defined case in helpers that
# wrap pagination.
[[tool.mypy.overrides]]
module = ["app.utils.pagination", "app.services.project_service"]
disable_error_code = ["attr-defined"]

# SQLAlchemy joinedload(rel) accepts ``RelationshipProperty`` at runtime but
# is typed as ``QueryableAttribute``. This pattern is used pervasively in
# services and is correct.
[[tool.mypy.overrides]]
module = ["app.services.task_service", "app.services.quote_service", "app.services.invoice_service", "app.services.expense_service", "app.services.scheduled_report_service"]
disable_error_code = ["arg-type"]

# Pytest config is in pytest.ini (single source of truth to avoid duplicate config warning)
