diff --git a/docs/breaking-changes.md b/docs/breaking-changes.md index ef28223708..fca54d0a5f 100644 --- a/docs/breaking-changes.md +++ b/docs/breaking-changes.md @@ -4,6 +4,7 @@ title: List of breaking changes and deprecations # List of breaking changes and deprecations +- [Version 0.285.0 - Deprecate StrawberryConfig Class - 30 October 2025](./breaking-changes/0.285.0.md) - [Version 0.279.0 - 19 August 2025](./breaking-changes/0.279.0.md) - [Version 0.278.1 - 5 August 2025](./breaking-changes/0.278.1.md) - [Version 0.268.0 - 10 May 2025](./breaking-changes/0.268.0.md) diff --git a/docs/breaking-changes/0.285.0.md b/docs/breaking-changes/0.285.0.md new file mode 100644 index 0000000000..82ceb7e39a --- /dev/null +++ b/docs/breaking-changes/0.285.0.md @@ -0,0 +1,57 @@ +--- +title: 0.285.0 Deprecations +slug: breaking-changes/0.285.0 +--- + +# v0.285.0 Deprecations + +In this release, we've simplified the schema configuration API by migrating from +a class-based approach to a dictionary-based approach. + +## What Changed + +The `StrawberryConfig` class syntax is now **deprecated** and will show a +`DeprecationWarning`. The class will be removed in a future release. + +Instead of instantiating a config class, you should now pass a plain Python +dictionary to the `config` parameter when creating a schema. + +## Migration + +Convert the class instantiation to a dictionary: + +**Before (deprecated):** + +```python +from strawberry.schema.config import StrawberryConfig + +schema = strawberry.Schema( + query=Query, + config=StrawberryConfig( + auto_camel_case=True, + relay_max_results=50, + ), +) +``` + +**After (recommended):** + +```python +schema = strawberry.Schema( + query=Query, + config={ + "auto_camel_case": True, + "relay_max_results": 50, + }, +) +``` + +The old syntax will continue to work but will show this warning: + +``` +DeprecationWarning: Using StrawberryConfig as a class is deprecated as of v0.285.0. +Use dictionary syntax instead: config={'auto_camel_case': True}. +``` + +For more information, see the +[Schema Configurations](../types/schema-configurations.md) documentation. diff --git a/docs/guides/query-batching.md b/docs/guides/query-batching.md index 85f5448bc6..06bd3429da 100644 --- a/docs/guides/query-batching.md +++ b/docs/guides/query-batching.md @@ -16,17 +16,14 @@ and how to integrate it into your application with an example using FastAPI. ## Enabling Query Batching -To enable query batching in Strawberry, you need to configure the -`StrawberryConfig` when defining your GraphQL schema. The batching configuration -is provided as a typed dictionary. Batching is disabled by default, if no -configuration is provided. +To enable query batching in Strawberry, you need to configure the schema config +when defining your GraphQL schema. The batching configuration is provided as a +dictionary. Batching is disabled by default, if no configuration is provided. ### Basic Configuration ```python -from strawberry.schema.config import StrawberryConfig - -config = StrawberryConfig(batching_config={"max_operations": 10}) +config = {"batching_config": {"max_operations": 10}} ``` ### Configuring Maximum Operations @@ -35,9 +32,7 @@ To set a limit on the number of operations in a batch request, use the `max_operations` key: ```python -from strawberry.schema.config import StrawberryConfig - -config = StrawberryConfig(batching_config={"max_operations": 5}) +config = {"batching_config": {"max_operations": 5}} ``` When batching is enabled, the server can handle a list of operations @@ -52,7 +47,6 @@ Below is an example of how to enable query batching in a FastAPI application: import strawberry from fastapi import FastAPI from strawberry.fastapi import GraphQLRouter -from strawberry.schema.config import StrawberryConfig @strawberry.type @@ -64,7 +58,7 @@ class Query: schema = strawberry.Schema( Query, - config=StrawberryConfig(batching_config={"max_operations": 5}), + config={"batching_config": {"max_operations": 5}}, ) graphql_app = GraphQLRouter(schema) diff --git a/docs/types/defer-and-stream.md b/docs/types/defer-and-stream.md index 0d0f6d00c8..8000735474 100644 --- a/docs/types/defer-and-stream.md +++ b/docs/types/defer-and-stream.md @@ -31,7 +31,6 @@ incremental execution in your schema configuration: ```python import strawberry -from strawberry.schema.config import StrawberryConfig @strawberry.type @@ -41,7 +40,7 @@ class Query: schema = strawberry.Schema( - query=Query, config=StrawberryConfig(enable_experimental_incremental_execution=True) + query=Query, config={"enable_experimental_incremental_execution": True} ) ``` diff --git a/docs/types/schema-configurations.md b/docs/types/schema-configurations.md index 62e3109aad..04cfc98dc8 100644 --- a/docs/types/schema-configurations.md +++ b/docs/types/schema-configurations.md @@ -4,24 +4,22 @@ title: Schema Configurations # Schema Configurations -Strawberry allows to customise how the schema is generated by passing -configurations. +Strawberry allows you to customise how the schema is generated by passing +configuration options as a dictionary. -To customise the schema you can create an instance of `StrawberryConfig`, as -shown in the example below: +To customise the schema, you can pass a configuration dictionary to the `config` +parameter, as shown in the example below: ```python import strawberry -from strawberry.schema.config import StrawberryConfig - @strawberry.type class Query: example_field: str -schema = strawberry.Schema(query=Query, config=StrawberryConfig(auto_camel_case=False)) +schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) ``` In this case we are disabling the auto camel casing feature, so your output @@ -33,6 +31,14 @@ type Query { } ``` + + +**Upgrading from v0.284.0 or earlier?** See the +[v0.285.0 breaking changes](../breaking-changes/0.285.0.md) for migration +instructions if you were using the old `StrawberryConfig` class syntax. + + + ## Available configurations Here's a list of the available configurations: @@ -44,7 +50,7 @@ like `example_field` will be converted to `exampleField`. You can disable this feature by setting `auto_camel_case` to `False`. ```python -schema = strawberry.Schema(query=Query, config=StrawberryConfig(auto_camel_case=False)) +schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) ``` ### default_resolver @@ -58,8 +64,6 @@ resolver. ```python import strawberry -from strawberry.schema.config import StrawberryConfig - def custom_resolver(obj, field): try: @@ -80,9 +84,7 @@ class Query: return {"name": "Patrick"} -schema = strawberry.Schema( - query=Query, config=StrawberryConfig(default_resolver=custom_resolver) -) +schema = strawberry.Schema(query=Query, config={"default_resolver": custom_resolver}) ``` ### relay_max_results @@ -91,7 +93,7 @@ By default Strawberry's max limit for relay connections is 100. You can customise this by setting the `relay_max_results` configuration. ```python -schema = strawberry.Schema(query=Query, config=StrawberryConfig(relay_max_results=50)) +schema = strawberry.Schema(query=Query, config={"relay_max_results": 50}) ``` ### disable_field_suggestions @@ -101,9 +103,7 @@ schema. You can disable this feature by setting `disable_field_suggestions` to `True`. ```python -schema = strawberry.Schema( - query=Query, config=StrawberryConfig(disable_field_suggestions=True) -) +schema = strawberry.Schema(query=Query, config={"disable_field_suggestions": True}) ``` ### info_class @@ -123,7 +123,7 @@ class CustomInfo(Info): return self.context["response"].headers -schema = strawberry.Schema(query=Query, config=StrawberryConfig(info_class=CustomInfo)) +schema = strawberry.Schema(query=Query, config={"info_class": CustomInfo}) ``` ### enable_experimental_incremental_execution @@ -142,7 +142,7 @@ response to be delivered incrementally. ```python schema = strawberry.Schema( - query=Query, config=StrawberryConfig(enable_experimental_incremental_execution=True) + query=Query, config={"enable_experimental_incremental_execution": True} ) ``` diff --git a/strawberry/codemods/__init__.py b/strawberry/codemods/__init__.py index 4e77d06ad9..183160cee5 100644 --- a/strawberry/codemods/__init__.py +++ b/strawberry/codemods/__init__.py @@ -1,9 +1,11 @@ from .annotated_unions import ConvertUnionToAnnotatedUnion +from .config_to_dict import ConvertStrawberryConfigToDict from .maybe_optional import ConvertMaybeToOptional from .update_imports import UpdateImportsCodemod __all__ = [ "ConvertMaybeToOptional", + "ConvertStrawberryConfigToDict", "ConvertUnionToAnnotatedUnion", "UpdateImportsCodemod", ] diff --git a/strawberry/codemods/config_to_dict.py b/strawberry/codemods/config_to_dict.py new file mode 100644 index 0000000000..07cfedd81c --- /dev/null +++ b/strawberry/codemods/config_to_dict.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import libcst as cst +import libcst.matchers as m +from libcst.codemod import VisitorBasedCodemodCommand + + +class ConvertStrawberryConfigToDict(VisitorBasedCodemodCommand): + """Convert StrawberryConfig() instantiation to dict syntax. + + This codemod converts the deprecated class-based StrawberryConfig() syntax + to the new dictionary syntax. + + Examples: + # Before: + config = StrawberryConfig() + config = StrawberryConfig(auto_camel_case=True) + + # After: + config = {} + config = {"auto_camel_case": True} + """ + + DESCRIPTION: str = ( + "Converts StrawberryConfig() class instantiation to dictionary syntax" + ) + + def leave_Call( # noqa: N802 + self, original_node: cst.Call, updated_node: cst.Call + ) -> cst.BaseExpression: + # Check if this is a StrawberryConfig() call + if not self._is_strawberry_config_call(original_node): + return updated_node + + # If no arguments, convert to empty dict {} + if not original_node.args: + return cst.Dict([]) + + # Convert arguments to dict entries + dict_elements = [] + for arg in original_node.args: + # Only handle keyword arguments + if arg.keyword is None: + # Positional arguments not supported, skip this conversion + return updated_node + + # Create dict key (string literal from keyword name) + key = cst.SimpleString(f'"{arg.keyword.value}"') + + # Create dict value (the argument value) + dict_elements.append( + cst.DictElement( + key=key, + value=arg.value, + ) + ) + + # Return dictionary literal + return cst.Dict(elements=dict_elements) + + def _is_strawberry_config_call(self, node: cst.Call) -> bool: + """Check if this is a call to StrawberryConfig.""" + # Check for direct StrawberryConfig() call + if m.matches(node.func, m.Name("StrawberryConfig")): + return True + + # Check for strawberry.schema.config.StrawberryConfig() + if m.matches( + node.func, + m.Attribute( + value=m.Attribute( + value=m.Attribute( + value=m.Name("strawberry"), + attr=m.Name("schema"), + ), + attr=m.Name("config"), + ), + attr=m.Name("StrawberryConfig"), + ), + ): + return True + + # Check for config.StrawberryConfig() + return m.matches( + node.func, + m.Attribute( + attr=m.Name("StrawberryConfig"), + ), + ) diff --git a/strawberry/extensions/directives.py b/strawberry/extensions/directives.py index cbd8a6be08..22296e7d0c 100644 --- a/strawberry/extensions/directives.py +++ b/strawberry/extensions/directives.py @@ -86,7 +86,7 @@ def process_directive( field_name=info.field_name, type_name=info.parent_type.name, ) - arguments[info_parameter.name] = schema.config.info_class( + arguments[info_parameter.name] = schema.config["info_class"]( _raw_info=info, _field=field ) if value_parameter: diff --git a/strawberry/federation/schema.py b/strawberry/federation/schema.py index 4bb2359fa3..edf8a023d6 100644 --- a/strawberry/federation/schema.py +++ b/strawberry/federation/schema.py @@ -238,7 +238,7 @@ def _add_link_for_composed_directive( return import_url = directive.compose_options.import_url - name = self.config.name_converter.from_directive(directive) + name = self.config["name_converter"].from_directive(directive) # import url is required by Apollo Federation, this might change in # future to be optional, so for now, when it is not passed we @@ -290,7 +290,7 @@ def _add_compose_directives(self) -> list["ComposeDirective"]: ) if is_federation_schema_directive and definition.compose_options: - name = self.config.name_converter.from_directive(definition) + name = self.config["name_converter"].from_directive(definition) compose_directives.append( ComposeDirective( diff --git a/strawberry/http/base.py b/strawberry/http/base.py index 4486ecf172..ad9411be67 100644 --- a/strawberry/http/base.py +++ b/strawberry/http/base.py @@ -88,7 +88,7 @@ def _is_multipart_subscriptions( def _validate_batch_request( self, request_data: list[GraphQLRequestData], protocol: str ) -> None: - if self.schema.config.batching_config is None: + if self.schema.config["batching_config"] is None: raise HTTPException(400, "Batching is not enabled") if protocol == "multipart-subscription": @@ -96,7 +96,7 @@ def _validate_batch_request( 400, "Batching is not supported for multipart subscriptions" ) - if len(request_data) > self.schema.config.batching_config["max_operations"]: + if len(request_data) > self.schema.config["batching_config"]["max_operations"]: raise HTTPException(400, "Too many operations") diff --git a/strawberry/printer/printer.py b/strawberry/printer/printer.py index 74152b8cd8..6fa5d3d4ce 100644 --- a/strawberry/printer/printer.py +++ b/strawberry/printer/printer.py @@ -131,7 +131,7 @@ def print_schema_directive_params( ast = ast_from_value( _serialize_dataclasses( value, - name_converter=schema.config.name_converter.apply_naming_config, + name_converter=schema.config["name_converter"].apply_naming_config, ), arg.type, ) @@ -157,7 +157,7 @@ def print_schema_directive( params = print_schema_directive_params( gql_directive, { - schema.config.name_converter.get_graphql_name(f): getattr( + schema.config["name_converter"].get_graphql_name(f): getattr( directive, f.python_name or f.name, UNSET ) for f in strawberry_directive.fields @@ -616,7 +616,7 @@ def print_schema(schema: BaseSchema) -> str: if (printed_directive := print_directive(directive, schema=schema)) is not None ] - if schema.config.enable_experimental_incremental_execution: + if schema.config["enable_experimental_incremental_execution"]: directives.append( "directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT" ) diff --git a/strawberry/relay/utils.py b/strawberry/relay/utils.py index eb7c05ed9d..e006b6cfe4 100644 --- a/strawberry/relay/utils.py +++ b/strawberry/relay/utils.py @@ -131,7 +131,7 @@ def from_arguments( max_results = ( max_results if max_results is not None - else info.schema.config.relay_max_results + else info.schema.config["relay_max_results"] ) start = 0 end: int | None = None diff --git a/strawberry/schema/base.py b/strawberry/schema/base.py index 89d0d6c18f..6ae6523ad7 100644 --- a/strawberry/schema/base.py +++ b/strawberry/schema/base.py @@ -27,11 +27,11 @@ from strawberry.types.scalar import ScalarDefinition from strawberry.types.union import StrawberryUnion - from .config import StrawberryConfig + from .config import StrawberryConfigDict class BaseSchema(Protocol): - config: StrawberryConfig + config: StrawberryConfigDict schema_converter: GraphQLCoreConverter query: type[WithStrawberryObjectDefinition] mutation: type[WithStrawberryObjectDefinition] | None @@ -109,7 +109,7 @@ def _process_errors( errors: list[GraphQLError], execution_context: ExecutionContext | None = None, ) -> None: - if self.config.disable_field_suggestions: + if self.config["disable_field_suggestions"]: for error in errors: self.remove_field_suggestion(error) diff --git a/strawberry/schema/config.py b/strawberry/schema/config.py index e484b0988e..e080455f12 100644 --- a/strawberry/schema/config.py +++ b/strawberry/schema/config.py @@ -1,6 +1,5 @@ from __future__ import annotations -from dataclasses import InitVar, dataclass, field from typing import TYPE_CHECKING, Any, TypedDict from strawberry.types.info import Info @@ -15,28 +14,155 @@ class BatchingConfig(TypedDict): max_operations: int -@dataclass -class StrawberryConfig: - auto_camel_case: InitVar[bool] = None # pyright: reportGeneralTypeIssues=false - name_converter: NameConverter = field(default_factory=NameConverter) - default_resolver: Callable[[Any, str], object] = getattr - relay_max_results: int = 100 - relay_use_legacy_global_id: bool = False - disable_field_suggestions: bool = False - info_class: type[Info] = Info - enable_experimental_incremental_execution: bool = False - _unsafe_disable_same_type_validation: bool = False - batching_config: BatchingConfig | None = None +class StrawberryConfigType(TypedDict, total=False): + """Type definition for Strawberry configuration. - def __post_init__( - self, - auto_camel_case: bool, - ) -> None: - if auto_camel_case is not None: - self.name_converter.auto_camel_case = auto_camel_case + This TypedDict defines the shape of configuration options. All fields are optional. + """ - if not issubclass(self.info_class, Info): - raise TypeError("`info_class` must be a subclass of strawberry.Info") + auto_camel_case: bool | None + name_converter: NameConverter + default_resolver: Callable[[Any, str], object] + relay_max_results: int + relay_use_legacy_global_id: bool + disable_field_suggestions: bool + info_class: type[Info] + enable_experimental_incremental_execution: bool + _unsafe_disable_same_type_validation: bool + batching_config: BatchingConfig | None -__all__ = ["StrawberryConfig"] +class StrawberryConfigDict(dict[str, Any]): + """Configuration for Strawberry Schema. + + This is a dictionary-like class that defines the configuration options for a + Strawberry schema. All fields are optional - missing fields will be filled with + sensible defaults. + + This class supports both dictionary-style access (config["name_converter"]) and + attribute-style access (config.name_converter) for backwards compatibility. + + Example: + ```python + schema = strawberry.Schema( + query=Query, + config={ + "auto_camel_case": True, + "relay_max_results": 50, + }, + ) + ``` + + Attributes: + auto_camel_case: If True, field names will be converted to camelCase. + This is a shorthand for setting name_converter.auto_camel_case. + name_converter: Custom name converter for field/type name transformations. + default_resolver: Function used to resolve field values (default: getattr). + relay_max_results: Maximum number of results for relay connections (default: 100). + relay_use_legacy_global_id: Use legacy global ID format (default: False). + disable_field_suggestions: Disable field name suggestions in errors (default: False). + info_class: Custom Info class to use (default: strawberry.Info). + enable_experimental_incremental_execution: Enable @defer/@stream support (default: False). + _unsafe_disable_same_type_validation: Skip duplicate type validation (default: False). + batching_config: Configuration for query batching (default: None). + """ + + if TYPE_CHECKING: + # For type checkers, show these as instance attributes + auto_camel_case: bool | None + name_converter: NameConverter + default_resolver: Callable[[Any, str], object] + relay_max_results: int + relay_use_legacy_global_id: bool + disable_field_suggestions: bool + info_class: type[Info] + enable_experimental_incremental_execution: bool + _unsafe_disable_same_type_validation: bool + batching_config: BatchingConfig | None + + def __getattr__(self, name: str) -> Any: + """Allow attribute-style access for backwards compatibility.""" + try: + return self[name] + except KeyError: + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{name}'" + ) from None + + def __setattr__(self, name: str, value: Any) -> None: + """Allow attribute-style setting for backwards compatibility.""" + self[name] = value + + def __delattr__(self, name: str) -> None: + """Allow attribute-style deletion for backwards compatibility.""" + try: + del self[name] + except KeyError: + raise AttributeError( + f"'{type(self).__name__}' object has no attribute '{name}'" + ) from None + + +def _complete_config( + config: StrawberryConfigType | StrawberryConfigDict | dict[str, Any] | None, +) -> StrawberryConfigDict: + """Normalize and complete a config dict with defaults. + + This function takes a partial or complete config dict and ensures all required + fields are present with appropriate defaults. + + Args: + config: Partial config dict or None + + Returns: + Complete StrawberryConfigDict with all defaults applied + + Raises: + TypeError: If info_class is not a subclass of strawberry.Info + """ + if config is None: + config = {} + + # Create a StrawberryConfigDict (or copy if already one) + result = StrawberryConfigDict(config) + + # Handle auto_camel_case -> name_converter conversion + auto_camel_case = result.pop("auto_camel_case", None) + + # Apply defaults + if "name_converter" not in result: + result["name_converter"] = NameConverter() + + if auto_camel_case is not None: + result["name_converter"].auto_camel_case = auto_camel_case + + # Validate info_class if provided + info_class = result.get("info_class", Info) + if not issubclass(info_class, Info): + raise TypeError("`info_class` must be a subclass of strawberry.Info") + + # Set other defaults + result.setdefault("default_resolver", getattr) + result.setdefault("relay_max_results", 100) + result.setdefault("relay_use_legacy_global_id", False) + result.setdefault("disable_field_suggestions", False) + result.setdefault("info_class", info_class) + result.setdefault("enable_experimental_incremental_execution", False) + result.setdefault("_unsafe_disable_same_type_validation", False) + result.setdefault("batching_config", None) + + return result + + +# Backwards compatibility: StrawberryConfig is StrawberryConfigDict +# This works at runtime and for type checking because StrawberryConfigDict +# has TYPE_CHECKING annotations that make attributes visible to type checkers +StrawberryConfig = StrawberryConfigDict + + +__all__ = [ + "StrawberryConfig", + "StrawberryConfigDict", + "StrawberryConfigType", + "_complete_config", +] diff --git a/strawberry/schema/schema.py b/strawberry/schema/schema.py index 48259566f4..fa3b5db11b 100644 --- a/strawberry/schema/schema.py +++ b/strawberry/schema/schema.py @@ -75,7 +75,7 @@ subscribe, ) from .base import BaseSchema -from .config import StrawberryConfig +from .config import StrawberryConfigType, _complete_config from .exceptions import CannotGetOperationTypeError, InvalidOperationTypeError if TYPE_CHECKING: @@ -214,7 +214,7 @@ def __init__( types: Iterable[type | StrawberryType] = (), extensions: Iterable[type[SchemaExtension] | SchemaExtension] = (), execution_context_class: type[GraphQLExecutionContext] | None = None, - config: StrawberryConfig | None = None, + config: StrawberryConfigType | None = None, scalar_overrides: ( Mapping[object, type | ScalarWrapper | ScalarDefinition] | None ) = None, @@ -252,7 +252,7 @@ class Query: name: str = "Patrick" - schema = strawberry.Schema(query=Query) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": True}) ``` """ self.query = query @@ -264,7 +264,9 @@ class Query: self.execution_context_class = ( execution_context_class or StrawberryGraphQLCoreExecutionContext ) - self.config = config or StrawberryConfig() + + # Normalize and complete config dict with defaults + self.config = _complete_config(config) self.schema_converter = GraphQLCoreConverter( self.config, @@ -325,7 +327,7 @@ class Query: try: directives = specified_directives + tuple(graphql_directives) # type: ignore - if self.config.enable_experimental_incremental_execution: + if self.config["enable_experimental_incremental_execution"]: directives = tuple(directives) + tuple(incremental_execution_directives) self._schema = GraphQLSchema( @@ -462,7 +464,7 @@ def get_field_for_type( ( field for field in type_.fields - if self.config.name_converter.get_graphql_name(field) == field_name + if self.config["name_converter"].get_graphql_name(field) == field_name ), None, ) @@ -473,7 +475,8 @@ def get_directive_by_name(self, graphql_name: str) -> StrawberryDirective | None ( directive for directive in self.directives - if self.config.name_converter.from_directive(directive) == graphql_name + if self.config["name_converter"].from_directive(directive) + == graphql_name ), None, ) @@ -578,7 +581,7 @@ async def execute( execute_function = execute - if self.config.enable_experimental_incremental_execution: + if self.config["enable_experimental_incremental_execution"]: execute_function = experimental_execute_incrementally if execute_function is None: @@ -684,7 +687,7 @@ def execute_sync( execute_function = execute - if self.config.enable_experimental_incremental_execution: + if self.config["enable_experimental_incremental_execution"]: execute_function = experimental_execute_incrementally if execute_function is None: diff --git a/strawberry/schema/schema_converter.py b/strawberry/schema/schema_converter.py index c2697e0f9d..aaa4f19a2f 100644 --- a/strawberry/schema/schema_converter.py +++ b/strawberry/schema/schema_converter.py @@ -262,7 +262,9 @@ def _get_scalar_registry( ) -> Mapping[object, ScalarWrapper | ScalarDefinition]: scalar_registry = {**DEFAULT_SCALAR_REGISTRY} - global_id_name = "GlobalID" if self.config.relay_use_legacy_global_id else "ID" + global_id_name = ( + "GlobalID" if self.config["relay_use_legacy_global_id"] else "ID" + ) scalar_registry[GlobalID] = _get_scalar_definition( scalar( @@ -301,7 +303,7 @@ def from_argument(self, argument: StrawberryArgument) -> GraphQLArgument: ) def from_enum(self, enum: EnumDefinition) -> CustomGraphQLEnumType: - enum_name = self.config.name_converter.from_type(enum) + enum_name = self.config["name_converter"].from_type(enum) assert enum_name is not None @@ -317,7 +319,7 @@ def from_enum(self, enum: EnumDefinition) -> CustomGraphQLEnumType: enum=enum, name=enum_name, values={ - self.config.name_converter.from_enum_value( + self.config["name_converter"].from_enum_value( enum, item ): self.from_enum_value(item) for item in enum.values @@ -348,10 +350,10 @@ def from_directive(self, directive: StrawberryDirective) -> GraphQLDirective: graphql_arguments = {} for argument in directive.arguments: - argument_name = self.config.name_converter.from_argument(argument) + argument_name = self.config["name_converter"].from_argument(argument) graphql_arguments[argument_name] = self.from_argument(argument) - directive_name = self.config.name_converter.from_type(directive) + directive_name = self.config["name_converter"].from_type(directive) return GraphQLDirective( name=directive_name, @@ -376,7 +378,7 @@ def from_schema_directive(self, cls: type) -> GraphQLDirective: if default == dataclasses.MISSING: default = UNSET - name = self.config.name_converter.get_graphql_name(field) + name = self.config["name_converter"].get_graphql_name(field) args[name] = self.from_argument( StrawberryArgument( python_name=field.python_name or field.name, @@ -390,7 +392,7 @@ def from_schema_directive(self, cls: type) -> GraphQLDirective: ) return GraphQLDirective( - name=self.config.name_converter.from_directive(strawberry_directive), + name=self.config["name_converter"].from_directive(strawberry_directive), locations=[ DirectiveLocation(loc.value) for loc in strawberry_directive.locations ], @@ -425,7 +427,7 @@ def from_field( graphql_arguments = {} for argument in field.arguments: - argument_name = self.config.name_converter.from_argument(argument) + argument_name = self.config["name_converter"].from_argument(argument) graphql_arguments[argument_name] = self.from_argument(argument) return GraphQLField( @@ -475,7 +477,7 @@ def get_graphql_fields( ) -> dict[str, GraphQLField]: return _get_thunk_mapping( type_definition=type_definition, - name_converter=self.config.name_converter.from_field, + name_converter=self.config["name_converter"].from_field, field_converter=self.from_field, get_fields=self.get_fields, ) @@ -485,7 +487,7 @@ def get_graphql_input_fields( ) -> dict[str, GraphQLInputField]: return _get_thunk_mapping( type_definition=type_definition, - name_converter=self.config.name_converter.from_field, + name_converter=self.config["name_converter"].from_field, field_converter=self.from_input_field, get_fields=self.get_fields, ) @@ -493,7 +495,7 @@ def get_graphql_input_fields( def from_input_object(self, object_type: type) -> GraphQLInputObjectType: type_definition = object_type.__strawberry_definition__ # type: ignore - type_name = self.config.name_converter.from_type(type_definition) + type_name = self.config["name_converter"].from_type(type_definition) # Don't reevaluate known types cached_type = self.type_map.get(type_name, None) @@ -543,7 +545,7 @@ def check_one_of(value: dict[str, Any]) -> dict[str, Any]: def from_interface( self, interface: StrawberryObjectDefinition ) -> GraphQLInterfaceType: - interface_name = self.config.name_converter.from_type(interface) + interface_name = self.config["name_converter"].from_type(interface) # Don't re-evaluate known types cached_type = self.type_map.get(interface_name, None) @@ -626,7 +628,7 @@ def from_list(self, type_: StrawberryList) -> GraphQLList: def from_object(self, object_type: StrawberryObjectDefinition) -> GraphQLObjectType: # TODO: Use StrawberryObjectType when it's implemented in another PR - object_type_name = self.config.name_converter.from_type(object_type) + object_type_name = self.config["name_converter"].from_type(object_type) # Don't reevaluate known types cached_type = self.type_map.get(object_type_name, None) @@ -687,7 +689,7 @@ def is_type_of(obj: Any, _info: GraphQLResolveInfo) -> bool: def from_resolver( self, field: StrawberryField ) -> Callable: # TODO: Take StrawberryResolver - field.default_resolver = self.config.default_resolver + field.default_resolver = self.config["default_resolver"] if field.is_basic_field: @@ -701,7 +703,7 @@ def _get_basic_result(_source: Any, *args: str, **kwargs: Any) -> Any: return _get_basic_result def _strawberry_info_from_graphql(info: GraphQLResolveInfo) -> Info: - return self.config.info_class( + return self.config["info_class"]( _raw_info=info, _field=field, ) @@ -801,7 +803,7 @@ async def _async_resolver( def from_scalar(self, scalar: type) -> GraphQLScalarType: from strawberry.relay.types import GlobalID - if not self.config.relay_use_legacy_global_id and scalar is GlobalID: + if not self.config["relay_use_legacy_global_id"] and scalar is GlobalID: from strawberry import ID return self.from_scalar(ID) @@ -818,7 +820,7 @@ def from_scalar(self, scalar: type) -> GraphQLScalarType: else: scalar_definition = scalar._scalar_definition # type: ignore[attr-defined] - scalar_name = self.config.name_converter.from_type(scalar_definition) + scalar_name = self.config["name_converter"].from_type(scalar_definition) if scalar_name not in self.type_map: implementation = ( @@ -904,7 +906,7 @@ def from_type(self, type_: StrawberryType | type) -> GraphQLNullableType: raise TypeError(f"Unexpected type '{type_}'") def from_union(self, union: StrawberryUnion) -> GraphQLUnionType: - union_name = self.config.name_converter.from_type(union) + union_name = self.config["name_converter"].from_type(union) for type_ in union.types: # This check also occurs in the Annotation resolving, but because of @@ -985,7 +987,7 @@ def validate_same_type_definition( ) -> None: # Skip validation if _unsafe_disable_same_type_validation is True if ( - self.config._unsafe_disable_same_type_validation + self.config["_unsafe_disable_same_type_validation"] or cached_type.definition == type_definition ): return diff --git a/strawberry/types/arguments.py b/strawberry/types/arguments.py index 06bc1b5d4e..9afea2db75 100644 --- a/strawberry/types/arguments.py +++ b/strawberry/types/arguments.py @@ -251,7 +251,7 @@ def convert_argument( type_definition = type_.__strawberry_definition__ for field in type_definition.fields: value = cast("Mapping", value) - graphql_name = config.name_converter.from_field(field) + graphql_name = config["name_converter"].from_field(field) if graphql_name in value: kwargs[field.python_name] = convert_argument( @@ -286,7 +286,7 @@ def convert_arguments( for argument in arguments: assert argument.python_name - name = config.name_converter.from_argument(argument) + name = config["name_converter"].from_argument(argument) if name in value: current_value = value[name] diff --git a/strawberry/utils/locate_definition.py b/strawberry/utils/locate_definition.py index e3fa9f152b..e13e4bad28 100644 --- a/strawberry/utils/locate_definition.py +++ b/strawberry/utils/locate_definition.py @@ -30,7 +30,7 @@ def locate_definition(schema_symbol: Schema, symbol: str) -> str | None: location = finder.find_class_attribute_from_object( schema_type.origin, # type: ignore to_snake_case(field) - if schema_symbol.config.name_converter.auto_camel_case + if schema_symbol.config["name_converter"].auto_camel_case else field, ) elif isinstance(schema_type, StrawberryUnion): diff --git a/tests/federation/printer/test_additional_directives.py b/tests/federation/printer/test_additional_directives.py index 8ae4662353..491fd935a9 100644 --- a/tests/federation/printer/test_additional_directives.py +++ b/tests/federation/printer/test_additional_directives.py @@ -3,7 +3,6 @@ import textwrap import strawberry -from strawberry.schema.config import StrawberryConfig from strawberry.schema_directive import Location @@ -45,7 +44,7 @@ class Query: """ schema = strawberry.federation.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) + query=Query, config={"auto_camel_case": False} ) assert schema.as_str() == textwrap.dedent(expected_type).strip() @@ -97,6 +96,6 @@ class Query: """ schema = strawberry.federation.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) + query=Query, config={"auto_camel_case": False} ) assert schema.as_str() == textwrap.dedent(expected_type).strip() diff --git a/tests/federation/printer/test_provides.py b/tests/federation/printer/test_provides.py index 87c8c6970e..e6642ef230 100644 --- a/tests/federation/printer/test_provides.py +++ b/tests/federation/printer/test_provides.py @@ -3,7 +3,6 @@ import textwrap import strawberry -from strawberry.schema.config import StrawberryConfig def test_field_provides_are_printed_correctly_camel_case_on(): @@ -33,7 +32,7 @@ def top_products(self, first: int) -> list[Product]: # pragma: no cover schema = strawberry.federation.Schema( query=Query, - config=StrawberryConfig(auto_camel_case=True), + config={"auto_camel_case": True}, enable_federation_2=True, ) @@ -105,7 +104,7 @@ def top_products(self, first: int) -> list[Product]: # pragma: no cover schema = strawberry.federation.Schema( query=Query, - config=StrawberryConfig(auto_camel_case=False), + config={"auto_camel_case": False}, enable_federation_2=True, ) diff --git a/tests/objects/generics/test_names.py b/tests/objects/generics/test_names.py index 54b3edb8db..01919450e5 100644 --- a/tests/objects/generics/test_names.py +++ b/tests/objects/generics/test_names.py @@ -4,7 +4,7 @@ import pytest import strawberry -from strawberry.schema.config import StrawberryConfig +from strawberry.schema.name_converter import NameConverter from strawberry.types.base import StrawberryList, StrawberryOptional from strawberry.types.enum import EnumDefinition from strawberry.types.lazy_type import LazyType @@ -18,6 +18,8 @@ Enum = EnumDefinition(None, name="Enum", values=[], description=None) # type: ignore CustomInt = strawberry.scalar(NewType("CustomInt", int)) +name_converter = NameConverter() + @strawberry.type class TypeA: @@ -57,20 +59,16 @@ class TypeB: ], ) def test_name_generation(types, expected_name): - config = StrawberryConfig() - @strawberry.type class Example(Generic[T]): a: T type_definition = Example.__strawberry_definition__ # type: ignore - assert config.name_converter.from_generic(type_definition, types) == expected_name + assert name_converter.from_generic(type_definition, types) == expected_name def test_nested_generics(): - config = StrawberryConfig() - @strawberry.type class Edge(Generic[T]): node: T @@ -82,13 +80,7 @@ class Connection(Generic[T]): type_definition = Connection.__strawberry_definition__ # type: ignore assert ( - config.name_converter.from_generic( - type_definition, - [ - Edge[int], - ], - ) - == "IntEdgeConnection" + name_converter.from_generic(type_definition, [Edge[int]]) == "IntEdgeConnection" ) @@ -96,7 +88,6 @@ def test_nested_generics_aliases_with_schema(): """This tests is similar to the previous test, but it also tests against the schema, since the resolution of the type name might be different. """ - config = StrawberryConfig() @strawberry.type class Value(Generic[T]): @@ -110,11 +101,9 @@ class DictItem(Generic[K, V]): type_definition = Value.__strawberry_definition__ # type: ignore assert ( - config.name_converter.from_generic( + name_converter.from_generic( type_definition, - [ - StrawberryList(DictItem[int, str]), - ], + [StrawberryList(DictItem[int, str])], ) == "IntStrDictItemListValue" ) diff --git a/tests/schema/extensions/schema_extensions/test_defer_extensions.py b/tests/schema/extensions/schema_extensions/test_defer_extensions.py index 2061895377..1ab4c02aee 100644 --- a/tests/schema/extensions/schema_extensions/test_defer_extensions.py +++ b/tests/schema/extensions/schema_extensions/test_defer_extensions.py @@ -2,7 +2,6 @@ from inline_snapshot import snapshot import strawberry -from strawberry.schema.config import StrawberryConfig from tests.conftest import skip_if_gql_32 from tests.schema.extensions.schema_extensions.conftest import ( ExampleExtension, @@ -34,7 +33,7 @@ def hero(self) -> Hero: schema = strawberry.Schema( query=Query, extensions=[extension], - config=StrawberryConfig(enable_experimental_incremental_execution=True), + config={"enable_experimental_incremental_execution": True}, ) result = await schema.execute( diff --git a/tests/schema/test_camel_casing.py b/tests/schema/test_camel_casing.py index 47a91fc2d8..dc280b1060 100644 --- a/tests/schema/test_camel_casing.py +++ b/tests/schema/test_camel_casing.py @@ -1,5 +1,4 @@ import strawberry -from strawberry.schema.config import StrawberryConfig def test_camel_case_is_on_by_default(): @@ -30,9 +29,7 @@ def test_can_set_camel_casing(): class Query: example_field: str = "Example" - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=True) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": True}) query = """ { @@ -55,9 +52,7 @@ def test_can_set_camel_casing_to_false(): class Query: example_field: str = "Example" - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) query = """ { @@ -80,9 +75,7 @@ def test_can_set_camel_casing_to_false_uses_name(): class Query: example_field: str = strawberry.field(name="exampleField") - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) query = """ { @@ -107,9 +100,7 @@ class Query: def example_field(self) -> str: return "ABC" - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) query = """ { @@ -162,9 +153,7 @@ class Query: def example_field(self, example_input: str) -> str: return example_input - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) query = """ { @@ -192,9 +181,7 @@ class Query: def example_field(self, example_input: str) -> str: return example_input - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) query = """ { diff --git a/tests/schema/test_config.py b/tests/schema/test_config.py index 665b772391..8fc3b239bf 100644 --- a/tests/schema/test_config.py +++ b/tests/schema/test_config.py @@ -7,13 +7,13 @@ def test_config_post_init_auto_camel_case(): config = StrawberryConfig(auto_camel_case=True) - assert config.name_converter.auto_camel_case is True + assert config["name_converter"].auto_camel_case is True def test_config_post_init_no_auto_camel_case(): config = StrawberryConfig(auto_camel_case=False) - assert config.name_converter.auto_camel_case is False + assert config["name_converter"].auto_camel_case is False def test_config_post_init_info_class(): diff --git a/tests/schema/test_directives.py b/tests/schema/test_directives.py index c0c9a191b8..87a1949150 100644 --- a/tests/schema/test_directives.py +++ b/tests/schema/test_directives.py @@ -235,7 +235,7 @@ def replace(value: DirectiveValue[str], old: str, new: str): schema = strawberry.Schema( query=Query, directives=[turn_uppercase, replace], - config=StrawberryConfig(auto_camel_case=False), + config={"auto_camel_case": False}, ) query = """query People($identified: Boolean!){ diff --git a/tests/test_printer/test_basic.py b/tests/test_printer/test_basic.py index cd7d73264f..6a48d0ce7b 100644 --- a/tests/test_printer/test_basic.py +++ b/tests/test_printer/test_basic.py @@ -4,7 +4,6 @@ import strawberry from strawberry.printer import print_schema from strawberry.scalars import JSON -from strawberry.schema.config import StrawberryConfig from strawberry.types.unset import UNSET from tests.conftest import skip_if_gql_32 @@ -48,9 +47,7 @@ class Query: } """ - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=True) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": True}) assert print_schema(schema) == textwrap.dedent(expected_type).strip() @@ -66,9 +63,7 @@ class Query: } """ - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) assert print_schema(schema) == textwrap.dedent(expected_type).strip() diff --git a/tests/test_printer/test_defer_stream.py b/tests/test_printer/test_defer_stream.py index 41022799eb..03e2f468d5 100644 --- a/tests/test_printer/test_defer_stream.py +++ b/tests/test_printer/test_defer_stream.py @@ -3,7 +3,6 @@ import textwrap import strawberry -from strawberry.schema.config import StrawberryConfig from tests.conftest import skip_if_gql_32 pytestmark = skip_if_gql_32("GraphQL 3.3.0 is required for incremental execution") @@ -17,7 +16,7 @@ class Query: def test_does_not_print_defer_and_stream_directives_when_experimental_execution_is_disabled(): schema = strawberry.Schema( query=Query, - config=StrawberryConfig(enable_experimental_incremental_execution=False), + config={"enable_experimental_incremental_execution": False}, ) expected_type = """ @@ -32,7 +31,7 @@ def test_does_not_print_defer_and_stream_directives_when_experimental_execution_ def test_prints_defer_and_stream_directives_when_experimental_execution_is_enabled(): schema = strawberry.Schema( query=Query, - config=StrawberryConfig(enable_experimental_incremental_execution=True), + config={"enable_experimental_incremental_execution": True}, ) expected_type = """ diff --git a/tests/test_printer/test_schema_directives.py b/tests/test_printer/test_schema_directives.py index 3bf4d45599..28d46c1c81 100644 --- a/tests/test_printer/test_schema_directives.py +++ b/tests/test_printer/test_schema_directives.py @@ -6,7 +6,6 @@ from strawberry import BasePermission, Info from strawberry.permission import PermissionExtension from strawberry.printer import print_schema -from strawberry.schema.config import StrawberryConfig from strawberry.schema_directive import Location from strawberry.types.unset import UNSET from tests.conftest import skip_if_gql_32 @@ -193,9 +192,7 @@ class Query: } """ - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) assert print_schema(schema) == textwrap.dedent(expected_output).strip() @@ -217,9 +214,7 @@ class Query: } """ - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) assert print_schema(schema) == textwrap.dedent(expected_output).strip() @@ -241,9 +236,7 @@ class Query: } """ - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) assert print_schema(schema) == textwrap.dedent(expected_output).strip() @@ -265,9 +258,7 @@ class Query: } """ - schema = strawberry.Schema( - query=Query, config=StrawberryConfig(auto_camel_case=False) - ) + schema = strawberry.Schema(query=Query, config={"auto_camel_case": False}) assert print_schema(schema) == textwrap.dedent(expected_output).strip()