Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[ty] Avoid enforcing __new__ with custom metaclasses#25180

Merged
charliermarsh merged 2 commits into
mainfrom
charlie/enum-metaclass-new
May 16, 2026
Merged

[ty] Avoid enforcing __new__ with custom metaclasses#25180
charliermarsh merged 2 commits into
mainfrom
charlie/enum-metaclass-new

Conversation

@charliermarsh

@charliermarsh charliermarsh commented May 15, 2026

Copy link
Copy Markdown
Member

Summary

This PR makes enum member validation account for custom EnumMeta.__new__ methods. Prior to this change, we validated the raw right-hand side of an enum member against inherited _value_, __new__, or __init__. But that's too strict for Django’s IntegerChoices pattern...

from django.db import models

class Status(models.IntegerChoices):
    GOOD = 1, "I like this"

Django’s custom metaclass strips the label out of the member value before IntEnum construction, so the raw (1, "I like this") tuple is not the value that should be checked against int.__new__.

We now detect a custom enum metaclass __new__ and treat member values as potentially transformed. In that case, we skip raw-RHS validation against enum construction hooks, and .value / ._value_ falls back to Any (unless the enum class itself declares an explicit _value_ annotation).

Pyright has similar behavior here: https://github.com/microsoft/pyright/blob/63998f4d0720a86447f4c4a04716e34f3e703660/packages/pyright-internal/src/analyzer/enums.ts#L653-L660

But we do differ in this case:

class Status(IntegerChoices):
    _value_: int
    GOOD = 1, "label"

Pyright exposes Status.GOOD.value as int, but still reports that the raw tuple is not assignable to int. We expose Status.GOOD.value as int, and don't report anything.

Closes astral-sh/ty#3468.

@astral-sh-bot astral-sh-bot Bot added the ty Multi-file analysis & type inference label May 15, 2026
@charliermarsh charliermarsh changed the title [ty] Avoid enforcing __new__ with custom metaclasses [ty] Avoid enforcing __new__ with custom metaclasses May 15, 2026
@astral-sh-bot

astral-sh-bot Bot commented May 15, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 89.36%. The percentage of expected errors that received a diagnostic held steady at 85.49%. The number of fully passing files held steady at 88/134.

@astral-sh-bot

astral-sh-bot Bot commented May 15, 2026

Copy link
Copy Markdown

Memory usage report

Summary

Project Old New Diff Outcome
prefect 685.20MB 685.25MB +0.01% (50.18kB)
sphinx 256.23MB 256.24MB +0.01% (13.27kB)
trio 115.58MB 115.58MB +0.00% (5.23kB)
flake8 47.43MB 47.43MB +0.00% (1.49kB)

Significant changes

Click to expand detailed breakdown

prefect

Name Old New Diff Outcome
enum_metadata 2.83MB 2.88MB +1.69% (49.06kB)
StaticClassLiteral<'db>::try_metaclass_ 1.40MB 1.41MB +0.06% (888.00B)
StaticClassLiteral<'db>::inheritance_cycle_ 378.28kB 378.54kB +0.07% (264.00B)

sphinx

Name Old New Diff Outcome
enum_metadata 715.90kB 728.48kB +1.76% (12.59kB)
StaticClassLiteral<'db>::try_metaclass_ 387.40kB 387.92kB +0.14% (536.00B)
StaticClassLiteral<'db>::inheritance_cycle_ 100.36kB 100.52kB +0.16% (168.00B)

trio

Name Old New Diff Outcome
enum_metadata 235.32kB 240.12kB +2.04% (4.79kB)
StaticClassLiteral<'db>::try_metaclass_ 139.28kB 139.62kB +0.25% (352.00B)
StaticClassLiteral<'db>::inheritance_cycle_ 34.66kB 34.76kB +0.27% (96.00B)

flake8

Name Old New Diff Outcome
enum_metadata 66.12kB 67.61kB +2.26% (1.49kB)

@astral-sh-bot

astral-sh-bot Bot commented May 15, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

Lint rule Added Removed Changed
unused-ignore-comment 2 0 0
Total 2 0 0

Raw diff:

django-stubs (https://github.com/typeddjango/django-stubs)
+ tests/assert_type/db/models/test_enums.py:244:75 warning[unused-ignore-comment] Unused `ty: ignore` directive
+ tests/assert_type/db/models/test_enums.py:309:47 warning[unused-ignore-comment] Unused `ty: ignore` directive

Full report with detailed diff (timing results)

@charliermarsh charliermarsh marked this pull request as ready for review May 15, 2026 15:02
@charliermarsh charliermarsh merged commit a7ab646 into main May 16, 2026
59 checks passed
@charliermarsh charliermarsh deleted the charlie/enum-metaclass-new branch May 16, 2026 04:45
thejchap pushed a commit to thejchap/ruff that referenced this pull request May 23, 2026
## Summary

This PR makes enum member validation account for custom
`EnumMeta.__new__` methods. Prior to this change, we validated the raw
right-hand side of an enum member against inherited `_value_,`
`__new__,` or `__init__`. But that's too strict for Django’s
`IntegerChoices` pattern...

```python
from django.db import models

class Status(models.IntegerChoices):
    GOOD = 1, "I like this"
```

Django’s custom metaclass strips the label out of the member value
before `IntEnum` construction, so the raw `(1,
"I like this")` tuple is _not_ the value that should be checked against
`int.__new__`.

We now detect a custom enum metaclass `__new__` and treat member values
as potentially transformed. In that case, we skip raw-RHS validation
against enum construction hooks, and `.value` / `._value_` falls back to
`Any` (unless the enum class itself declares an explicit `_value_`
annotation).

Pyright has similar behavior here:
https://github.com/microsoft/pyright/blob/63998f4d0720a86447f4c4a04716e34f3e703660/packages/pyright-internal/src/analyzer/enums.ts#L653-L660

But we do differ in this case:

```python
class Status(IntegerChoices):
    _value_: int
    GOOD = 1, "label"
```

Pyright exposes `Status.GOOD.value` as `int`, but still reports that the
raw tuple is not assignable to `int`. We expose `Status.GOOD.value` as
`int`, and don't report anything.

Closes astral-sh/ty#3468.
anishgirianish pushed a commit to anishgirianish/ruff that referenced this pull request May 28, 2026
## Summary

This PR makes enum member validation account for custom
`EnumMeta.__new__` methods. Prior to this change, we validated the raw
right-hand side of an enum member against inherited `_value_,`
`__new__,` or `__init__`. But that's too strict for Django’s
`IntegerChoices` pattern...

```python
from django.db import models

class Status(models.IntegerChoices):
    GOOD = 1, "I like this"
```

Django’s custom metaclass strips the label out of the member value
before `IntEnum` construction, so the raw `(1,
"I like this")` tuple is _not_ the value that should be checked against
`int.__new__`.

We now detect a custom enum metaclass `__new__` and treat member values
as potentially transformed. In that case, we skip raw-RHS validation
against enum construction hooks, and `.value` / `._value_` falls back to
`Any` (unless the enum class itself declares an explicit `_value_`
annotation).

Pyright has similar behavior here:
https://github.com/microsoft/pyright/blob/63998f4d0720a86447f4c4a04716e34f3e703660/packages/pyright-internal/src/analyzer/enums.ts#L653-L660

But we do differ in this case:

```python
class Status(IntegerChoices):
    _value_: int
    GOOD = 1, "label"
```

Pyright exposes `Status.GOOD.value` as `int`, but still reports that the
raw tuple is not assignable to `int`. We expose `Status.GOOD.value` as
`int`, and don't report anything.

Closes astral-sh/ty#3468.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ty 0.0.36 causes apparent regression with Django IntegerChoices

2 participants