Skip to content

feat: add field-level alias - #194

Merged
gazorby merged 1 commit into
mainfrom
worktree-model-field-alias
Jun 12, 2026
Merged

feat: add field-level alias#194
gazorby merged 1 commit into
mainfrom
worktree-model-field-alias

Conversation

@gazorby

@gazorby gazorby commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • Added model_field parameter to schema field declarations, enabling explicit mapping of GraphQL fields to underlying model attributes for flexible field renaming and customization.
  • Tests

    • Added comprehensive test coverage for model field mapping and validation, including scenarios with duplicate targets, missing fields, and type overrides.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces explicit model_field mapping for GraphQL schema fields to alternative SQLAlchemy model attributes. StrawchemyField and Strawchemy.field() now accept a model_field parameter. A new factory helper validates these mappings against the model schema and detects conflicts. DTO configuration now merges and reverses aliases, and the updated type/input wrappers wire the new behavior. Deprecated alias parameters are removed from public surfaces and legacy usage emits warnings.

Changes

Model Field Mapping Implementation

Layer / File(s) Summary
Field Declaration Contract
src/strawchemy/schema/field.py, src/strawchemy/mapper.py
StrawchemyField.__init__ now accepts model_field: str | None = None, stored on the instance. Strawchemy.field() accepts and forwards model_field, disables root_field when provided, and is documented with validation expectations.
DTO Config Union and Kwargs
src/strawchemy/dto/types.py, src/strawchemy/schema/factories/_kwargs.py
DTOConfig.union() merges aliases from both configs. New exported LegacyKwargs TypedDict documents deprecated legacy aliasing; TypeDecoratorKwargs and InputDecoratorKwargs inherit LegacyKwargs to declare legacy parameters in public API.
Factory Alias Collection and Validation
src/strawchemy/schema/factories/base.py
New collect_field_model_aliases() helper scans class bodies for StrawchemyField declarations with model_field, validates targets against model schema, detects missing/duplicate/shadowing conflicts, and returns model→schema alias mappings. DTO config resolution now reverses-maps config aliases onto model annotations. _config() accepts model and class_ parameters and emits deprecation warning for legacy aliases= usage.
Factory Integration: Type and Input Wiring
src/strawchemy/schema/factories/base.py
_type_wrapper and _input_wrapper now pass model and class_ into _config(), wiring the new alias collection behavior into both GraphQL type and input DTO generation paths.
Type Factory Field Processing
src/strawchemy/schema/factories/types.py
ObjectTypeFactory._add_fields_arguments excludes StrawchemyField instances with non-None model_field from annotation override suppression, preserving resolver-driven return type annotations for model-mapped fields.
API Surface Cleanup
src/strawchemy/dto/base.py, src/strawchemy/validation/pydantic.py
DTOFactory.decorator() no longer accepts aliases and alias_generator parameters. StrawchemyMutationInputValidationFactory.input() TYPE_CHECKING signature removes alias-related parameters.
Comprehensive Test Coverage
tests/unit/mapping/test_model_field.py, tests/unit/schemas/model_field/*
New test suite validates strawchemy.field(model_field=...) storage and non-root enforcement, collect_field_model_aliases() behavior and error detection for missing/duplicate targets, type/input-level field renaming with model linkage, deprecated aliases= parameter renaming with DeprecationWarning, alias_generator= renaming without warnings, annotation overrides, and import-time error cases.
CI Configuration Formatting
mise.toml
test:patch-coverage and clean tasks updated with consistent indentation/formatting without changing command contents.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops through mapping fields,
Where model names reveal their hidden shields,
Aliases merge and errors ring alarm,
When duplicates dare to cause a charm!
New paths are wired, old ones farewell,
GraphQL schema now maps quite well.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add field-level alias' accurately describes the main feature being added—field-level aliasing capability—which is demonstrated across multiple files including new model_field parameter support and alias collection logic.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-model-field-alias

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.77778% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 93.15%. Comparing base (a1b6052) to head (5c7ff13).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/strawchemy/schema/factories/base.py 97.50% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #194      +/-   ##
==========================================
+ Coverage   93.06%   93.15%   +0.09%     
==========================================
  Files          69       69              
  Lines        6215     6257      +42     
  Branches      821      834      +13     
==========================================
+ Hits         5784     5829      +45     
+ Misses        288      286       -2     
+ Partials      143      142       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/strawchemy/schema/field.py (1)

429-460: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve model_field when cloning StrawchemyField.

Line 429 reconstructs the field but omits model_field, so copied instances silently reset this mapping to None.

💡 Proposed fix
         new_field = type(self)(
             python_name=self.python_name,
             graphql_name=self.graphql_name,
             type_annotation=self.type_annotation,
             origin=self.origin,
             is_subscription=self.is_subscription,
             description=self.description,
             base_resolver=self.base_resolver,
             permission_classes=(self.permission_classes[:] if self.permission_classes is not None else []),
             default=self.default_value,
             default_factory=self.default_factory,
             metadata=self.metadata.copy() if self.metadata is not None else None,
             deprecation_reason=self.deprecation_reason,
             directives=self.directives[:] if self.directives is not None else [],
             extensions=self.extensions[:] if self.extensions is not None else [],
             filter_statement=self._filter_statement,
             query_hook=self.query_hook,
             id_field_name=self.id_field_name,
+            model_field=self.model_field,
             repository_type=self._repository_type,
             root_aggregations=self.root_aggregations,
             filter_type=self._filter,
             order_by=self._order_by,
             distinct_on=self._distinct_on,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/strawchemy/schema/field.py` around lines 429 - 460, The __copy__
implementation for StrawchemyField reconstructs a new instance but omits
preserving the model_field, causing it to be reset; update the __copy__ method
(in class StrawchemyField) to pass model_field=self.model_field (or a shallow
copy if appropriate) into the type(self)(...) constructor so the cloned field
retains the original model_field mapping.
src/strawchemy/dto/types.py (1)

328-339: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

DTOConfig.union() can now build an invalid config (aliases + alias_generator).

After Line 328 merges aliases, copy_with() preserves self.alias_generator; __post_init__ then raises ValueError when both are set. This breaks composition paths that add alias deltas later (e.g., field-level model_field mappings).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/strawchemy/dto/types.py` around lines 328 - 339, DTOConfig.union
currently merges aliases and then calls copy_with which preserves
self.alias_generator, causing __post_init__ to raise when both aliases and
alias_generator are set; update DTOConfig.union to ensure the resulting config
does not have both set by clearing alias_generator (or removing aliases) on the
result when aliases is non-empty. Specifically, in DTOConfig.union adjust the
logic around aliases = {**self.aliases, **other.aliases} before calling
copy_with so that you pass alias_generator=None (or remove conflicting aliases)
into copy_with when aliases is not empty; reference DTOConfig.union, copy_with,
__post_init__, alias_generator, and aliases to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/strawchemy/schema/factories/base.py`:
- Around line 173-185: The DTO factory currently adds aliased model fields into
include via config = config | DTOConfig(...), but it doesn't remove them from
exclude/global_exclude so explicit excludes still override the intended
model_field mapping; update the merge so that when you add extra_include you
also remove those model field names from config.exclude and
config.global_exclude (e.g., compute new_exclude = config.exclude -
extra_include and new_global_exclude = config.global_exclude - extra_include)
and create the new DTOConfig using include=extra_include and exclude=new_exclude
(and global_exclude=new_global_exclude) so the aliased model fields win as
intended while still preserving other config properties (reference symbols:
DTOConfig, config, include, exclude, global_exclude, model_field,
annotation_overrides, extra_include).

---

Outside diff comments:
In `@src/strawchemy/dto/types.py`:
- Around line 328-339: DTOConfig.union currently merges aliases and then calls
copy_with which preserves self.alias_generator, causing __post_init__ to raise
when both aliases and alias_generator are set; update DTOConfig.union to ensure
the resulting config does not have both set by clearing alias_generator (or
removing aliases) on the result when aliases is non-empty. Specifically, in
DTOConfig.union adjust the logic around aliases = {**self.aliases,
**other.aliases} before calling copy_with so that you pass alias_generator=None
(or remove conflicting aliases) into copy_with when aliases is not empty;
reference DTOConfig.union, copy_with, __post_init__, alias_generator, and
aliases to locate the change.

In `@src/strawchemy/schema/field.py`:
- Around line 429-460: The __copy__ implementation for StrawchemyField
reconstructs a new instance but omits preserving the model_field, causing it to
be reset; update the __copy__ method (in class StrawchemyField) to pass
model_field=self.model_field (or a shallow copy if appropriate) into the
type(self)(...) constructor so the cloned field retains the original model_field
mapping.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3a056f56-94d2-41d0-95f6-384c906369b0

📥 Commits

Reviewing files that changed from the base of the PR and between a1b6052 and 5c7ff13.

📒 Files selected for processing (13)
  • mise.toml
  • src/strawchemy/dto/base.py
  • src/strawchemy/dto/types.py
  • src/strawchemy/mapper.py
  • src/strawchemy/schema/factories/_kwargs.py
  • src/strawchemy/schema/factories/base.py
  • src/strawchemy/schema/factories/types.py
  • src/strawchemy/schema/field.py
  • src/strawchemy/validation/pydantic.py
  • tests/unit/mapping/test_model_field.py
  • tests/unit/schemas/model_field/__init__.py
  • tests/unit/schemas/model_field/duplicate_target.py
  • tests/unit/schemas/model_field/missing_model_field.py
💤 Files with no reviewable changes (2)
  • src/strawchemy/dto/base.py
  • src/strawchemy/validation/pydantic.py

Comment thread src/strawchemy/schema/factories/base.py
@gazorby
gazorby merged commit e8ef6a3 into main Jun 12, 2026
96 checks passed
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.

1 participant