Skip to content

fix: ModelSchema doesn't apply alias_generator to foreign key fields - #1747

Open
shivanij1203 wants to merge 2 commits into
vitalik:masterfrom
shivanij1203:fix/foreignkey-alias-generator
Open

fix: ModelSchema doesn't apply alias_generator to foreign key fields#1747
shivanij1203 wants to merge 2 commits into
vitalik:masterfrom
shivanij1203:fix/foreignkey-alias-generator

Conversation

@shivanij1203

Copy link
Copy Markdown

Summary

When a ModelSchema sets an alias_generator (e.g. to_camel), regular fields are aliased but a foreign key's generated _id field is not — it stays snake_case while everything else is transformed:

{
  "id": "...",
  "model_a_id": ...,        // stays snake_case
  "myOtherProperty": "..."  // camelCased
}

The reason is that relation fields set their alias explicitly (to the _id attname) in get_schema_field. In pydantic v2 an explicit alias takes precedence over the model's alias_generator, so the generator never runs on the id field.

The fix threads the schema's alias_generator (from the base class' model_config) into relation-field construction and applies it to the attname, so the id field is aliased consistently with the rest of the schema. When no generator is configured, behavior is unchanged (model_a_id).

Fixes #1691

Changes

  • ninja/orm/fields.py: get_schema_field accepts an optional alias_generator and applies it to a relation's _id attname (supports a plain callable and AliasGenerator).
  • ninja/orm/factory.py: read alias_generator from the base class' model_config and pass it through when building fields.
  • tests/test_orm_relations.py: regression test asserting a FK's id field is camelCased like other fields.

Testing

  • pytest tests/test_orm_relations.py tests/test_orm_metaclass.py tests/test_models.py passes.
  • The new test fails against main (the FK alias is target_id instead of targetId) and passes with the fix.
  • ruff format --check, ruff check, and mypy ninja are all clean.

A foreign key's generated _id field sets its alias explicitly, which bypasses the schema's alias_generator, so it stays snake_case while every other field is transformed (e.g. to camelCase). Apply the generator to the relation attname so the id field is aliased consistently. Closes vitalik#1691.

@mbaragiola mbaragiola left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker

  • ninja/orm/fields.py:122: AliasGenerator supports independent alias, validation_alias, and serialization_alias callbacks, but _apply_alias_generator() consults only .alias. If .alias is unset—for example, AliasGenerator(validation_alias=to_camel, serialization_alias=to_pascal)—the foreign-key field keeps target_id for both directions while ordinary fields receive their configured aliases. Please generate and assign the validation and serialization aliases independently, and add runtime validation plus model_dump(by_alias=True) coverage rather than checking only field metadata.

Positives

  • The common callable alias_generator=to_camel case is addressed.
  • The test compares the foreign-key alias with an ordinary generated field.

Verification

I reviewed the factory/field path and Pydantic’s AliasGenerator.generate_aliases() behavior. GitHub currently reports no checks for this head; I did not run the project suite locally.

Reviewed by Hermes Agent using Codex gpt-5.6-sol.

AliasGenerator can set alias, validation_alias and serialization_alias
independently, but the fk _id field only consulted .alias, so a generator
without a plain .alias left the field snake_case in both directions while
ordinary fields were aliased. Generate and assign each direction separately,
falling back to the _id attname where the generator leaves a direction unset.
@shivanij1203

Copy link
Copy Markdown
Author

Good catch, thanks. Reworked this so the _id field goes through pydantic's own AliasGenerator.generate_aliases() instead of only reading .alias. alias, validation_alias, and serialization_alias are now generated and assigned independently, and each direction the generator leaves unset falls back to the _id attname (so a single-callback generator like alias_generator=to_camel still covers both directions, unchanged from before).

For the AliasGenerator(validation_alias=to_camel, serialization_alias=to_pascal) case you flagged, the author_id field now gets authorId for validation and AuthorId for serialization rather than staying author_id in both.

Also swapped the test from metadata-only checks to a behavioral one: it validates via the camelCase validation alias (model_validate) and asserts the PascalCase serialization alias comes back out of model_dump(by_alias=True), alongside a plain field for comparison. mypy ninja and ruff (format + check) are clean.

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] camelCase not supported for relations on ModelSchema

2 participants