Skip to content

fix(helpers): coerce Enum primary keys to .value in get_object_identifier#1096

Open
AdarshJ173 wants to merge 1 commit into
smithyhq:mainfrom
AdarshJ173:fix/enum-pk-object-identifier
Open

fix(helpers): coerce Enum primary keys to .value in get_object_identifier#1096
AdarshJ173 wants to merge 1 commit into
smithyhq:mainfrom
AdarshJ173:fix/enum-pk-object-identifier

Conversation

@AdarshJ173

Copy link
Copy Markdown

Summary

Fixes #955get_object_identifier() returns AnimalEnum.DOG instead of "dog" when the model primary key is an enum.Enum, breaking all edit/detail URLs for Enum-PK models.

Root cause

get_object_identifier() calls getattr(obj, pk.name) which returns the raw Enum instance as stored by SQLAlchemy (e.g. AnimalEnum.DOG). The single-PK fast-path returns this directly; the multi-PK path passes it through str(), which produces "AnimalEnum.DOG" rather than the underlying value "dog". Both paths therefore generate malformed admin URLs:

/admin/animal/edit/AnimalEnum.DOG   ← broken
/admin/animal/edit/dog              ← correct

What changed

sqladmin/helpers.py

Added a private _coerce_pk_value() helper that unwraps .value from any enum.Enum instance and is a no-op for all other types:

def _coerce_pk_value(value: Any) -> Any:
    if isinstance(value, enum.Enum):
        return value.value
    return value

get_object_identifier() now applies _coerce_pk_value() to each PK value before returning or joining:

- values = [getattr(obj, pk.name) for pk in primary_keys]
+ values = [_coerce_pk_value(getattr(obj, pk.name)) for pk in primary_keys]
  • enum is already imported at the top of the module — no new imports.
  • The helper is placed immediately before get_object_identifier() so it reads as a unit.
  • No other call-sites are affected.

Acceptance criteria

  • get_object_identifier(dog) returns "dog" (not AnimalEnum.DOG) for a str-Enum PK
  • get_object_identifier(status) returns 1 (not StatusEnum.ACTIVE) for an int-Enum PK
  • get_object_identifier(obj) continues to return the raw value for int, str, and UUID PKs (no regression)
  • Multi-PK models with mixed Enum + non-Enum PKs serialise correctly

How to test

from enum import Enum
from sqladmin.helpers import get_object_identifier

class AnimalEnum(str, Enum):
    DOG = "dog"
    CAT = "cat"

class StatusEnum(int, Enum):
    ACTIVE = 1
    INACTIVE = 2

# Simulate a model instance with an Enum PK
class FakeAnimal:
    id = AnimalEnum.DOG

class FakeStatus:
    id = StatusEnum.ACTIVE

# Before this fix both assertions failed:
assert get_object_identifier(FakeAnimal()) == "dog"
assert get_object_identifier(FakeStatus()) == 1

(Full integration test: create an Animal SQLModel with id: AnimalEnum as PK, add it to an sqladmin view, open the admin list, click Edit — URL should be /admin/animal/edit/dog, not /admin/animal/edit/AnimalEnum.DOG.)

Notes

  • No schema changes
  • No new dependencies
  • No server/API changes
  • No breaking changes — _coerce_pk_value is a no-op for all non-Enum values; existing integer, string, and UUID primary keys are unaffected
  • enum module was already imported at the top of helpers.py

…fier

When a model uses an Enum as its primary key, getattr() returns the Enum
instance (e.g. AnimalEnum.DOG). Passing this raw instance into URL
construction produces paths like /admin/animal/edit/AnimalEnum.DOG
instead of /admin/animal/edit/dog, breaking all edit/detail routes for
such models.

Fix: extract .value from any enum.Enum instance before returning or
serialising. This is done via a small _coerce_pk_value() helper so the
logic is applied consistently in both the single-PK fast-path and the
multi-PK join path.

Closes smithyhq#955
@mmzeynalli

Copy link
Copy Markdown
Member

This is duplicate of #1092, although this is better solution. If possible merge the tests of that PR to your PR (as you are missing tests). Do not copy, preferrably merge.

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.

bug: get_object_identifier returns Enum instance instead of string value for Enum primary keys

2 participants