Problem
When building mutations that accept multiple frontend fields that need to be combined/transformed before database storage, there's no hook to perform this transformation.
Real-world example: IP Address + Subnet Mask → CIDR
Frontend sends (GraphQL):
mutation {
createNetworkConfiguration(input: {
ipAddress: "192.168.1.1"
subnetMask: "255.255.255.0"
})
}
Backend needs (PostgreSQL INET):
INSERT INTO network_config (ip_address) VALUES ('192.168.1.1/24'::inet);
Current Workaround (Doesn't Work)
We tried adding a prepare_input static method to the mutation class:
@fraiseql.mutation(function="create_network_configuration")
class CreateNetworkConfiguration:
input: CreateNetworkConfigurationInput
success: CreateNetworkConfigurationSuccess
failure: CreateNetworkConfigurationError
@staticmethod
def prepare_input(input_data: dict) -> dict:
"""Convert IP + subnet mask to CIDR notation."""
ip = input_data.get("ip_address")
mask = input_data.get("subnet_mask")
if ip and mask:
# Convert to CIDR: "192.168.1.1" + "255.255.255.0" → "192.168.1.1/24"
cidr = parse_ip_input(ip, mask)
input_data["ip_address"] = cidr
input_data.pop("subnet_mask", None)
return input_data
Problem: FraiseQL's mutation decorator never calls this method.
Proposed Solution
Add hook support in fraiseql/src/fraiseql/mutations/mutation_decorator.py:
async def resolver(info, input):
"""Auto-generated resolver for PostgreSQL mutation."""
db = info.context.get("db")
if not db:
msg = "No database connection in context"
raise RuntimeError(msg)
# Convert input to dict
input_data = _to_dict(input)
# NEW: Call prepare_input if defined on mutation class
if hasattr(self.mutation_class, 'prepare_input'):
input_data = self.mutation_class.prepare_input(input_data)
# Call PostgreSQL function
full_function_name = f"{self.schema}.{self.function_name}"
# ... rest of resolver
Alternative Names
Consider these hook names:
prepare_input (current usage in PrintOptim)
transform_input (more descriptive)
input_transformer (noun form)
Benefits
- Clean separation: Frontend schema vs backend storage format
- Reusable transformations: Same pattern for dates, coordinates, compound fields
- Type safety: Transformations happen before database call
- No workarounds: No need for custom resolvers or middleware
Related Use Cases
- Date/time format conversions
- Coordinate conversions (lat/lng → PostGIS point)
- Multi-field combinations (street + city + zip → full address)
- Unit conversions (imperial → metric)
Current Impact
Without this hook, we're forced to:
- ❌ Duplicate IP conversion logic in database functions
- ❌ Handle conversion in GraphQL layer (wrong abstraction)
- ❌ Write custom resolvers (defeats purpose of
@fraiseql.mutation)
Compatibility
This is a non-breaking change:
- Existing mutations without
prepare_input work unchanged
- Optional hook only runs if defined
- No changes to mutation decorator API
Problem
When building mutations that accept multiple frontend fields that need to be combined/transformed before database storage, there's no hook to perform this transformation.
Real-world example: IP Address + Subnet Mask → CIDR
Frontend sends (GraphQL):
Backend needs (PostgreSQL INET):
Current Workaround (Doesn't Work)
We tried adding a
prepare_inputstatic method to the mutation class:Problem: FraiseQL's mutation decorator never calls this method.
Proposed Solution
Add hook support in
fraiseql/src/fraiseql/mutations/mutation_decorator.py:Alternative Names
Consider these hook names:
prepare_input(current usage in PrintOptim)transform_input(more descriptive)input_transformer(noun form)Benefits
Related Use Cases
Current Impact
Without this hook, we're forced to:
@fraiseql.mutation)Compatibility
This is a non-breaking change:
prepare_inputwork unchanged