diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2dedadf9a5..9859aab052 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -51,6 +51,11 @@ repos: exclude_types: [json, pofile] exclude: 'changelog/|py.typed|disnake/bin/COPYING|.github/PULL_REQUEST_TEMPLATE.md|.github/CODEOWNERS|LICENSE|MANIFEST.in|.gitattributes' + - repo: https://github.com/tox-dev/pyproject-fmt + rev: v2.6.0 + hooks: + - id: pyproject-fmt + - repo: https://github.com/charliermarsh/ruff-pre-commit rev: v0.12.12 hooks: diff --git a/pyproject.toml b/pyproject.toml index 62c459b93c..108f2de90f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,28 +1,30 @@ # SPDX-License-Identifier: MIT [build-system] -requires = ["setuptools>=61"] build-backend = "setuptools.build_meta" +requires = [ "setuptools>=61" ] [project] name = "disnake" description = "A Python wrapper for the Discord API" readme = "README.md" -authors = [ - { name = "Disnake Development" } +keywords = [ + "discord", + "discord api", + "disnake", ] -requires-python = ">=3.8" -keywords = ["disnake", "discord", "discord api"] license = { text = "MIT" } -dependencies = [ - "aiohttp>=3.7.0,<4.0", +authors = [ + { name = "Disnake Development" }, ] +requires-python = ">=3.8" classifiers = [ "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: MIT License", "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -35,162 +37,143 @@ classifiers = [ "Topic :: Utilities", "Typing :: Typed", ] -dynamic = ["version"] - -[project.urls] -Changelog = "https://docs.disnake.dev/page/whats_new.html" -Documentation = "https://docs.disnake.dev/" -Repository = "https://github.com/DisnakeDev/disnake" - -[project.optional-dependencies] -speed = [ - "orjson~=3.6", - # taken from aiohttp[speedups] - "aiodns>=1.1", - "Brotli", - 'cchardet; python_version < "3.10"', -] -voice = [ - "PyNaCl>=1.5.0,<1.6", - 'audioop-lts==0.2.1; python_version >= "3.13"' +dynamic = [ "version" ] +dependencies = [ + "aiohttp>=3.7.0,<4.0", ] -docs = [ +optional-dependencies.docs = [ "sphinx==7.0.1", - "sphinxcontrib-trio~=1.1.2", - "sphinx-hoverxref==1.3.0", "sphinx-autobuild~=2021.3", - "sphinxcontrib-towncrier==0.3.2a0", - "towncrier==23.6.0", + "sphinx-hoverxref==1.3.0", "sphinx-notfound-page==0.8.3", + "sphinxcontrib-towncrier==0.3.2a0", + "sphinxcontrib-trio~=1.1.2", "sphinxext-opengraph==0.9.1", + "towncrier==23.6.0", ] +optional-dependencies.speed = [ + # taken from aiohttp[speedups] + "aiodns>=1.1", + "brotli", + "cchardet; python_version<'3.10'", + "orjson~=3.6", +] +optional-dependencies.voice = [ + "audioop-lts==0.2.1; python_version>='3.13'", + "pynacl>=1.5.0,<1.6", +] +urls.Changelog = "https://docs.disnake.dev/page/whats_new.html" +urls.Documentation = "https://docs.disnake.dev/" +urls.Repository = "https://github.com/DisnakeDev/disnake" [dependency-groups] +test = [ + "coverage[toml]~=7.6.0", + "looptime~=0.2.0", + "pytest~=8.3.2", + "pytest-asyncio~=0.24.0", + "pytest-cov~=4.0.0", +] nox = [ "nox==2025.5.1", ] tools = [ - "pre-commit~=3.0", - "slotscheck~=0.16.4", "check-manifest==0.49", + "pre-commit~=3.0", "ruff==0.12.12", + "slotscheck~=0.16.4", ] changelog = [ "towncrier==23.6.0", ] codemod = [ - # run codemods on the repository (mostly automated typing) + "autotyping==23.2.0", "libcst~=1.1.0", "ruff==0.12.12", - "autotyping==23.2.0", ] typing = [ # this is not pyright itself, but the python wrapper "pyright==1.1.336", + "pytz", # only used for type-checking, version does not matter "typing-extensions~=4.12.0", - # only used for type-checking, version does not matter - "pytz", -] -test = [ - "pytest~=8.3.2", - "pytest-cov~=4.0.0", - "pytest-asyncio~=0.24.0", - "looptime~=0.2.0", - "coverage[toml]~=7.6.0", ] build = [ - "wheel~=0.40.0", "build~=0.10.0", "twine~=5.1.1", + "wheel~=0.40.0", ] -[tool.setuptools.packages.find] -where = ["."] -include = ["disnake*"] - -[tool.nox] -script-venv-backend = "uv|virtualenv" - [tool.pdm] # Ignore `requires-python` warnings when locking, the latest versions of some # dependencies already require >=3.9 # See also https://pdm-project.org/en/latest/usage/config/#ignore-package-warnings -ignore_package_warnings = ["*"] +ignore_package_warnings = [ "*" ] [tool.pdm.scripts] docs = { cmd = "nox -Rs docs --", help = "Build the documentation for development" } lint = { cmd = "nox -Rs lint --", help = "Check all files for linting errors" } pyright = { cmd = "nox -Rs pyright --", help = "Run pyright" } setup_env = { cmd = "{pdm} install -G:all", help = "Set up the local environment and all dependencies" } -post_setup_env = { composite = ["python -m ensurepip --default-pip", "pre-commit install --install-hooks"] } +post_setup_env = { composite = [ + "python -m ensurepip --default-pip", + "pre-commit install --install-hooks", +] } test = { cmd = "nox -Rs test --", help = "Run pytest" } +[tool.setuptools.packages.find] +where = [ "." ] +include = [ "disnake*" ] + [tool.ruff] line-length = 100 -target-version = "py38" - -[tool.codespell] -skip = ['docs/_static/**', '*.po'] -ignore-words-list = ['GroupT', "OT",] - -[tool.ruff.lint] -select = [ - # commented out codes are intended to be enabled in future prs - "F", # pyflakes - "E", "W", # pycodestyle - # "D", # pydocstyle - "D2", # pydocstyle, docstring formatting - "D4", # pydocstyle, docstring structure/content - "ANN2", # flake8-annotations - "S", # flake8-bandit - "B", # flake8-bugbear - "C4", # flake8-comprehensions - "DTZ", # flake8-datetimez - # "EM", # flake8-errmsg - "G", # flake8-logging-format - # "RET", # flake8-return - # "SIM", # flake8-simplify - "TID251", # flake8-tidy-imports, replaces S404 - "TC", # flake8-type-checking - "RUF", # ruff specific exceptions - "PT", # flake8-pytest-style - "Q", # flake8-quotes - "RSE", # flake8-raise - "T20", # flake8-print +# commented out codes are intended to be enabled in future prs +lint.select = [ + "ANN2", # flake8-annotations + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "D2", # pydocstyle, docstring formatting + "D4", # pydocstyle, docstring structure/content + "DTZ", # flake8-datetimez + "E", # pyflakes + "F", # pyflakes + "G", # flake8-logging-format + "I", # isort "PGH", # pygrep-hooks "PLC", # pylint convention "PLE", # pylint error - # "PLR", # pylint refactor "PLW", # pylint warnings - "TRY002", "TRY004", "TRY201", # tryceratops - "I", # isort + "PT", # flake8-pytest-style + "Q", # flake8-quotes + "RSE", # flake8-raise + "RUF", # ruff specific exceptions + "S", # flake8-bandit + "T20", # flake8-print + "TC", # flake8-type-checking + "TID251", # flake8-tidy-imports, replaces S404 + "TRY002", + "TRY004", + "TRY201", # tryceratops + "W", # pycodestyle ] -ignore = [ - # star imports - "F403", - - # pydocstyle +lint.ignore = [ + "B026", # backwards star-arg unpacking + "B904", # within an except clause raise from error or from none + ## pydocstyle "D203", # incompat with D211 + "D205", # blank line required between summary and description "D213", # multiline docstring should start on second line, incompatible with D212 "D400", # first line ends in period, does not work with `|coro|` etc. + "D401", # first line of docstring should be in imperative mood "D415", # same thing but punctuation in general "D416", # section name should end with a colon, incompatible with D406 + "D417", # missing argument description in docstring - # unknown if this is actually an issue - "RUF006", # might not be an issue/very extreme cases - - # we keep __all__ and __slots__ (roughly) sorted by source, not alphabetically - "RUF022", - "RUF023", + "E501", # line too long + "E731", # assigning lambdas to variables + "E741", # ambiguous variable names - # calling subprocess with dynamic arguments is generally fine, the only way to avoid this is ignoring it - "S603", - - # partial executable paths (i.e. "git" instead of "/usr/bin/git") are fine - "S607", - - # ignore try-except-pass. Bare excepts are caught with E722 - "S110", + # star imports + "F403", # provide specific codes on type: ignore "PGH003", @@ -201,6 +184,15 @@ ignore = [ # import aliases are fixed by ruff "PLC0414", + # ignore non-to-level imports + "PLC0415", + + # pyright seems to catch this already + "PLE0237", + + # object does not implement hashing + "PLW1641", + # outer loop variables are overwritten by inner assignment target, these are mostly intentional "PLW2901", @@ -208,11 +200,25 @@ ignore = [ # pytest.warns() without a match variable is too broad "PT030", - # object does not implement hashing - "PLW1641", + # unknown if this is actually an issue + "RUF006", # might not be an issue/very extreme cases - # ignore non-to-level imports - "PLC0415", + # we keep __all__ and __slots__ (roughly) sorted by source, not alphabetically + "RUF022", + "RUF023", + + # ignore try-except-pass. Bare excepts are caught with E722 + "S110", + + "S311", # insecure RNG usage, we don't use these for security-related things + # calling subprocess with dynamic arguments is generally fine, the only way to avoid this is ignoring it + "S603", + + # partial executable paths (i.e. "git" instead of "/usr/bin/git") are fine + "S607", + + # temporary disabled, to fix later + "T201", # ignore imports that could be moved into type-checking blocks # (no real advantage other than possibly avoiding cycles, @@ -221,162 +227,93 @@ ignore = [ "TC002", "TC003", - "S311", # insecure RNG usage, we don't use these for security-related things - "PLE0237", # pyright seems to catch this already - - "E741", # ambiguous variable names - - # temporary disables, to fix later - "D205", # blank line required between summary and description - "D401", # first line of docstring should be in imperative mood - "D417", # missing argument description in docstring - "B904", # within an except clause raise from error or from none - "B026", # backwards star-arg unpacking - "E501", # line too long - "E731", # assigning lambdas to variables - "T201", # print statements ] -[tool.ruff.lint.per-file-ignores] -"disnake/__main__.py" = ["T201"] # print statements are okay in our simple cli -"disnake/i18n.py" = [ +lint.per-file-ignores."disnake/**.py" = [ + "PT", +] # this is not a module of pytest tests +lint.per-file-ignores."disnake/__main__.py" = [ + "T201", +] # print statements are okay in our simple cli +lint.per-file-ignores."disnake/i18n.py" = [ "B027", # lib bug. Excluded here because ruff does not have a --disable-noqa flag yet ] -"disnake/ui/select/*.py" = [ +lint.per-file-ignores."disnake/ui/select/*.py" = [ "F401", # unused imports. Excluded because there is a bug with ruff. ] -"disnake/**.py" = ["PT"] # this is not a module of pytest tests -"tests/*.py" = ["S101"] # use of assert is okay in test files -"scripts/*.py" = ["S101"] # use of assert is okay in scripts # we are not using noqa in the example files themselves -"examples/*.py" = [ +lint.per-file-ignores."examples/*.py" = [ + "ANN", # missing type annotations in examples is fine "B008", # do not perform function calls in argument defaults, this is how most commands work "PT", # this is not a module of pytest tests "S311", # pseudo-random generators aren't suitable for cryptographic purposes "T201", # print found, printing is okay in examples - "ANN", # missing type annotations in examples is fine ] -"examples/basic_voice.py" = ["S104"] # possible binding to all interfaces - -[tool.ruff.lint.isort] -combine-as-imports = true - -[tool.ruff.lint.pydocstyle] -convention = "numpy" - -[tool.ruff.lint.flake8-pytest-style] -fixture-parentheses = false -mark-parentheses = false +lint.per-file-ignores."examples/basic_voice.py" = [ + "S104", # possible binding to all interfaces +] +lint.per-file-ignores."scripts/*.py" = [ + "S101", # use of assert is okay in scripts +] +lint.per-file-ignores."tests/*.py" = [ + "S101", # use of assert is okay in test files +] -[tool.ruff.lint.flake8-tidy-imports.banned-api] -"subprocess".msg = "Consider possible security implications associated with the subprocess module." # replaces S404 +lint.flake8-annotations.ignore-fully-untyped = true -[tool.ruff.lint.flake8-bugbear] -extend-immutable-calls = [ +lint.flake8-bugbear.extend-immutable-calls = [ # this is immutable, except for storing locks, which are fine to share across contexts "disnake.webhook.async_.AsyncWebhookAdapter", ] -[tool.ruff.lint.flake8-annotations] -ignore-fully-untyped = true - -[tool.towncrier] -template = "changelog/_template.rst.jinja" -package = "disnake" -filename = "docs/whats_new.rst" -directory = "changelog/" -title_format = false -underlines = "-~" -issue_format = ":issue:`{issue}`" - - [[tool.towncrier.type]] - directory = "breaking" - name = "Breaking Changes" - showcontent = true - - [[tool.towncrier.type]] - directory = "deprecate" - name = "Deprecations" - showcontent = true - - [[tool.towncrier.type]] - directory = "feature" - name = "New Features" - showcontent = true - - [[tool.towncrier.type]] - directory = "bugfix" - name = "Bug Fixes" - showcontent = true +lint.flake8-pytest-style.fixture-parentheses = false +lint.flake8-pytest-style.mark-parentheses = false - [[tool.towncrier.type]] - directory = "doc" - name = "Documentation" - showcontent = true +lint.flake8-tidy-imports.banned-api."subprocess".msg = "Consider possible security implications associated with the subprocess module." # replaces S404 - [[tool.towncrier.type]] - directory = "misc" - name = "Miscellaneous" - showcontent = true +lint.isort.combine-as-imports = true +lint.pydocstyle.convention = "numpy" -[tool.slotscheck] -strict-imports = true -require-superclass = true -require-subclass = false -exclude-modules = ''' -( - ^disnake\.types\. -) -''' - +[tool.codespell] +skip = [ 'docs/_static/**', '*.po' ] +ignore-words-list = [ 'GroupT', "OT" ] -[tool.pyright] -typeCheckingMode = "strict" -include = [ - "disnake", - "docs", - "examples", - "scripts", - "tests", - "*.py", -] +[tool.check-manifest] ignore = [ - "disnake/ext/mypy_plugin", + # CI + ".libcst.codemod.yaml", + ".pre-commit-config.yaml", + ".readthedocs.yml", + "noxfile.py", + # docs + "CONTRIBUTING.md", + "RELEASE.md", + "assets/**", + "changelog/**", + "docs/**", + "examples/**", + # tests + "scripts/**", + "tests/**", ] -# this is one of the diagnostics that aren't enabled by default, even in strict mode -reportUnnecessaryTypeIgnoreComment = true - -# it's unlikely that these will ever be enabled -reportOverlappingOverload = false -reportPrivateUsage = false -reportUnnecessaryIsInstance = false -reportFunctionMemberAccess = false -reportMissingTypeStubs = false -reportUnusedFunction = false -reportUnusedClass = false -reportConstantRedefinition = false -reportImportCycles = false -reportIncompatibleMethodOverride = false -reportIncompatibleVariableOverride = false - -# these are largely due to missing type hints, and make up most of the error count -reportUnknownMemberType = false -reportUnknownParameterType = false -reportUnknownArgumentType = false -reportMissingParameterType = false -reportUnknownVariableType = false -reportMissingTypeArgument = false - +[tool.pyproject-fmt] +column_width = 40 +indent = 4 +keep_full_version = true +max_supported_python = "3.13" [tool.pytest.ini_options] minversion = "8.2" -pythonpath = "." # legacy configuration, new pytest versions don't add the rootdir to path +pythonpath = "." # legacy configuration, new pytest versions don't add the rootdir to path + testpaths = "tests" addopts = "--strict-markers -Werror -s" xfail_strict = true + asyncio_mode = "strict" +asyncio_default_fixture_loop_scope = "function" [tool.coverage.run] branch = true @@ -385,9 +322,9 @@ include = [ "tests/*", ] omit = [ - "disnake/ext/mypy_plugin/*", - "disnake/types/*", "disnake/__main__.py", + "disnake/types/*", + "disnake/ext/mypy_plugin/*", ] [tool.coverage.report] @@ -401,22 +338,68 @@ exclude_lines = [ "^\\s*\\.\\.\\.$", ] +[tool.towncrier] +template = "changelog/_template.rst.jinja" +package = "disnake" +filename = "docs/whats_new.rst" +directory = "changelog/" +title_format = false +underlines = "-~" +issue_format = ":issue:`{issue}`" -[tool.check-manifest] +type = [ + { directory = "breaking", name = "Breaking Changes", showcontent = true }, + { directory = "bugfix", name = "Bug Fixes", showcontent = true }, + { directory = "deprecate", name = "Deprecations", showcontent = true }, + { directory = "doc", name = "Documentation", showcontent = true }, + { directory = "feature", name = "New Features", showcontent = true }, + { directory = "misc", name = "Miscellaneous", showcontent = true }, +] + +[tool.pyright] +typeCheckingMode = "strict" +include = [ + "disnake", + "docs", + "examples", + "scripts", + "tests", + "*.py", +] ignore = [ - # CI - ".pre-commit-config.yaml", - ".readthedocs.yml", - ".libcst.codemod.yaml", - "noxfile.py", - # docs - "CONTRIBUTING.md", - "RELEASE.md", - "assets/**", - "changelog/**", - "docs/**", - "examples/**", - # tests - "tests/**", - "scripts/**", + "disnake/ext/mypy_plugin", ] + +# this is one of the diagnostics that aren't enabled by default, even in strict mode +reportUnnecessaryTypeIgnoreComment = true + +# it's unlikely that these will ever be enabled +reportConstantRedefinition = false +reportFunctionMemberAccess = false +reportImportCycles = false +reportIncompatibleMethodOverride = false +reportIncompatibleVariableOverride = false +reportMissingTypeStubs = false +reportOverlappingOverload = false +reportPrivateUsage = false +reportUnnecessaryIsInstance = false +reportUnusedClass = false +reportUnusedFunction = false + +# these are largely due to missing type hints, and make up most of the error count +reportMissingParameterType = false +reportMissingTypeArgument = false +reportUnknownArgumentType = false +reportUnknownMemberType = false +reportUnknownParameterType = false +reportUnknownVariableType = false + +[tool.slotscheck] +strict-imports = true +require-superclass = true +require-subclass = false +exclude-modules = ''' +( + ^disnake\.types\. +) +'''