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.1.tar.gz (107.1 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.1-py3-none-any.whl (17.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: nodiscard-0.1.1.tar.gz
  • Upload date:
  • Size: 107.1 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.1.tar.gz
Algorithm Hash digest
SHA256 9ee63fe3f42ec1863d2a1081c79820be0e36363a0229294c9637e4a2fe370b68
MD5 803a75664f650ed9f96d88bfa97bdfa0
BLAKE2b-256 45a944e60fc457d414450532fe954cd1d3512fcf0bb5255fe1cf00ff506d863d

See more details on using hashes here.

Provenance

The following attestation bundles were made for nodiscard-0.1.1.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.1-py3-none-any.whl.

File metadata

  • Download URL: nodiscard-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 17.6 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b692e0d9e08d64f13931871d206ec53cc39f734adc3398ae4de45b673696d5ad
MD5 32e21a296dc760c2aa8cabf759127fe7
BLAKE2b-256 53087a1c1c1b443abecc17d1c2d06c5c8d7162d345d127670993b1eb848bf9d0

See more details on using hashes here.

Provenance

The following attestation bundles were made for nodiscard-0.1.1-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