diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 99475fe3a..9deab5fee 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.1.0 +current_version = 2.1.1rc1 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(rc(?P\d+))? diff --git a/docs/architecture/application/graphql.md b/docs/architecture/application/graphql.md index 3c990f17e..56154f7b1 100644 --- a/docs/architecture/application/graphql.md +++ b/docs/architecture/application/graphql.md @@ -7,6 +7,7 @@ - [Federating with Autogenerated Types](#federating-with-autogenerated-types) - [Federating with Specific Subscriptions](#federating-with-specific-subscriptions) - [Federating with Specific Subscription Product Blocks](#federating-with-specific-subscription-product-blocks) + - [Usage of USE_PYDANTIC_ALIAS_MODEL_MAPPING](#usage-of-use_pydantic_alias_model_mapping) - [Overriding types](#overriding-types) - [Overriding CustomerType and Resolvers](#overriding-customertype-and-resolvers) - [CustomerType Override](#customertype-override) @@ -304,6 +305,67 @@ class YourProductBlock: By following these examples, you can effectively federate autogenerated types (`subscriptions` and `product blocks`) enabling seamless integration across multiple GraphQL endpoints. +### Usage of USE_PYDANTIC_ALIAS_MODEL_MAPPING + +`USE_PYDANTIC_ALIAS_MODEL_MAPPING` is a mapping to prevent pydantic field alias from being used as field names when creating strawberry types in the domain model autoregistration. +Our usecase for this is that functions decorated with pydantics `@computed_field` and `@property` in domain models are not converted to strawberry fields inside the strawberry types. +to add the function properties, we use a aliased pydantic field: + +```python +class ExampleProductInactive(SubscriptionModel, is_base=True): + # this aliased property is used to add `property_example` as strawberry field. + # you need a default it the value can't be `None` since it doesn't directly add the return value of property_example + aliased_property_example: Field(alias="property_example", default="") + + # this computed property function does not get converted into the strawberry type. + @computed_field # type: ignore[misc] + @property + def property_example(self) -> str: + return "example" + + +class ExampleProductProvisioning( + ExampleProductInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING] +): + pass + + +class ExampleProduct(ExampleProductInactive, lifecycle=[SubscriptionLifecycle.ACTIVE]): + pass +``` + +The problem with this is that strawberry automatically uses the alias name and doesn't camelcase it so the strawberry field becomes `property_example`. +To fix it and have camelcasing, we can prevent aliases from being used in strawberry.type using the created mapping `USE_PYDANTIC_ALIAS_MODEL_MAPPING`: + +```python +from orchestrator.graphql.autoregistration import USE_PYDANTIC_ALIAS_MODEL_MAPPING + +USE_PYDANTIC_ALIAS_MODEL_MAPPING.update({"ExampleProductSubscription": False}) +``` + +which would now give us a strawberry field `aliasedPropertyExample`. +To name it `propertyExample` you can't override the function property name and have two choices. + +1. camelcase the aliased property: + ```python + class ExampleProductInactive(SubscriptionModel, is_base=True): + # this aliased property is used to add `property_example` as strawberry field. + propertyExample: Field(alias="property_example") + ``` + +2. rename the property function and name the aliased field correctly, when accessing outside of the graphql field, you do need to use `computed_property_example` instead of `property_example`: + ```python + class ExampleProductInactive(SubscriptionModel, is_base=True): + # this aliased property is used to add `property_example` as strawberry field. + property_example: Field(alias="computed_property_example") + + # this computed property function does not get converted into the strawberry type. + @computed_field # type: ignore[misc] + @property + def computed_property_example(self) -> str: + return "example" + ``` + ## Overriding Types Overriding strawberry types can be achieved through various methods. One less desirable approach involves extending classes using class inheritance. diff --git a/orchestrator/__init__.py b/orchestrator/__init__.py index 9c8887b6d..88cd425c8 100644 --- a/orchestrator/__init__.py +++ b/orchestrator/__init__.py @@ -13,7 +13,7 @@ """This is the orchestrator workflow engine.""" -__version__ = "2.1.0" +__version__ = "2.1.1rc1" from orchestrator.app import OrchestratorCore from orchestrator.settings import app_settings, oauth2_settings diff --git a/orchestrator/graphql/autoregistration.py b/orchestrator/graphql/autoregistration.py index 85f41f87b..e62282dd2 100644 --- a/orchestrator/graphql/autoregistration.py +++ b/orchestrator/graphql/autoregistration.py @@ -31,6 +31,11 @@ EnumDict = dict[str, EnumMeta] +# Mapping to specifically prevent aliases for fields from being used when creating strawberry types. +# use like `USE_PYDANTIC_ALIAS_MODEL_MAPPING.updates({ "ProductNameSubscription": False })`, default is True. +# more info can be read here: https://workfloworchestrator.org/orchestrator-core/architecture/application/graphql/#usage-of-use-pydantic-alias-model-mapping +USE_PYDANTIC_ALIAS_MODEL_MAPPING: dict[str, bool] = {} + def create_strawberry_enum(enum: Any) -> EnumMeta: return strawberry.enum(enum) @@ -90,7 +95,11 @@ def create_subscription_strawberry_type(strawberry_name: str, model: type[Domain directives = [Key(fields="subscriptionId", resolvable=UNSET)] pydantic_wrapper = strawberry.experimental.pydantic.type( - model, all_fields=True, directives=directives, description=f"{strawberry_name} Type" + model, + all_fields=True, + directives=directives, + description=f"{strawberry_name} Type", + use_pydantic_alias=USE_PYDANTIC_ALIAS_MODEL_MAPPING.get(strawberry_name, True), ) return type(strawberry_name, (pydantic_wrapper(base_type),), {})