[project]
name = "frappe"
authors = [
    { name = "Frappe Technologies Pvt Ltd", email = "developers@frappe.io"}
]
description = "Metadata driven, full-stack low code web framework"
requires-python = ">=3.14,<3.15"
readme = "README.md"
dynamic = ["version"]
dependencies = [
    # core dependencies
    "Babel~=2.16.0",
    "Click~=8.3.1",
    "filelock~=3.20.1",
    "filetype~=1.2.0",
    "GitPython~=3.1.45",
    "Jinja2~=3.1.6",
    "Pillow~=12.2.0",
    "PyJWT~=2.12.1",
    # We depend on internal attributes,
    # do NOT add loose requirements on PyMySQL versions.
    "PyMySQL==1.1.2",
    "pypdf==6.10.2",
    "PyPika @ git+https://github.com/frappe/pypika@2c50e6142b2d61d2d243e466fdd5dc03b3d918f2",
    "mysqlclient==2.2.7",
    "PyQRCode~=1.2.1",
    "PyYAML~=6.0.3",
    "RestrictedPython~=8.1",
    "WeasyPrint==68.0",
    "pydyf==0.12.1",
    "Werkzeug==3.1.6",
    "Whoosh~=2.7.4",
    "beautifulsoup4~=4.13.5",
    "bleach-allowlist~=1.0.3",
    "chardet~=5.2.0",
    "croniter~=6.0.0",
    "cryptography~=46.0.3",
    "cssutils~=2.11.1",
    "email-reply-parser~=0.5.12",
    "Faker~=18.10.1",
    "gunicorn @ git+https://github.com/frappe/gunicorn@bb554053bb87218120d76ab6676af7015680e8b6",
    "html5lib~=1.1",
    "ipython~=8.37.0",
    "ldap3~=2.9.1",
    "markdown2~=2.5.4",
    "MarkupSafe~=3.0.3",
    "nh3~=0.3.2",
    "num2words~=0.5.14",
    "oauthlib~=3.3.1",
    "openpyxl~=3.1.5",
    "xlsxwriter~=3.2.9",
    "orjson~=3.11.5",
    "passlib~=1.7.4",
    "pdfkit~=1.0.0",
    "phonenumbers~=9.0.21",
    "premailer~=3.10.0",
    "psutil~=7.0.0",
    "psycopg2-binary~=2.9.11",
    "pyOpenSSL~=26.0.0",
    "pydantic~=2.12.5",
    "pyotp~=2.9.0",
    "python-dateutil~=2.9.0.post0",
    "pytz==2025.2",
    "rauth~=0.7.3",
    "redis~=7.1.0",
    "hiredis~=3.3.0",
    "requests-oauthlib~=2.0.0",
    "requests~=2.33.0",
    # We depend on internal attributes of RQ.
    # Do NOT add loose requirements on RQ versions.
    # Audit the code changes w.r.t. background_jobs.py before updating.
    "rq==2.6.1",
    "rsa~=4.9.1",
    "semantic-version~=2.10.0",
    "sentry-sdk~=1.45.1",
    "sqlparse~=0.5.5",
    "sql_metadata~=2.19.0",
    "tenacity~=9.1.2",
    "terminaltables~=3.1.10",
    "traceback-with-variables~=2.2.1",
    "typing_extensions>=4.15.0,<5",
    "xlrd~=2.0.2",
    "zxcvbn~=4.5.0",
    "markdownify~=1.2.2",
    # integration dependencies
    "google-api-python-client~=2.188.0",
    "google-auth-oauthlib~=1.2.4",
    "google-auth~=2.48.0",
    "posthog~=5.0.0",
    "vobject~=0.9.9",
    "pycountry~=24.6.1",
    "websockets~=15.0.1",
]

[project.urls]
Homepage = "https://frappe.io/framework"
Repository = "https://github.com/frappe/frappe.git"
Documentation = "https://docs.frappe.io/framework"
"Bug Reports" = "https://github.com/frappe/frappe/issues"

[project.optional-dependencies]
dev = [
    "pyngrok~=7.5.0",
    "watchdog~=6.0.0",
    "responses==0.23.1",
    # typechecking
    "basedmypy",
    "types-PyMySQL",
    "types-PyYAML",
    "types-Pygments",
    "types-beautifulsoup4",
    "types-cffi",
    "types-colorama",
    "types-croniter",
    "types-decorator",
    "types-ldap3",
    "types-oauthlib",
    "types-openpyxl",
    "types-passlib",
    "types-psutil",
    "types-psycopg2",
    "types-python-dateutil",
    "types-pytz",
    "types-requests",
    "types-six",
    "types-vobject",
    "types-zxcvbn",
    "pypika-stubs",  # contributed
]
test = [
    "unittest-xml-reporting~=3.2.0",
    "coverage~=7.13.5",
    "hypothesis~=6.77.0",
    "freezegun~=1.5.1",
]

[build-system]
requires = ["flit_core >=3.4,<4"]
build-backend = "flit_core.buildapi"

[tool.frappe.testing.function_type_validation]
max_module_depth = 0
skip_namespaces = [
    "frappe.deprecation_dumpster",
    "frappe.utils.typing_validations",
]

[tool.bench.dev-dependencies]
coverage = "~=7.13.5"
pyngrok = "~=7.5.0"
unittest-xml-reporting = "~=3.2.0"
watchdog = "~=6.0.0"
hypothesis = "~=6.77.0"
responses = "==0.23.1"
freezegun = "~=1.5.5"

[tool.ruff]
line-length = 110
target-version = "py314"
exclude = [
    "**/doctype/*/boilerplate/*.py" # boilerplate are template strings, not valid python
]

[tool.ruff.lint]
select = [
    "F",
    "E",
    "W",
    "I",
    "UP",
    "B",
    "RUF",
]
ignore = [
    "B017", # assertRaises(Exception) - should be more specific
    "B018", # useless expression, not assigned to anything
    "B023", # function doesn't bind loop variable - will have last iteration's value
    "B904", # raise inside except without from
    "E101", # indentation contains mixed spaces and tabs
    "E402", # module level import not at top of file
    "E501", # line too long
    "E741", # ambiguous variable name
    "F401", # "unused" imports
    "F403", # can't detect undefined names from * import
    "F405", # can't detect undefined names from * import
    "F722", # syntax error in forward type annotation
    "W191", # indentation contains tabs
    "UP030", # Use implicit references for positional format fields (translations)
    "UP031", # Use format specifiers instead of percent format
    "UP032", # Use f-string instead of `format` call (translations)
    "UP037", # quoted annotations
    "UP040", # Use type aliases instead of type annotations
]
typing-modules = ["frappe.types.DF"]

[tool.ruff.format]
quote-style = "double"
indent-style = "tab"
docstring-code-format = true

[tool.frappix]
# use identifier from https://search.nixos.org/packages
nixpkgs-deps = [
    "mariadb",
    "restic",
    "wkhtmltopdf-bin",
    "which",
    "gzip",
    "bash",
    "redis",
    "nodejs_20",
    "python312",
]

[tool.mypy]
strict = false
pretty = true
incremental = true
sqlite_cache = true
files = [
    # start small, with a lot of multiplication potential
    "frappe/types/__init__.py",
    "frappe/types/DF.py",
    "frappe/types/frappedict.py",
    "frappe/types/filter.py",
]
exclude = [
    # permanent excludes
    "^frappe/patches",
    '/test_.+\.py$',
    "^frappe/tests/ui_test_helpers.py",
    "^frappe/parallel_test_runner.py",
    "^frappe/deprecation_dumpster.py",
]
disable_error_code = [
]

[[tool.mypy.overrides]]
module = "frappe"
# Too many for a start
disable_error_code = [
    "no-any-expr",
    "no-untyped-def",
    "no-untyped-call",
    "no-untyped-usage",
]

# External libraries without types
[[tool.mypy.overrides]]
module = [
    "bleach_allowlist",
    "cssutils",
    "cups",
    "email_reply_parser",
    "filetype",
    "google",
    "googleapiclient.discovery",
    "googleapiclient.errors",
    "google.oauth2",
    "google.oauth2.credentials",
    "markdown2",
    "markdownify",
    "num2words",
    "pdfkit",
    "premailer",
    "pyngrok",
    "pypika",
    "pypika.dialects",
    "pypika.functions",
    "pypika.queries",
    "pypika.terms",
    "pypika.utils",
    "pyqrcode",
    "rauth",
    "requests_oauthlib",
    "RestrictedPython",
    "RestrictedPython.Guards",
    "RestrictedPython.transformer",
    "semantic_version",
    "sql_metadata",
    "sqlparse",
    "terminaltables",
    "traceback_with_variables",
    "weasyprint",
    "whoosh.fields",
    "whoosh.index",
    "whoosh.qparser",
    "whoosh.query",
    "whoosh.writing",
    "xlrd",
    "xmlrunner",
]
ignore_missing_imports = true
