Skip to content

Commit

Permalink
Add USE_PYDANTIC_ALIAS_MODEL_MAPPING to use property name instead of …
Browse files Browse the repository at this point in the history
…pydantic alias (#519)

* Add USE_PYDANTIC_ALIAS_MODEL_MAPPING to use property name instead of pydantic alias

* Bumpversion to 2.1.1rc1

* Add documentation on USE_PYDANTIC_ALIAS_MODEL_MAPPING
  • Loading branch information
tjeerddie authored Mar 5, 2024
1 parent 69104bf commit ba26cf8
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 2.1.0
current_version = 2.1.1rc1
commit = False
tag = False
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(rc(?P<build>\d+))?
Expand Down
62 changes: 62 additions & 0 deletions docs/architecture/application/graphql.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion orchestrator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion orchestrator/graphql/autoregistration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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),), {})

Expand Down

0 comments on commit ba26cf8

Please sign in to comment.