Skip to content

Remove deprecated ScalarWrapper and scalar class wrapping#4227

Open
Ckk3 wants to merge 9 commits intostrawberry-graphql:mainfrom
Ckk3:deprecation/scalar-wrapper
Open

Remove deprecated ScalarWrapper and scalar class wrapping#4227
Ckk3 wants to merge 9 commits intostrawberry-graphql:mainfrom
Ckk3:deprecation/scalar-wrapper

Conversation

@Ckk3
Copy link
Collaborator

@Ckk3 Ckk3 commented Feb 10, 2026

Description

Remove the deprecated strawberry.scalar(cls, ...) wrapper pattern and ScalarWrapper, deprecated since 0.288.0.

You can run strawberry upgrade replace-scalar-wrappers <path> to automatically replace built-in scalar wrapper imports.

Migration guide

Before (deprecated):

import strawberry
from datetime import datetime

EpochDateTime = strawberry.scalar(
    datetime,
    serialize=lambda v: int(v.timestamp()),
    parse_value=lambda v: datetime.fromtimestamp(v),
)

@strawberry.type
class Query:
    created: EpochDateTime

After:

import strawberry
from typing import NewType
from datetime import datetime
from strawberry.schema.config import StrawberryConfig

EpochDateTime = NewType("EpochDateTime", datetime)

@strawberry.type
class Query:
    created: datetime

schema = strawberry.Schema(
    query=Query,
    config=StrawberryConfig(
        scalar_map={
            EpochDateTime: strawberry.scalar(
                name="EpochDateTime",
                serialize=lambda v: int(v.timestamp()),
                parse_value=lambda v: datetime.fromtimestamp(v),
            )
        }
    ),
)

Types of Changes

  • Core
  • Bugfix
  • New feature
  • Enhancement/optimization
  • Documentation

Checklist

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • I have tested the changes and verified that they work and don't break anything (as well as I can manage).

Summary by Sourcery

Remove the deprecated scalar class wrapper API in favor of ScalarDefinition-based configuration and scalar_map usage across the codebase.

New Features:

  • Generate schema config scalar_map entries for custom scalars during schema codegen.

Bug Fixes:

  • Ensure query code generation correctly records original Python types for scalars when collecting scalar metadata.

Enhancements:

  • Simplify scalar handling by eliminating ScalarWrapper, updating scalar registries to work purely with ScalarDefinition, and adjusting federation scalar helpers accordingly.

Documentation:

  • Update scalar, schema, pydantic, error, and query documentation to describe the scalar_map-based configuration and removal of class-wrapping scalar usage.

Tests:

  • Refactor tests to use NewType plus scalar_map or scalar_overrides instead of the removed scalar class wrapper pattern, and add expectations for updated schema codegen output.

Chores:

  • Add a minor release note describing the removal of scalar wrappers and the migration path.

@Ckk3 Ckk3 self-assigned this Feb 10, 2026
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 10, 2026

Reviewer's Guide

Remove the deprecated strawberry.scalar(cls, ...) wrapper pattern and ScalarWrapper, standardizing scalar handling on ScalarDefinition plus StrawberryConfig.scalar_map, and updating schema/codegen, federation helpers, runtime, tests, and docs accordingly.

File-Level Changes

Change Details Files
Schema codegen now emits NewType-based scalars and wires them through StrawberryConfig.scalar_map instead of ScalarWrapper-based scalars.
  • Introduce ScalarInfo dataclass to capture scalar metadata (name, description, specifiedBy URL) during parsing.
  • Change _get_scalar_definition to emit only a NewType assignment and return both Definition and ScalarInfo for non-built-in scalars.
  • Accumulate scalar_infos in codegen() and pass them (plus imports) into _get_schema_definition.
  • Extend _get_schema_definition to generate a StrawberryConfig(scalar_map={...}) argument on strawberry.Schema with per-scalar strawberry.scalar(...) calls and identity serialize/parse_value lambdas for custom scalars.
  • Update scalar-related schema codegen tests to expect NewType definitions and schema config with scalar_map.
strawberry/schema_codegen/__init__.py
tests/schema_codegen/test_scalar.py
Remove ScalarWrapper and class-wrapping scalar API, making strawberry.scalar/strawberry.federation.scalar return ScalarDefinition or decorators only, and adjust all type hints and call sites to use ScalarDefinition and scalar_map/scalar_overrides.
  • Delete ScalarWrapper implementation and internal _process_scalar helper; restrict scalar TypeVar _T to bound=type.
  • Simplify strawberry.types.scalar.scalar() overloads to two forms: returning ScalarDefinition when name is provided, or a no-op decorator for typing when name is omitted; remove deprecated cls parameter behavior and deprecation warnings.
  • Change strawberry.federation.scalar to mirror the new API (no cls param), build ScalarDefinition directly with federation directives, and provide a decorator form for typing only.
  • Update all exports in scalar modules to drop ScalarWrapper.
  • Adjust GraphQLCoreConverter and related functions (get_arguments, convert_argument(s), _is_leaf_type, is_scalar, is_scalar in compat, etc.) so scalar registries/maps are Mapping[object, ScalarDefinition] and no longer handle ScalarWrapper.
  • Remove ScalarWrapper-specific handling from schema printer, query code generator, exceptions.InvalidUnionTypeError, and federation.Schema scalar_overrides types.
strawberry/types/scalar.py
strawberry/federation/scalar.py
strawberry/schema/schema_converter.py
strawberry/codegen/query_codegen.py
strawberry/printer/printer.py
strawberry/exceptions/invalid_union_type.py
strawberry/federation/schema.py
strawberry/schema/schema.py
strawberry/types/arguments.py
strawberry/scalars.py
strawberry/schema/compat.py
Migrate tests, fixtures, and docs from scalar wrappers to NewType + StrawberryConfig.scalar_map or ScalarDefinition-based scalar_overrides, ensuring behavior and error semantics are preserved.
  • Replace usages of strawberry.scalar(cls, ...) in tests with NewType definitions plus StrawberryConfig.scalar_map or scalar_overrides entries that call strawberry.scalar(name=..., ...).
  • Update federation directive tests on scalars (authenticated, inaccessible, policy, requires_scopes, tag) to use NewType scalars configured through strawberry.federation.scalar(name=..., ...) in scalar_map.
  • Adjust tests that relied on ScalarWrapper types in annotations (e.g., duplicate scalar registration, directives with scalar arguments, name converter, query arguments) to instead annotate with NewType types and configure scalars via scalar_map.
  • Update sample_package fixture to define ExampleScalar as a NewType and create_schema() to pass StrawberryConfig(scalar_map={...}); fix locate_definition tests/snapshots that depend on line numbers and scalar positions.
  • Modify pydantic integration, general queries, scalar error docs, and type/schema docs to describe the new scalar configuration pattern (NewType + scalar_map/scalar_overrides) and updated signature of scalar_overrides (Dict[object, ScalarDefinition]).
  • Update typechecker tests to reflect that strawberry.scalar(name=...) now returns ScalarDefinition, including expected revealed type messages.
tests/schema/test_scalars.py
tests/schema/test_custom_scalar.py
tests/test_printer/test_schema_directives.py
tests/federation/printer/test_requires_scopes.py
tests/federation/printer/test_policy.py
tests/federation/printer/test_authenticated.py
tests/federation/printer/test_tag.py
tests/federation/printer/test_link.py
tests/schema/test_directives.py
tests/schema/test_name_converter.py
tests/fixtures/sample_package/sample_module.py
tests/utils/test_locate_definition.py
tests/cli/test_locate_definition.py
tests/fields/test_arguments.py
tests/objects/generics/test_names.py
tests/codegen/conftest.py
tests/typecheckers/test_scalars.py
docs/errors/scalar-already-registered.md
docs/integrations/pydantic.md
docs/general/queries.md
docs/types/schema.md
Add release notes documenting the removal of ScalarWrapper and class-based scalar wrappers, with a migration guide to NewType + StrawberryConfig.scalar_map.
  • Create RELEASE.md marking this as a minor release and explaining the removal of strawberry.scalar(cls, ...) and ScalarWrapper.
  • Document the recommended migration path using NewType plus StrawberryConfig.scalar_map, including example code before and after, and mention the strawberry upgrade replace-scalar-wrappers CLI helper.
RELEASE.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@Ckk3 Ckk3 force-pushed the deprecation/scalar-wrapper branch from c74eb8b to 062d67d Compare February 10, 2026 19:51
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 10, 2026

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing Ckk3:deprecation/scalar-wrapper (d541011) with main (e0764c2)

Open in CodSpeed

@Ckk3 Ckk3 marked this pull request as ready for review February 15, 2026 20:07
@botberry
Copy link
Member

botberry commented Feb 15, 2026

Thanks for adding the RELEASE.md file!

Here's a preview of the changelog:


Remove deprecated strawberry.scalar(cls, ...) wrapper pattern and ScalarWrapper, deprecated since 0.288.0.

You can run strawberry upgrade replace-scalar-wrappers <path> to automatically replace built-in scalar wrapper imports.

Migration guide

Before (deprecated):

import strawberry
from datetime import datetime

EpochDateTime = strawberry.scalar(
    datetime,
    serialize=lambda v: int(v.timestamp()),
    parse_value=lambda v: datetime.fromtimestamp(v),
)


@strawberry.type
class Query:
    created: EpochDateTime

After:

import strawberry
from typing import NewType
from datetime import datetime
from strawberry.schema.config import StrawberryConfig

EpochDateTime = NewType("EpochDateTime", datetime)


@strawberry.type
class Query:
    created: EpochDateTime


schema = strawberry.Schema(
    query=Query,
    config=StrawberryConfig(
        scalar_map={
            EpochDateTime: strawberry.scalar(
                name="EpochDateTime",
                serialize=lambda v: int(v.timestamp()),
                parse_value=lambda v: datetime.fromtimestamp(v),
            )
        }
    ),
)

Here's the tweet text:

🆕 Release (next) is out! Thanks to Luis Gustavo for the PR 👏

Get it here 👉 https://strawberry.rocks/release/(next)

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In strawberry.federation.scalar you now construct ScalarDefinition directly with origin=None, which means federated scalars will no longer carry source file/line information used by rich exceptions and locate-definition; consider mirroring the main scalar implementation by capturing _source_file / _source_line and setting origin so tooling behaves consistently for federated scalars.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `strawberry.federation.scalar` you now construct `ScalarDefinition` directly with `origin=None`, which means federated scalars will no longer carry source file/line information used by rich exceptions and locate-definition; consider mirroring the main `scalar` implementation by capturing `_source_file` / `_source_line` and setting `origin` so tooling behaves consistently for federated scalars.

## Individual Comments

### Comment 1
<location> `strawberry/schema/schema_converter.py:810-811` </location>
<code_context>
         scalar_definition: ScalarDefinition

         if scalar in self.scalar_registry:
-            _scalar_definition = self.scalar_registry[scalar]
-            # TODO: check why we need the cast and we are not trying with getattr first
-            if isinstance(_scalar_definition, ScalarWrapper):
-                scalar_definition = _scalar_definition._scalar_definition
-            else:
-                scalar_definition = _scalar_definition
+            scalar_definition = self.scalar_registry[scalar]
         else:
             scalar_definition = scalar._scalar_definition  # type: ignore[attr-defined]
</code_context>

<issue_to_address>
**issue (bug_risk):** Fallback to `scalar._scalar_definition` is now effectively dead/unsafe with the new scalar API.

Since `ScalarWrapper` is gone and `scalar()` no longer attaches `_scalar_definition`, this `else` branch now relies on an attribute the new API never sets. Legacy decorated scalars (or any type without `_scalar_definition`) will now raise `AttributeError` rather than a clear migration error.

Consider either:
- Detecting this case explicitly and raising a clear deprecation/migration error (e.g. `TypeError` explaining decorator-style scalars are no longer supported), or
- Removing the `else` branch and only supporting scalars present in `scalar_registry`.

This would prevent unexpected runtime errors and make the new scalar requirements explicit.
</issue_to_address>

### Comment 2
<location> `docs/types/schema.md:113` </location>
<code_context>
-#### `scalar_overrides: Optional[Dict[object, ScalarWrapper]] = None`
+#### `scalar_overrides: Optional[Dict[object, ScalarDefinition]] = None`

 Override the implementation of the built in scalars.
 [More information](/docs/types/scalars#overriding-built-in-scalars).
</code_context>

<issue_to_address>
**nitpick (typo):** Use hyphenated "built-in" for consistency and correct usage.

Update this sentence to use "built-in" so it matches the later "built-in scalars" link text.

```suggestion
Override the implementation of the built-in scalars.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 15, 2026

Greptile Summary

Removes the deprecated ScalarWrapper class and strawberry.scalar(cls, ...) wrapper pattern, completing a deprecation introduced in v0.288.0. The PR successfully migrates the entire codebase to use NewType with StrawberryConfig.scalar_map for custom scalars.

Key changes:

  • Removed ScalarWrapper class and wrapper functionality from strawberry.types.scalar
  • Updated scalar() function to only return ScalarDefinition (with name) or a decorator for type hints
  • Migrated federation scalar support to use the same pattern with federation directives
  • Updated schema codegen to generate NewType definitions with scalar_map configuration
  • Simplified scalar registry throughout codebase to only handle ScalarDefinition
  • Comprehensively updated all tests to use the new pattern
  • Updated documentation with clear migration examples

Migration pattern:
The PR consistently applies the migration from strawberry.scalar(Type, ...) to NewType("Name", Type) with configuration in scalar_map. All 36 changed files follow this pattern correctly, ensuring type safety and better IDE support.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • Well-executed deprecation removal with comprehensive test coverage, consistent migration pattern across codebase, and thorough documentation updates
  • No files require special attention

Important Files Changed

Filename Overview
strawberry/types/scalar.py Removed deprecated ScalarWrapper class and updated scalar() to return ScalarDefinition or decorator
strawberry/federation/scalar.py Migrated federation scalar() to return ScalarDefinition with directives, removed class wrapping
strawberry/schema_codegen/init.py Updated codegen to generate NewType definitions with scalar_map config instead of wrapped scalars
strawberry/schema/schema_converter.py Simplified scalar registry to only handle ScalarDefinition, removed ScalarWrapper support
strawberry/codegen/query_codegen.py Updated query codegen to handle NewType scalars from registry, improved type extraction logic
RELEASE.md Added migration guide for scalar wrapper removal, showing before/after with scalar_map pattern

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User defines custom scalar] --> B{Old Pattern}
    B --> C[strawberry.scalar wraps type]
    C --> D[ScalarWrapper created]
    D --> E[Schema uses wrapper]
    
    A --> F{New Pattern}
    F --> G[NewType definition]
    G --> H[scalar returns ScalarDefinition]
    H --> I[Add to scalar_map in config]
    I --> J[Schema uses ScalarDefinition]
    
    K[Schema Codegen] --> L[Parse GraphQL schema]
    L --> M[Generate NewType for custom scalars]
    M --> N[Generate scalar_map config]
    N --> O[Output complete schema code]
    
    P[Federation Scalars] --> Q[Define NewType]
    Q --> R[federation.scalar with directives]
    R --> S[Add to scalar_map]
    S --> T[Directives applied to scalar]
    
    style D fill:#ffcccc
    style E fill:#ffcccc
    style C fill:#ffcccc
    style J fill:#ccffcc
    style O fill:#ccffcc
    style T fill:#ccffcc
Loading

Last reviewed commit: 444ad2e

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

35 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@Ckk3 Ckk3 marked this pull request as draft February 16, 2026 23:10
@patrick91 patrick91 added this to the V1 milestone Feb 17, 2026
@Ckk3 Ckk3 marked this pull request as ready for review February 22, 2026 02:31
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In query_codegen._get_field_type, when a scalar registry key is neither a type nor has __supertype__, python_type ends up as None and is passed into _collect_scalar; consider either constraining the allowed scalar registry keys or making _collect_scalar robust to a None python_type to avoid subtle failures.
  • The new federation.scalar implementation always sets origin=None on the returned ScalarDefinition, which drops the source information you collect in strawberry.types.scalar.scalar; consider capturing the caller frame here as well so rich error reporting remains consistent for federation scalars.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `query_codegen._get_field_type`, when a scalar registry key is neither a `type` nor has `__supertype__`, `python_type` ends up as `None` and is passed into `_collect_scalar`; consider either constraining the allowed scalar registry keys or making `_collect_scalar` robust to a `None` `python_type` to avoid subtle failures.
- The new `federation.scalar` implementation always sets `origin=None` on the returned `ScalarDefinition`, which drops the source information you collect in `strawberry.types.scalar.scalar`; consider capturing the caller frame here as well so rich error reporting remains consistent for federation scalars.

## Individual Comments

### Comment 1
<location> `strawberry/federation/scalar.py:139-148` </location>
<code_context>
-    def wrap(cls: _T) -> ScalarWrapper:
-        return _process_scalar(
-            cls,
+    if name is not None:
+        return ScalarDefinition(
             name=name,
             description=description,
             specified_by_url=specified_by_url,
             serialize=serialize,
-            parse_value=parse_value,
             parse_literal=parse_literal,
-            directives=directives,
+            parse_value=parse_value,
+            directives=tuple(all_directives),
+            origin=None,
         )

-    if cls is None:
-        return wrap
+    # Decorator pattern for type hinting purposes only
+    def wrap(cls: _T) -> _T:
</code_context>

<issue_to_address>
**issue (bug_risk):** Decorator form of `federation.scalar` now ignores all runtime options and silently becomes a no-op.

The updated behavior means decorator usage no longer creates a `ScalarDefinition`, so federation options like `authenticated`, `inaccessible`, `policy`, `requires_scopes`, `tags`, or `directives` are ignored when `name` is omitted. To avoid silent behavior changes, consider either raising when `name is None` and any of these options are passed, or preserving the previous decorator behavior that created a `ScalarDefinition` for backward compatibility.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

36 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@Ckk3 Ckk3 requested a review from bellini666 February 22, 2026 16:08
)
from typing import NewType

BigInt = NewType("BigInt", int)
Copy link
Member

@patrick91 patrick91 Feb 24, 2026

Choose a reason for hiding this comment

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

we need to define the scalar for this 😊

Copy link
Collaborator Author

@Ckk3 Ckk3 Feb 25, 2026

Choose a reason for hiding this comment

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

Sure, I added it here d541011

Comment on lines +451 to +458
scalar_overrides={
Base64: strawberry.scalar(
name="Base64",
serialize=lambda v: base64.b64encode(v).decode("utf-8"),
parse_value=lambda v: base64.b64decode(v.encode("utf-8")),
)
},
)
Copy link
Member

Choose a reason for hiding this comment

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

should this be in scalar map instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done! d541011

@Ckk3 Ckk3 requested a review from patrick91 February 25, 2026 13:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants