|
| 1 | +# Azure SDK Migration Guide: New Hybrid Model Design Generation Breaking Changes |
| 2 | + |
| 3 | +The direct link to this page can be found at aka.ms/azsdk/python/migrate/hybrid-models |
| 4 | + |
| 5 | +This guide covers the breaking changes you'll encounter when upgrading to our new model design and how to fix them in your code. |
| 6 | + |
| 7 | +Our new hybrid models are named as such because they have a dual dictionary and model nature. |
| 8 | + |
| 9 | +## Summary of Breaking Changes |
| 10 | + |
| 11 | +When migrating to the hybrid model design, expect these breaking changes: |
| 12 | + |
| 13 | +| Change | Impact | Quick Fix | |
| 14 | +| ----------------------------------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------- | |
| 15 | +| [Dictionary Access](#dictionary-access-syntax) | `as_dict()` parameter renamed, output format changed | Recommended removal of `as_dict()` and directly access model, or replace `keep_readonly=True` with `exclude_readonly=False`, expect `camelCase` keys | |
| 16 | +| [Model Hierarchy](#model-hierarchy-reflects-rest-api-structure) | Multi-level flattened properties removed | Replace `obj.level1_level2_prop` with `obj.level1.level2.prop` | |
| 17 | +| [Additional Properties](#additional-properties-handling) | `additional_properties` parameter removed | Use direct dictionary syntax: `model["key"] = value` | |
| 18 | +| [String Representation](#string-representation-matches-rest-api) | Model key output changed from `snake_case` to `camelCase` | Update any code parsing model strings to expect `camelCase` | |
| 19 | +| [Serialization/Deserialization](#serialization-and-deserialization-methods-removed) | `serialization` and `deserialization` methods removed | Use dictionary access for serialization, constructor for deserialization | |
| 20 | + |
| 21 | +## Detailed Breaking Changes |
| 22 | + |
| 23 | +### Dictionary Access Syntax |
| 24 | + |
| 25 | +**What changed**: Hybrid models support direct dictionary access and use different parameter names and output formats compared to our old models. |
| 26 | + |
| 27 | +**What will break**: |
| 28 | + |
| 29 | +- Code that relies on parameter `keep_readonly` to `.on_dict()` |
| 30 | +- Code that expects `snake_case` keys in dictionary output |
| 31 | + |
| 32 | +**Before**: |
| 33 | + |
| 34 | +```python |
| 35 | +from azure.mgmt.test.models import Model |
| 36 | +model = Model(name="example") |
| 37 | + |
| 38 | +# Dictionary access required as_dict() |
| 39 | +json_model = model.as_dict(keep_readonly=True) |
| 40 | +print(json_model["my_name"]) # snake_case key |
| 41 | +``` |
| 42 | + |
| 43 | +**After**: |
| 44 | + |
| 45 | +```python |
| 46 | +from azure.mgmt.test.models import Model |
| 47 | +model = Model(name="example") |
| 48 | + |
| 49 | +# Direct dictionary access now works |
| 50 | +print(model["myName"]) # Works directly |
| 51 | + |
| 52 | +# as_dict() parameter changed |
| 53 | +json_model = model.as_dict(exclude_readonly=False) # Parameter renamed |
| 54 | +print(json_model["myName"]) # Now returns camelCase key (matches REST API) |
| 55 | +``` |
| 56 | + |
| 57 | +**Migration steps:** |
| 58 | + |
| 59 | +- (Recommended) If you don't need a memory copy as a dict, simplify code by using direct dictionary access: `model["key"]` instead of `model.as_dict()["key"]` |
| 60 | +- Replace `keep_readonly=True` with `exclude_readonly=False` |
| 61 | +- Update code expecting `snake_case` keys to use `camelCase` keys (consistent with REST API) |
| 62 | + |
| 63 | +### Model Hierarchy Reflects REST API Structure |
| 64 | + |
| 65 | +**What changed**: Hybrid model generation preserves the actual REST API hierarchy instead of artificially flattening it. |
| 66 | + |
| 67 | +**What will break**: |
| 68 | + |
| 69 | +- We've maintained backcompat for attribute access for single-level flattened properties, but multi-level flattening will no longer be supported. |
| 70 | +- No level of flattening will be supported when dealing with the the response object from `.to_dict()`. |
| 71 | + |
| 72 | +**Before**: |
| 73 | + |
| 74 | +```python |
| 75 | +model = Model(...) |
| 76 | +print(model.properties_name) # Works |
| 77 | +print(model.properties_properties_name) # Works (artificially flattened) |
| 78 | +json_model = model.as_dict() |
| 79 | +print(json_model["properties_properties_name"]) # Works (artificially flattened) |
| 80 | +``` |
| 81 | + |
| 82 | +**After**: |
| 83 | + |
| 84 | +```python |
| 85 | + |
| 86 | +model = Model(...) |
| 87 | +print(model.properties_name) # Still works (single-level flattening maintained for compatibility) |
| 88 | +print(model.properties.name) # Equivalent to above, preferred approach |
| 89 | +print(model["properties_name"]) # ❌ Raises KeyError |
| 90 | +print(model.properties_properties_name) # ❌ Raises AttributeError |
| 91 | +print(model.properties.properties.name) # ✅ Mirrors actual API structure |
| 92 | +print(model["properties_properties_name"]) # ❌ Raises KeyError |
| 93 | +print(model["properties"]["properties"]["name"]) # ✅ Mirrors actual API structure |
| 94 | +``` |
| 95 | + |
| 96 | +**Migration steps:** |
| 97 | + |
| 98 | +Identify any properties with multiple underscores that represent nested structures |
| 99 | +Replace them with the actual nested property access using dot notation |
| 100 | + |
| 101 | +Example: `obj.level1_level2_property` → `obj.level1.level2.property` |
| 102 | +This new structure will match your REST API documentation exactly |
| 103 | + |
| 104 | +### Additional Properties Handling |
| 105 | + |
| 106 | +**What changed**: Hybrid models inherently support additional properties through dictionary-like behavior, eliminating the need for a separate additional_properties parameter. |
| 107 | +**What will break**: |
| 108 | + |
| 109 | +- Code that passes `additional_properties` parameter |
| 110 | +- Code that reads `.additional_properties` attribute |
| 111 | + |
| 112 | +**Before**: |
| 113 | + |
| 114 | +```python |
| 115 | +# Setting additional properties |
| 116 | +model = Model(additional_properties={"custom": "value"}) |
| 117 | +print(model.additional_properties) # {"custom": "value"} |
| 118 | +``` |
| 119 | + |
| 120 | +**After**: |
| 121 | + |
| 122 | +```python |
| 123 | +# ❌ Raises TypeError |
| 124 | +model = Model(additional_properties={"custom": "value"}) |
| 125 | + |
| 126 | +# ✅ Use these approaches instead |
| 127 | +model = Model({"custom": "value"}) |
| 128 | +# OR |
| 129 | +model = Model() |
| 130 | +model.update({"custom": "value"}) |
| 131 | +# OR |
| 132 | +model = Model() |
| 133 | +model["custom"] = "value" |
| 134 | + |
| 135 | +print(model) # Shows the additional properties directly |
| 136 | +``` |
| 137 | + |
| 138 | +**Migration steps:** |
| 139 | + |
| 140 | +- Remove all `additional_properties=` parameters from model constructors |
| 141 | +- Replace with direct dictionary syntax or `.update()` calls |
| 142 | +- Replace `.additional_properties` attribute access with direct dictionary access |
| 143 | + |
| 144 | +### String Representation Matches REST API |
| 145 | + |
| 146 | +**What changed**: Hybrid models string output uses `camelCase` (matching the REST API) instead of Python's `snake_case` convention in our old models. |
| 147 | +**What will break**: |
| 148 | + |
| 149 | +- Code that parses or matches against model string representations |
| 150 | +- Tests that compare string output |
| 151 | + |
| 152 | +**Before**: |
| 153 | + |
| 154 | +```python |
| 155 | +model = Model(type_name="example") |
| 156 | +print(model) # {"type_name": "example"} |
| 157 | +``` |
| 158 | + |
| 159 | +**After**: |
| 160 | + |
| 161 | +```python |
| 162 | +model = Model(type_name="example") |
| 163 | +print(model) # {"typeName": "example"} - matches REST API format |
| 164 | +``` |
| 165 | + |
| 166 | +**Migration steps:** |
| 167 | + |
| 168 | +- Update any code that parses model string representations to expect `camelCase` |
| 169 | +- Update test assertions that compare against model string output |
| 170 | +- Consider using property access instead of string parsing where possible |
| 171 | + |
| 172 | +### Serialization and Deserialization Methods Removed |
| 173 | + |
| 174 | +**What changed**: Hybrid models no longer include explicit `serialize()` and `deserialize()` methods. Models are now inherently serializable through dictionary access, and deserialization happens automatically through the constructor. |
| 175 | + |
| 176 | +**What will break**: |
| 177 | + |
| 178 | +- Code that calls `model.serialize()` or `Model.deserialize()` |
| 179 | +- Custom serialization/deserialization workflows |
| 180 | +- Code that depends on the specific format returned by the old serialization methods |
| 181 | + |
| 182 | +**Before**: |
| 183 | + |
| 184 | +```python |
| 185 | +from azure.mgmt.test.models import Model |
| 186 | +import json |
| 187 | + |
| 188 | +# Serialization |
| 189 | +model = Model(name="example", value=42) |
| 190 | +serialized_dict = model.serialize() # Returns dict using the REST API name, compatible with `json.dump` |
| 191 | +json_string = json.dumps(serialized_dict) |
| 192 | + |
| 193 | +# Deserialization |
| 194 | +json_data = json.loads(json_string) |
| 195 | +model = Model.deserialize(json_data) # Static method for deserialization |
| 196 | +print(model.name) # "example" |
| 197 | + |
| 198 | +# Custom serialization with options |
| 199 | +serialized_full = model.serialize(keep_readonly=True) |
| 200 | +serialized_minimal = model.serialize(keep_readonly=False) |
| 201 | +``` |
| 202 | + |
| 203 | +**After**: |
| 204 | + |
| 205 | +```python |
| 206 | +from azure.mgmt.test.models import Model |
| 207 | +import json |
| 208 | + |
| 209 | +# Serialization - model is already in serialized format when accessed as dictionary |
| 210 | +model = Model(name="example", value=42) |
| 211 | + |
| 212 | +# Method 1: Explicit as_dict() method (recommended) |
| 213 | +json_string = json.dumps(model.as_dict()) |
| 214 | + |
| 215 | +# Method 2: Direct dictionary access |
| 216 | +serialized_dict = {} |
| 217 | +for key in model: |
| 218 | + serialized_dict[key] = model[key] |
| 219 | + |
| 220 | +# Deserialization - pass JSON dict directly to constructor |
| 221 | +json_data = json.loads(json_string) |
| 222 | +model = Model(json_data) # Constructor handles deserialization automatically |
| 223 | +print(model.name) # "example" |
| 224 | + |
| 225 | +# Advanced: Constructor also accepts keyword arguments |
| 226 | +model = Model(name="example", value=42) # Still works as before |
| 227 | +``` |
| 228 | + |
| 229 | +**Migration steps:** |
| 230 | + |
| 231 | +- Replace serialization calls: `model.serialize()` → `model` or `model.as_dict()` or `dict(model)` |
| 232 | +- Replace deserialization calls: `Model.deserialize(data) → Model(data)` |
| 233 | +- Remove any static method imports |
| 234 | +- Update serialization options: |
| 235 | + - `serialize(keep_readonly=True)` → `as_dict(exclude_readonly=False)` |
| 236 | + - `serialize(keep_readonly=False)` → `as_dict(exclude_readonly=True)` |
| 237 | +- Test serialization format: |
| 238 | + - Verify the output format matches your expectations |
| 239 | + - Check that `camelCase` keys are handled correctly |
| 240 | + |
| 241 | +## Why These Changes? |
| 242 | + |
| 243 | +Our hybrid models prioritize consistency with the underlying REST API: |
| 244 | + |
| 245 | +- **Better API Alignment**: Model hierarchy and property names now match your REST API documentation exactly |
| 246 | +- **Improved Developer Experience**: Direct dictionary access eliminates extra method calls |
| 247 | +- **Consistency**: `camelCase` output matches what you see in REST API responses |
| 248 | +- **Maintainability**: Reduced artificial flattening makes the SDK easier to maintain and understand |
| 249 | + |
| 250 | +If you encounter issues not covered here, please file an issue on [GitHub](https://github.com/microsoft/typespec/issues) with tag `emitter:client:python`. |
0 commit comments