[build-system]
requires = ["flit_core >=3.11,<5"]
build-backend = "flit_core.buildapi"

[project]
name = "pypdf"
authors = [{ name = "Mathieu Fenniak", email = "biziqe@mathieu.fenniak.net" }]
maintainers = [{ name = "stefan6419846" }, { name = "Martin Thoma", email = "info@martin-thoma.de" }]
description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files"
readme = "README.md"
dynamic = ["version"]
license = "BSD-3-Clause"
license-files = ["LICENSE"]
requires-python = ">=3.9"
classifiers = [
    "Development Status :: 5 - Production/Stable",
    "Intended Audience :: Developers",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
    "Operating System :: OS Independent",
    "Topic :: Software Development :: Libraries :: Python Modules",
    "Typing :: Typed",
]

dependencies = [
    "typing_extensions >= 4.0; python_version < '3.11'",
]

[project.urls]
Changelog = "https://pypdf.readthedocs.io/en/latest/meta/CHANGELOG.html"
Documentation = "https://pypdf.readthedocs.io/en/latest/"
Source = "https://github.com/py-pdf/pypdf"
"Bug Reports" = "https://github.com/py-pdf/pypdf/issues"

[project.optional-dependencies]
# This is due to the backend parameter being required back then:
# https://cryptography.io/en/latest/changelog/#v3-1
crypto = ["cryptography>3.0"]
cryptodome = ["PyCryptodome"]
fonts = ["fonttools"]
image = ["Pillow>=8.0.0"]
rtl_text = [
    "arabic-reshaper",
    "python-bidi",
]
full = [
    "arabic-reshaper",
    "cryptography>3.0",
    "fonttools",
    "Pillow>=8.0.0",
    "python-bidi"
]
dev = [
    "flit",
    "pip-tools",
    "pre-commit",
    "pytest-cov",
    "pytest-socket",
    "pytest-timeout",
    "pytest-xdist",
    "wheel"
]
docs = [
    "myst_parser",
    "sphinx",
    "sphinx_rtd_theme"
]

[tool.check-wheel-contents]
package = "./pypdf"

[tool.flit.sdist]
exclude = [
    ".gitblame-ignore-revs",
    ".github/*",
    ".gitignore",
    ".gitmodules",
    ".pre-commit-config.yaml",
    "docs/*",
    "make_release.py",
    "Makefile",
    "requirements/*",
    "sample-files/.github/*",
    "sample-files/.gitignore",
    "sample-files/.pre-commit-config.yaml",
    "tests/pdf_cache/*",
]
include = ["resources/", "tests/", "CHANGELOG.md"]

[tool.pytest.ini_options]
addopts = "--disable-socket"
filterwarnings = ["error"]
markers = [
    "slow: Test which require more than a second",
    "samples: Tests which use files from https://github.com/py-pdf/sample-files",
    "enable_socket: Tests which need to download files"
]
testpaths = ["tests"]
norecursedirs = ["tests/pdf_cache"]

[tool.isort]
line_length = 79
indent = '    '
multi_line_output = 3
include_trailing_comma = true
known_third_party = ["pytest"]

[tool.coverage.run]
source = ["pypdf"]
branch = true
patch = [
    "subprocess",
]
parallel = true

[tool.coverage.report]
# Regexes for lines to exclude from consideration
exclude_lines = [
    # Have to re-enable the standard pragma
    "pragma: no cover",
    "@overload",
    "deprecated",

    # Don't complain about type-checking code not being hit by unit tests
    "if TYPE_CHECKING",

    # Don't complain about missing debug-only code:
    "def __repr__",
    "def __str__",

    # 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 __name__ == .__main__.:",
]

[tool.ruff]
line-length = 120
exclude = [
    "sample-files/",
]

[tool.ruff.lint]
select = ["ALL"]
ignore = [
    "A001",    # Variable is shadowing a Python builtin
    "A002",    # Function argument is shadowing a Python builtin
    "ANN401",  # Dynamically typed expressions (typing.Any) are disallowed
    "ARG001",  # Unused function argument
    "ARG002",  # Unused method argument
    "ARG004",  # Unused static method argument
    "B904",    # Within an `except` clause, raise exceptions with
    "B905",    # `zip()` without an explicit `strict=` parameter
    "BLE001",  # Do not catch blind exception: `Exception`
    "COM812",  # Yes, they make the diff smaller
    "CPY001",  # We do not have a policy for copyrights per file at the moment
    "D101",    # Missing docstring in public class
    "D102",    # Missing docstring in public method
    "D105",    # Missing docstring in magic method
    "D106",    # Missing docstring in public nested class
    "D107",    # Missing docstring in `__init__`
    "D205",    # One blank line required between summary line and description
    "D212",    # I want multiline-docstrings to start at the second line
    "D401",    # First line of docstring should be in imperative mood - false positives
    "D415",    # First line should end with a period
    "D417",    # Missing argument descriptions in the docstring
    "DTZ001",  # The use of `datetime.datetime()` without `tzinfo` is necessary
    "EM101",   # Exception must not use a string literal, assign to variable first
    "EM102",   # Exception must not use an f-string literal, assign to variable first
    "ERA001",  # Found commented-out code
    "FA100",   # Missing `from __future__ import annotations`, but uses `typing.Dict`
    "FA102",   # Missing `from __future__ import annotations`, but uses PEP 604 union
    "FBT001",  # Boolean positional arg in function definition
    "FBT002",  # Boolean default value in function definition
    "FBT003",  # Boolean positional value in function call
    "FIX002",  # TODOs should typically not be in the code, but sometimes are ok
    "N806",    # non-lowercase-variable-in-function
    "N814",    # Camelcase `PageAttributes` imported as constant `PG`
    "N817",    # CamelCase `PagesAttributes` imported as acronym `PA`
    "PERF203", # `try`-`except` within a loop incurs performance overhead
    "PGH003",  # Use specific rule codes when ignoring type issues
    "PLW1510", # `subprocess.run` without explicit `check` argument
    "PLW2901", # `with` statement variable `img` overwritten by assignment target
    "PT011",   # `pytest.raises(ValueError)` is too broad, set the `match`
    "PT012",   # `pytest.raises()` block should contain a single simple statement
    "PT014",   # Ruff bug: Duplicate of test case at index 1 in `@pytest_mark.parametrize`
    "PTH123",  # `open()` should be replaced by `Path.open()`
    "PYI042",  # Type alias `mode_str_type` should be CamelCase
    "RUF001",  # Detect confusable Unicode-to-Unicode units. Introduces bugs
    "RUF002",  # Detect confusable Unicode-to-Unicode units. Introduces bugs
    "S101",    # Use of `assert` detected
    "S110",    # `try`-`except`-`pass` detected, consider logging the exception
    "SIM105",  # contextlib.suppress
    "SIM108",  # Don't enforce ternary operators
    "SLF001",  # Private member accessed
    "TC006",   # To discuss: Add quotes to type expression in `typing.cast()`
    "TD002",   # Authors of TODOs can be found via git
    "TD003",   # For the moment, fix it later: Missing issue link on the line following this TODO
    "TID252",  # We want relative imports
    "TRY002",  # Create your own exception
    "TRY003",  # Avoid specifying long messages outside the exception class
    "TRY004",  # Prefer `TypeError` exception for invalid type
    "TRY300",  # Consider moving this statement to an `else` block
    "TRY301",  # Abstract `raise` to an inner function
    "UP006",   # Non-PEP 585 annotation. As long as we are not on Python 3.11+
    "UP007",   # Non-PEP 604 annotation. As long as we are not on Python 3.11+
]

[tool.ruff.lint.mccabe]
max-complexity = 30  # Recommended: 10

[tool.ruff.lint.per-file-ignores]
"_cryptography.py" = ["S304", "S305"]  # Use of insecure cipher / modes, aka RC4 and AES-ECB
"_encryption.py" = ["S324"]
"_writer.py" = ["S324"]
"pypdf/_codecs/symbol.py" = ["A005"]  # Module shadows a Python standard-library module
"types.py" = ["A005"]  # Module shadows a Python standard-library module
"pypdf/_text_extraction/__init__.py" = ["PLW0603"]  # Using the global statement to update is discouraged
"docs/conf.py" = ["INP001", "PTH100"]
"json_consistency.py" = ["T201"]
"make_release.py" = ["S603", "S607", "T201"]
"pypdf/*" = ["N802", "N803"]  # We first need to deprecate old stuff
"tests/*" = ["ANN001", "ANN201", "B017", "B018", "D103", "D104", "S105", "S106"]
"tests/test_workflows.py" =  ["T201"]

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.lint.pylint]
allow-magic-value-types = ["bytes", "float", "int", "str"]
max-args = 12  # Recommended: 5
max-branches = 36  # Recommended: 12
max-returns = 11  # Recommended: 6
max-statements = 176  # Recommended: 50

[tool.docformatter]
pre-summary-newline = true
wrap-summaries = 0
wrap-descriptions = 0

[tool.mypy]
strict = true
ignore_missing_imports = true
show_error_codes = true
exclude = ['venv', '.venv', 'sample-files']

[[tool.mypy.overrides]]
module = "tests.*"
ignore_errors = true

[[tool.mypy.overrides]]
module = "tests.generic.*"
ignore_errors = false

[[tool.mypy.overrides]]
module = "tests.scripts.*"
ignore_errors = false
