Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions docs/developer/sdk_from_swagger_and_typespec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# Overview

This document compares Python SDKs generated from Swagger (OpenAPI) specifications versus TypeSpec. For clarity, we'll refer to these as "Swagger SDKs" and "TypeSpec SDKs" respectively.

## Model

### Model Structure

#### Msrest Model

Swagger SDKs are generated from [Swagger specifications](https://github.com/Azure/azure-rest-api-specs/tree/main/specification) using [@autorest/python](https://www.npmjs.com/package/@autorest/python), and implement the Msrest model pattern. The following example illustrates the fundamental structure of an Msrest model:

```python
class Person(_serialization.Model):
_attribute_map = {
"name": {"key": "name", "type": "str"},
"parent_name": {"key": "parentName", "type": "str"},
}

def __init__(self, *, name: Optional[str] = None, parent_name: Optional[str] = None) -> None:
...
```

#### DPG Model

TypeSpec SDKs are generated from [TypeSpec](https://github.com/microsoft/typespec/) using [@azure-tools/typespec-python](https://www.npmjs.com/package/@azure-tools/typespec-python), and implement the DPG model pattern. The following example demonstrates the fundamental structure of a DPG model:

```python
class Person(_Model):
name: Optional[str] = rest_field()
parent_name: Optional[str] = rest_field(name="parentName")

@overload
def __init__(
self,
*,
name: Optional[str] = None,
parent_name: Optional[str] = None,
) -> None: ...

@overload
def __init__(self, mapping: Mapping[str, Any]) -> None: ...


def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
```

### Model Usage

#### Msrest Model Usage

```python
msrest_model = Person(name="xxx", parent_name="xxx")
print(msrest_model.name)
print(msrest_model.parent_name)

# Access model as a dictionary
json_model = msrest_model.as_dict()
print(json_model["name"])
print(json_model["parentName"])
```

#### DPG Model Usage

```python
dpg_model = Person(name="xxx", parent_name="xxx")
print(dpg_model.name)
print(dpg_model.parent_name)

# Access model directly as a dictionary
print(dpg_model["name"])
print(dpg_model["parentName"])
```

By comparing these usage patterns, we can see that DPG models can be accessed directly as dictionaries without calling `.as_dict()`, providing a more convenient experience.

#### Usage Note

For backward compatibility, DPG models continue to support the `.as_dict()` method for existing SDK users.

### Model Flattening
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can jsut stick with simple, and have a note about how nested flattening etc is supported in msrest but not typespec. No need to add examples for these more complex scenarios, i think we can just briefly talk about iot

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it is doc for internal use, I think it is easy to describe and understand if there is an example.


When a property is marked with `"x-ms-flatten": "true"` (as described [here](https://azure.github.io/autorest/extensions/#x-ms-client-flatten)), nested properties can be accessed directly in Msrest models as follows:

#### Simple Flattening Example

```python
class Person(_serialization.Model):
_attribute_map = {
"name": {"key": "properties.name", "type": "str"},
}

def __init__(self, *, name: Optional[str] = None) -> None:
...

msrest_model = Person(name="xxx")
print(msrest_model.name) # equivalent to `msrest_model.serialize()["properties"]["name"]`
```

When an inner property name matches an outer property name, a prefix is added to avoid name collisions:

#### Complex Flattening Example

```python
class Person(_serialization.Model):
_attribute_map = {
"name": {"key": "name", "type": "str"},
"properties_name": {"key": "properties.name", "type": "str"},
}

def __init__(self, *, name: Optional[str] = None) -> None:
...

msrest_model = Person(name="xxx", properties_name="properties_name")
print(msrest_model.name)
print(msrest_model.properties_name) # equivalent to `msrest_model.serialize()["properties"]["name"]`
```

Due to inconsistent usage of flattening in some Swagger specifications, property names can become unwieldy and user-unfriendly. For this reason, **DPG models do not support flattening**.

#### Flattening Compatibility Note

For legacy SDKs generated from Swagger that are migrated to TypeSpec, we've designed a [compatibility mechanism](https://azure.github.io/typespec-azure/docs/howtos/generate-client-libraries/07types/#flattening) to minimize breaking changes. However, for deeply nested flattened properties, code updates may be required:

```python
# Msrest model
msrest_model = Model(...)

print(msrest_model.properties_name) # A, equivalent to `msrest_model.serialize()["properties"]["name"]`
print(msrest_model.properties_properties_name) # B, equivalent to `msrest_model.serialize()["properties"]["properties"]["name"]`

# After migration to TypeSpec
dpg_model = Model(...)
print(dpg_model.properties_name) # A, backwards compatible but not recommended
print(dpg_model.properties.name) # equivalent to A
print.dpg_model.properties_properties_name) # no longer works
print(dpg_model.properties.properties.name) # recommended approach after migration
```

### Additional Properties

#### Additional Properties in Msrest Models

To support [additional properties](https://www.apimatic.io/openapi/additionalproperties), Msrest models include an `additional_properties` parameter:

```python
msrest_model = Model(additional_properties={"hello": "world"})
print(msrest_model.as_dict()) # output is `{"hello": "world"}`
```

#### Additional Properties in DPG Models

DPG models inherently support additional properties through dictionary-like behavior:

```python
dpg_model = Model({"hello": "world"})
# or
dpg_model = Model()
dpg_model.update({"hello": "world"})
# or
dpg_model = Model()
dpg_model["hello"] = "world"

print(dpg_model) # output is `{"hello": "world"}`
```

#### Additional Properties Note

DPG models don't have property named `additional_properties` anymore.

## Operations

### Query/Header Parameters in Operations

Query and header parameters in Swagger-generated SDKs are positional, while in TypeSpec-generated SDKs they are keyword-only:

```python
# Swagger SDK
client.operation("header", "query") # A
client.operation(header_parameter="header", query_parameter="query") # equivalent to A

# After migration to TypeSpec
client.operation("header", "query") # no longer works
client.operation(header_parameter="header", query_parameter="query") # correct approach
```

## File Name Changes

After migration, some internal file names change, but these changes do not affect SDK users:

```
_xxx_client.py => _client.py
_xxx_enums.py => _enum.py
_models_py3.py => _models.py
```

### File Name Note

Files that name starts with `_` are internal files.
Loading