Skip to content

Forbid functools.singledispatch / singledispatchmethod via Ruff banned-api#1361

Merged
nathanjmcdougall merged 3 commits intomainfrom
copilot/forbid-usage-of-functools-singledispatch
Mar 11, 2026
Merged

Forbid functools.singledispatch / singledispatchmethod via Ruff banned-api#1361
nathanjmcdougall merged 3 commits intomainfrom
copilot/forbid-usage-of-functools-singledispatch

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 11, 2026

functools.singledispatch has poor static analysis semantics. This PR bans it via Ruff's flake8-tidy-imports.banned-api and replaces all existing usages with explicit isinstance-based if-branch dispatch.

Config

Added to pyproject.toml:

lint.flake8-tidy-imports.banned-api."functools.singledispatch".msg = "Use if-branch isinstance logic instead of singledispatch."
lint.flake8-tidy-imports.banned-api."functools.singledispatchmethod".msg = "Use if-branch isinstance logic instead of singledispatchmethod."

Refactoring pattern

All @singledispatch / @singledispatchmethod usages replaced:

# Before
@singledispatch
def _as_dict(value: INIDocument | Section) -> ...:
    raise NotImplementedError

@_as_dict.register(INIDocument)
def _(value: INIDocument) -> ...:
    return {k: _as_dict(v) for k, v in value.items()}

@_as_dict.register(Section)
def _(value: Section) -> ...:
    return {option.key: option.value for option in value.iter_options()}

# After
def _as_dict(value: INIDocument | Section) -> ...:
    if isinstance(value, INIDocument):
        return {k: _as_dict(v) for k, v in value.items()}
    elif isinstance(value, Section):
        return {option.key: option.value for option in value.iter_options()}
    else:
        assert_never(value)

Files changed

  • _file/ini/io_.py_as_dict
  • _pipeweld/func.pyAdder.partition_component (method) and _get_instructions_for_insertion; complex branches extracted into _partition_series_component, _partition_parallel_component, _partition_depgroup_component
  • _integrations/ci/bitbucket/pipeweld.pyget_pipeweld_object, _extract_step_from_item, _insert_parallel_step, _is_insertion_necessary; extracted _extract_step_from_parallel_item
  • _integrations/ci/bitbucket/steps.py_censor_step, get_steps_in_pipeline_item; extracted _censor_parallel_item_step, _censor_stage_item_step
  • _integrations/pydantic/dump.pyfancy_model_dump; extracted _fancy_model_dump_list, _fancy_model_dump_dict, _fancy_model_dump_base_model

Where functions were large, helpers were extracted to stay within Ruff's branch/return-count limits (PLR0912, PLR0911). All dispatch exhaustiveness is enforced via assert_never.

Original prompt

This section details on the original issue you should resolve

<issue_title>Forbid usage of functools.singledispatch</issue_title>
<issue_description>Toward #1340.

singledispatch has semantics which don't play nicely with static analysis. Arguably, they're needlessly verbose.

I would prefer to refactor just use if-branch logic.

We should set up a Ruff rule for this, e.g. using example config from docs:

banned-api

Specific modules or module members that may not be imported or accessed. Note that this rule is only meant to flag accidental uses, and can be circumvented via eval or importlib.

Default value: {}

Type: dict[str, { "msg": str }]

Example usage:

pyproject.toml
ruff.toml

[lint.flake8-tidy-imports.banned-api]
"cgi".msg = "The cgi module is deprecated, see https://peps.python.org/pep-0594/#cgi."
"typing.TypedDict".msg = "Use typing_extensions.TypedDict instead."</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits March 11, 2026 01:38
…add Ruff banned-api config

Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com>
Co-authored-by: nathanjmcdougall <18602289+nathanjmcdougall@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor to forbid usage of functools.singledispatch Forbid functools.singledispatch / singledispatchmethod via Ruff banned-api Mar 11, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Mar 11, 2026

Merging this PR will not alter performance

✅ 2 untouched benchmarks


Comparing copilot/forbid-usage-of-functools-singledispatch (47127e7) with main (8152857)

Open in CodSpeed

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 11, 2026

Codecov Report

❌ Patch coverage is 97.29730% with 4 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/usethis/_integrations/ci/bitbucket/pipeweld.py 95.00% 3 Missing ⚠️
src/usethis/_integrations/pydantic/dump.py 92.85% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@nathanjmcdougall nathanjmcdougall marked this pull request as ready for review March 11, 2026 03:54
@nathanjmcdougall nathanjmcdougall merged commit 798fbbd into main Mar 11, 2026
20 checks passed
@nathanjmcdougall nathanjmcdougall deleted the copilot/forbid-usage-of-functools-singledispatch branch March 11, 2026 03:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Forbid usage of functools.singledispatch

2 participants