[project]
name = "markitai"
version = "0.14.0"
description = "Opinionated Markdown converter with native LLM enhancement support"
requires-python = ">=3.11,<3.14"
authors = [
    { name = "Ynewtime", email = "longqiliuye@gmail.com" }
]
dynamic = ["readme"]
keywords = ["markdown", "converter", "llm", "pdf", "docx", "ocr"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Environment :: Console",
    "Intended Audience :: Developers",
    "Operating System :: OS Independent",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Topic :: Text Processing :: Markup :: Markdown",
    "Topic :: Utilities",
]
dependencies = [
    "pymupdf4llm>=1.27.2",
    "markitdown[all]>=0.1.5",
    "litellm>=1.82.0,<1.82.7",  # pinned: >=1.82.7 compromised (supply-chain attack) — TODO: lift upper bound once a safe release is available
    "instructor>=1.14.5",
    "rapidocr>=3.7.0",
    "click>=8.3.0",
    "loguru>=0.7.3",
    "rich>=14.3.0",
    "Pillow>=12.1.0",
    "opencv-python>=4.13.0",
    "aiofiles>=25.1.0",
    "pydantic>=2.12.0",
    "python-dotenv>=1.2.2",
    "pywin32>=310; sys_platform == 'win32'",
    "questionary>=2.1.0",
    "tinycss2>=1.2",
]

[project.urls]
Homepage = "https://markitai.ynewtime.com"
Documentation = "https://markitai.ynewtime.com/guide/getting-started"
Repository = "https://github.com/Ynewtime/markitai"
Changelog = "https://github.com/Ynewtime/markitai/blob/main/CHANGELOG.md"

[project.scripts]
markitai = "markitai.cli:app"

[project.optional-dependencies]
claude-agent = ["claude-agent-sdk>=0.1.50"]
copilot = ["github-copilot-sdk>=0.2.0"]
gemini-cli = ["google-auth>=2.49.1", "google-auth-oauthlib>=1.3.0"]
browser = ["playwright>=1.58.0"]
extra-fetch = ["curl-cffi>=0.14.0"]
kreuzberg = ["kreuzberg>=4.5.3"]
svg = ["cairosvg>=2.9.0"]
all = [
    "claude-agent-sdk>=0.1.50",
    "github-copilot-sdk>=0.2.0",
    "google-auth>=2.49.1",
    "google-auth-oauthlib>=1.3.0",
    "playwright>=1.58.0",
    "curl-cffi>=0.14.0",
    "kreuzberg>=4.5.3",
    "cairosvg>=2.9.0",
]

[dependency-groups]
dev = [
    "pytest>=9.0.0",
    "pytest-xdist>=3.8.0",
    "pytest-asyncio>=1.3.0",
    "ruff>=0.15.7",
    "pyright>=1.1.408",
    "lxml-stubs>=0.5.1",
]

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

[tool.hatch.metadata.hooks.custom]

[tool.hatch.build.targets.sdist.force-include]
"../../README.md" = "README.md"

[tool.hatch.build.targets.wheel]
packages = ["src/markitai"]

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
markers = [
    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
    "network: marks tests that require network access (deselect with '-m \"not network\"')",
    "parity: marks extraction parity tests against fixture contracts",
]
filterwarnings = [
    # pydub: checks for ffmpeg at import time, not always on PATH in CI venvs
    "ignore:Couldn't find ffmpeg or avconv:RuntimeWarning:pydub",
    # litellm: async mock coroutines not awaited inside cost_calculator
    "ignore:coroutine .* was never awaited:RuntimeWarning",
]

[tool.ruff]
target-version = "py313"
line-length = 88
src = ["src", "tests"]

[tool.ruff.lint]
select = [
    "E",      # pycodestyle errors
    "W",      # pycodestyle warnings
    "F",      # Pyflakes
    "I",      # isort
    "B",      # flake8-bugbear
    "C4",     # flake8-comprehensions
    "UP",     # pyupgrade
    "ARG",    # flake8-unused-arguments
    "SIM",    # flake8-simplify
]
ignore = [
    "E501",   # line too long (handled by formatter)
    "E402",   # module level import not at top (needed for env setup before imports)
    "B008",   # do not perform function calls in argument defaults
    "B904",   # raise from in except (too verbose for simple re-raises)
    "B905",   # zip without explicit strict
    "ARG001", # unused function argument (click callback signatures)
    "ARG002", # unused method argument (callback signatures)
    "SIM102", # nested if (explicit nesting is clearer for complex conditions)
    "SIM105", # contextlib.suppress (explicit try-except is clearer)
    "SIM108", # use ternary operator instead of if-else
    "SIM103", # return condition directly (explicit return is clearer)
    "UP047",  # use type parameters (PEP 695) - TypeVar is clearer for compatibility
]

[tool.ruff.lint.isort]
known-first-party = ["markitai"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"

[tool.pyright]
pythonVersion = "3.13"
typeCheckingMode = "basic"
include = ["src"]
exclude = ["tests", "**/__pycache__"]
venvPath = "../.."
venv = ".venv"
# Allow optional dependencies to be missing (claude-agent-sdk)
# These are runtime-checked before import using importlib.util.find_spec
reportMissingImports = "warning"
reportMissingTypeStubs = false
reportUnusedImport = true
reportUnusedVariable = "warning"
# Third-party libraries with incomplete type stubs
reportPrivateImportUsage = "warning"
reportAttributeAccessIssue = "warning"
reportArgumentType = "warning"
reportCallIssue = "warning"
reportReturnType = "warning"
# Click decorator patterns (e.g., @app.group())
reportFunctionMemberAccess = "warning"
