Skip to main content

A @nodiscard decorator and static checker for Python — detect when return values of marked methods are silently discarded

Project description

PyPI version Python versions CI License: MIT

nodiscard

A @nodiscard decorator and static checker for Python — like Rust's #[must_use] or C++'s [[nodiscard]].

Problem

Frozen/immutable models return new instances from mutation methods. Forgetting to capture the return value is a silent no-op:

user = User(name="Alice")
user.rename("Bob")   # Bug! Returns a new User, but the result is silently discarded.
print(user.name)     # Still "Alice"

Quick Start

pip install nodiscard
from nodiscard import nodiscard

class User:
    @nodiscard
    def rename(self, name: str) -> "User":
        return User(name=name)
nodiscard check src/
# src/app.py:42:5: ND001 Return value of '@nodiscard' method 'rename' is discarded

Usage

Decorator

from nodiscard import nodiscard, NoDiscard
from typing import Annotated

class Schema:
    @nodiscard
    def merge(self, other: "Schema") -> "Schema": ...

    # Alternative: Annotated marker
    def replace(self, **kwargs: object) -> Annotated["Schema", NoDiscard]: ...

Works with @classmethod, @staticmethod, @abstractmethod, async def, and decorator stacking.

CLI

nodiscard check src/                       # Check a directory
nodiscard check src/models.py              # Check a single file
nodiscard check src/ --src src/            # Set import resolution root
nodiscard check src/ --exclude "tests/*"   # Exclude patterns
nodiscard check src/ --format json         # JSON output

Exit codes: 0 = no violations, 1 = violations found, 2 = error (e.g. path not found).

Configuration

# pyproject.toml
[tool.nodiscard]
src = ["src"]
exclude = ["tests/*", "migrations/*"]
format = "text"  # "text" or "json"

CLI arguments override pyproject.toml settings.

Rules

Code Name Description
ND001 discarded-nodiscard-return Return value of a @nodiscard method is discarded

Inline suppression

obj.method()  # nodiscard: ignore

pre-commit

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/KinjiKawaguchi/nodiscard
    rev: v0.1.0
    hooks:
      - id: nodiscard
        args: [check, src/]

Comparison

Feature Rust #[must_use] C++ [[nodiscard]] Python @nodiscard
Compile-time Yes Yes Static analysis
Functions Yes Yes Methods (v0.1)
Custom message Yes C++20 @nodiscard(reason="...")
Suppressible let _ = ... (void)expr _ = expr / # nodiscard: ignore

Limitations

  • v0.1 detects method calls only (not bare function calls)
  • Type inference is local-scope only (no full type resolution like mypy/pyright)
  • No IDE integration yet (LSP planned for future)
  • async task tracking (asyncio.create_task) is not analyzed

Contributing

  • DeepWiki — AI-generated codebase documentation
git clone https://github.com/KinjiKawaguchi/nodiscard.git
cd nodiscard
uv sync
uv run pytest --cov=nodiscard
uv run ruff check src/ tests/

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nodiscard-0.1.0.tar.gz (101.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nodiscard-0.1.0-py3-none-any.whl (17.4 kB view details)

Uploaded Python 3

File details

Details for the file nodiscard-0.1.0.tar.gz.

File metadata

  • Download URL: nodiscard-0.1.0.tar.gz
  • Upload date:
  • Size: 101.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for nodiscard-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ac8c6c805d41b390f7b079d2060791ded2799c52516b9efc544484dd678ea865
MD5 ee26c53c743d4e6c789a67261b73c5b1
BLAKE2b-256 f6e46e010d3b62dd3b459b8e138db41b87ba735df90693138bc2fd011333b0d1

See more details on using hashes here.

Provenance

The following attestation bundles were made for nodiscard-0.1.0.tar.gz:

Publisher: release.yml on KinjiKawaguchi/nodiscard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nodiscard-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: nodiscard-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for nodiscard-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 671b698aca12287eb9528244e9e1eb2c4c9b6484fa9ba55bc777a2f1850352f8
MD5 5903f2b41c93c256a19ac20f1fc707dc
BLAKE2b-256 2379661b05936062c810490a6fa9b8fb68aef8825f7ef97b2a48708b477783cb

See more details on using hashes here.

Provenance

The following attestation bundles were made for nodiscard-0.1.0-py3-none-any.whl:

Publisher: release.yml on KinjiKawaguchi/nodiscard

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page