# Comparison guide: OpenAPI/Swagger Python client generation

Many of our users have switched from [OpenAPI Generator](https://openapi-generator.tech/) to Speakeasy for their Python SDKs. Learn how to use both SDK creators in this guide, and the differences between them.

Open-source OpenAPI generators are great for experimentation but lack the reliability, performance, and intuitive developer experience required for critical applications. As an alternative, Speakeasy creates [idiomatic SDKs](/post/client-sdks-as-a-service) that meet the bar for enterprise use.

In this post, we'll focus on Python, but Speakeasy can also create SDKs in Go, TypeScript, Java, Ruby, PHP, and more.

<Callout title="View More Comparisons" type="info">
- [Read about the differences between TypeScript SDKs generated by Speakeasy and OpenAPI Generator.](/post/speakeasy-sdk-vs-openapi-typescript-generator/) <br/>
- [Read about the differences between Go SDKs generated by Speakeasy and OpenAPI Generator.](/post/speakeasy-oss-go-generator)
</Callout>

Here's a summary of the major differences between a Python SDK created using Speakeasy, compared to an SDK created by the OpenAPI Generator. Unless support for Python 3.9 or earlier is critically important, Speakeasy is recommended for Python SDKs.

For the full technical walkthrough, read on!

## What is OpenAPI Generator?

OpenAPI Generator, as opposed to an OpenAPI generator, is a specific community-run open-source SDK generator for the OpenAPI specification, [focusing on version 3](https://openapi-generator.tech/docs/fork-qna). The [Swagger Codegen](https://swagger.io/tools/swagger-codegen) tool is similar, but run by the company Smartbear. OpenAPI Generator was forked from Swagger Codegen.

## Preparing the SDK generators

For our comparison, we ran Speakeasy and OpenAPI Generator each in its own Docker container, which works on Windows, macOS, or Linux. Using Docker instead of running code directly on your physical machine is safer, as the code cannot access files outside the folder you specify.

We used the PetStore 3.1 YAML schema file from the [Swagger editor](https://editor-next.swagger.io) examples menu in **File > Load Example > OpenAPI 3.1 Petstore**. To follow along with this guide, save the file to a subfolder called `app` in your current path, such as `app/petstore31.yaml`. We changed the schema to use the server version 3.1:

```yaml
servers:
  - url: https://petstore31.swagger.io/api/v31
```

OpenAPI Generator provides a Docker image, but Speakeasy does not. To install the Speakeasy CLI, you can either install the Go binary directly on your computer following the steps in the [Speakeasy Getting Started](/docs/speakeasy-reference/cli/getting-started) guide, or run it in Docker, which we did.

To use Docker yourself, first create a Docker file called `se.dockerfile` with the content below, replacing `YourApiKey` with your key from the Speakeasy website.

```bash
FROM alpine:3.19
WORKDIR /app
RUN apk add bash go curl unzip sudo nodejs npm
RUN curl -fsSL https://go.speakeasy.com/cli-install.sh | sh;
ENV GOPATH=/root/go
ENV PATH=$PATH:$GOPATH/bin
ENV SPEAKEASY_API_KEY=YourApiKey
```

Then build the Speakeasy image with the command below.

```sh
docker build -f se.dockerfile -t seimage .
```

## Validating the schemas

Both OpenAPI Generator and the Speakeasy CLI can validate an OpenAPI schema. We'll run both and compare the output.

### Validation using OpenAPI Generator

To validate `petstore31.yaml` using OpenAPI Generator, run the following in the terminal:

```bash
docker run --rm -v "./app:/local" openapitools/openapi-generator-cli validate -i /local/petstore31.yaml
```

OpenAPI Generator returns two warnings:

```
Warnings:
	- Unused model: Address
	- Unused model: Customer

[info] Spec has 2 recommendation(s).
```

### Validation using Speakeasy

Validate the schema with Speakeasy by running the following in the terminal:

```bash
docker run --rm -v "./app:/app" seimage speakeasy validate openapi -s /app/petstore31.yaml
```

The Speakeasy validator returns 72 hints about missing examples, seven warnings about missing responses, and three warnings about unused components. Each warning includes a detailed JSON-formatted error with line numbers.

Since both validators returned only warnings and not errors, we can assume both generators will create SDKs without issues.

## Creating the SDKs

First, we'll generate an SDK with OpenAPI Generator, and then we'll create one with Speakeasy.

### Generating an SDK with OpenAPI Generator

OpenAPI Generator includes two different Python SDK generators (and four server generators):

* [python](https://openapi-generator.tech/docs/generators/python)
* [python-pydantic-v1](https://openapi-generator.tech/docs/generators/python-pydantic-v1)

The only difference between the two is that `python` is the latest version, which uses Pydantic V2. You can ignore Pydantic V1.

To generate an SDK from the schema file in OpenAPI Generator, we ran the command given in the [OpenAPI Generator readme](https://github.com/OpenAPITools/openapi-generator#16---docker) below.

```sh
docker run --rm -v "./app:/local" openapitools/openapi-generator-cli generate -i /local/petstore31.yaml -g python -o /local/og --additional-properties=packageName=petstore_sdk,projectName=petstore-sdk-python
```

This command gives one warning that looks like it could cause errors, `o.o.codegen.utils.ModelUtils - Failed to get the schema name: null`, but OpenAPI Generator successfully created three folders:

The generator warned us in the terminal that `Generation using 3.1.0 specs is in development and is not officially supported yet.` The OpenAPI Generator [roadmap](https://openapi-generator.tech/docs/roadmap) hasn't been updated in almost two years.

### Creating an SDK with Speakeasy

Next, we'll create an SDK using the Speakeasy CLI with the command below.

```bash
docker run --rm -v "./app:/app" seimage speakeasy quickstart
```

Speakeasy creates the following folders.

Unlike the OpenAPI Generator output, the Speakeasy output includes no tests.

## SDK code comparison: Package structure

We'll start our comparison by looking at the structure of each SDK: the OpenAPI Generator SDK and the Speakeasy SDK.

To count the lines of code in the SDKs, we'll run `cloc` for each (ignoring documentation and test folders):

```bash
cloc ./app/og/petstore_sdk
cloc ./app/se/src/openapi
```

Below are the results for each SDK.

We see that the Speakeasy SDK has about the same number of lines of code and lines of comments as OpenAPI Generator, but OpenAPI Generator leaves more blank lines.

The following commands output the files of each SDK.

```sh
tree ./app/og/petstore_sdk   # exclude docs and tests
tree ./app/se/src/openapi
```

Below is the output for OpenAPI Generator.

```sh
├── api
│   ├── __init__.py
│   ├── pet_api.py
│   ├── store_api.py
│   └── user_api.py
├── api_client.py
├── api_response.py
├── configuration.py
├── exceptions.py
├── __init__.py
├── models
│   ├── address.py
│   ├── api_response.py
│   ├── category.py
│   ├── customer.py
│   ├── __init__.py
│   ├── order.py
│   ├── pet.py
│   ├── tag.py
│   └── user.py
├── py.typed
└── rest.py
```

The folder structure is simple and clear with nothing unexpected. Files are separated at the API level (pet, store, and user) and by model. There are a few helper files, like `exceptions.py`.

Below is the output for Speakeasy.

```sh
├── basesdk.py
├── _hooks
│   ├── __init__.py
│   ├── registration.py
│   ├── sdkhooks.py
│   └── types.py
├── httpclient.py
├── __init__.py
├── models
│   ├── components
│   │   ├── apiresponse.py
│   │   ├── category.py
│   │   ├── httpmetadata.py
│   │   ├── __init__.py
│   │   ├── order.py
│   │   ├── pet.py
│   │   ├── security.py
│   │   ├── tag.py
│   │   └── user.py
│   ├── errors
│   │   ├── __init__.py
│   │   └── sdkerror.py
│   ├── __init__.py
│   └── operations
│       ├── addpet_form.py
│       ├── addpet_json.py
│       ├── addpet_raw.py
│       ├── createuser_form.py
│       ├── createuser_json.py
│       ├── createuser_raw.py
│       ├── createuserswithlistinput.py
│       ├── deleteorder.py
│       ├── deletepet.py
│       ├── deleteuser.py
│       ├── findpetsbystatus.py
│       ├── findpetsbytags.py
│       ├── getinventory.py
│       ├── getorderbyid.py
│       ├── getpetbyid.py
│       ├── getuserbyname.py
│       ├── __init__.py
│       ├── loginuser.py
│       ├── logoutuser.py
│       ├── placeorder_form.py
│       ├── placeorder_json.py
│       ├── placeorder_raw.py
│       ├── updatepet_form.py
│       ├── updatepet_json.py
│       ├── updatepet_raw.py
│       ├── updatepetwithform.py
│       ├── updateuser_form.py
│       ├── updateuser_json.py
│       ├── updateuser_raw.py
│       └── uploadfile.py
├── pet.py
├── sdkconfiguration.py
├── sdk.py
├── store.py
├── types
│   ├── basemodel.py
│   └── __init__.py
├── user.py
└── utils
    ├── enums.py
    ├── eventstreaming.py
    ├── forms.py
    ├── headers.py
    ├── __init__.py
    ├── metadata.py
    ├── queryparams.py
    ├── requestbodies.py
    ├── retries.py
    ├── security.py
    ├── serializers.py
    ├── url.py
    └── values.py
```

The Speakeasy SDK is more complex and has more features. Files are separated at a lower level than OpenAPI Generator — at the operation level - and further split into media types of the operation, like `addpet_json.py`. There are more helper files bundled with the SDK in the `utils` folder. The `_hooks` folder allows you to [insert custom code](/docs/customize/code/sdk-hooks) into the SDK.

## Calling the server

Swagger provides a complete test server for the PetStore OpenAPI version 3.1 server at https://petstore31.swagger.io. (There is a version 3.0 server too, but that gives 500 errors when called.)

To check that both SDKs contain code that actually works, we called the pet operations given in the readme files against the test server.

We used a Docker Python 3.8 container, as 3.8 is supported by both OpenAPI Generator and Speakeasy.

### Calling the server with OpenAPI Generator

For OpenAPI Generator, we ran the command below, with a successful response. First, the command installs the Python dependencies in the Docker container as recommended in the SDK `README.md` file, then it runs the sample `main.py` script to call the server using the SDK.

```sh
docker run --rm -v "./app/og:/app" -w "/app" python:3.8.19-alpine3.20 sh -c  "pip install . && python setup.py install && python main.py"

# The response of PetApi->add_pet:
# Pet(id=10, name='doggie', category=None, photo_urls=['string'], tags=[], status='available')
```

The `README.md` does not give clear instructions for installing all dependencies. After running the installation commands above, `pytest` was not installed, even though it was mentioned in `README.md`.

Below is the `main.py` script to call the API.

```py
import petstore_sdk
from petstore_sdk.models.pet import Pet
from petstore_sdk.rest import ApiException
from pprint import pprint

configuration = petstore_sdk.Configuration(
    host = "https://petstore31.swagger.io/api/v31"
)
with petstore_sdk.ApiClient(configuration) as api_client:
    api_instance = petstore_sdk.PetApi(api_client)
    pet = Pet.from_json('''{
        "id": 10,
        "name": "doggie",
        "photoUrls": [
            "string"
        ],
        "status": "available"
        }''')
    try:
        api_response = api_instance.add_pet(pet)
        print("The response of PetApi->add_pet:\n")
        pprint(api_response)
    except ApiException as e:
        print("Exception when calling PetApi->add_pet: %s\n" % e)
```

The example code was partially given in `README.md`, but we needed to add the pet JSON sample from https://petstore31.swagger.io/#/pet/addPet.

### Calling the server with Speakeasy

Speakeasy also called the server successfully. The command below is almost identical to the one for OpenAPI Generator in the previous section, except that the Speakeasy SDK has more dependencies than OpenAPI Generator. Poetry needs packages that are not included with a minimal Linux installation.

```sh
docker run --rm -v "./app/se:/app" -w "/app" python:3.8.19-alpine3.20 sh -c "apk update && apk add --no-cache gcc musl-dev libffi-dev openssl-dev python3-dev py3-cffi py3-cryptography make && pip install --upgrade pip &&
pip install . && pip install poetry && poetry install && python main.py"

# Updated pet name: doggie
```

Before `poetry install` would work however, we had to comment out the invalid line in `pyproject.toml`:

```toml
[tool.poetry.group.extraDependencies.dependencies]
# dev = "[object Object]"
```

Below is the `main.py` script to call the API. The code comes straight from the `README.md` file (except the print line), including the correct JSON to create a pet.

```py
from openapi import SDK
s = SDK(petstore_auth="<YOUR_PETSTORE_AUTH_HERE>",)
res = s.pet.update_pet_json(request={
    "name": "doggie",
    "photo_urls": [
        "<value>",
    ],
    "id": 10,
    "category": {
        "id": 1,
        "name": "Dogs",
    },
})
if res.pet is not None:
    print("Updated Pet Name:", res.pet.name)
```

## Models, data containers, and typing

Both SDKs use strong typing in their models.

The OpenAPI Generator and Speakeasy SDK models inherit from the Pydantic `BaseModel` class. [Pydantic](https://docs.pydantic.dev/latest) validates data at runtime and provides clear error messages when data is invalid.

For example, creating a `Pet` object with the `name` field set to an integer value will result in a validation error:

```python
# Python Nextgen SDK
import petstore_sdk

pet = petstore_sdk.Pet(id=1, name="Archie", photoUrls=[])

pet2 = petstore_sdk.Pet(id=2, name=2, photoUrls=[])
# > pydantic.error_wrappers.ValidationError: 1 validation error for Pet
# > name
# >   str type expected (type=type_error.str)
```

Both SDKs create a `BaseModel` pet like below.

```py
class Pet(BaseModel):
    name: Annotated[str, FieldMetadata(form=True)]
    photo_urls: Annotated[List[str], pydantic.Field(alias="photoUrls"), FieldMetadata(form=True)]
    id: Annotated[Optional[int], FieldMetadata(form=True)] = None
    category: Annotated[Optional[Category], FieldMetadata(form=FormMetadata(json=True))] = None
    tags: Annotated[Optional[List[Tag]], FieldMetadata(form=FormMetadata(json=True))] = None
    status: Annotated[Annotated[Optional[Status], PlainValidator(validate_open_enum(False))], FieldMetadata(form=True)] = None
    r"""pet status in the store"""
```

In addition, Speakeasy adds `TypedDict`s to its components. An example is from `Pet.py` below.

```py
class PetTypedDict(TypedDict):
    name: str
    photo_urls: List[str]
    id: NotRequired[int]
    category: NotRequired[CategoryTypedDict]
    tags: NotRequired[List[TagTypedDict]]
    status: NotRequired[Status]
    r"""pet status in the store"""
```

These typings provide strong runtime type checking and static type safety, which your IDE can use, too.

Speakeasy also has enums in models, which OpenAPI Generator does not. Below is an example from the pet model.

```python
class Status(str, Enum):
    r"""pet status in the store"""
    AVAILABLE = 'available'
    PENDING = 'pending'
    SOLD = 'sold'
```

Contrast this enum with the string-based specification and validation created by OpenAPI Generator:

```python
class Pet(BaseModel):
    id: Optional[StrictInt] = None
    ...
    status: Optional[StrictStr] = Field(default=None, description="pet status in the store")
    ...

    @field_validator('status')
    def status_validate_enum(cls, value):
        """Validates the enum"""
        if value is None:
            return value

        if value not in set(['available', 'pending', 'sold']):
            raise ValueError("must be one of enum values ('available', 'pending', 'sold')")
        return value

    def to_str(self) -> str:
        """Returns the string representation of the model using alias"""
        return pprint.pformat(self.model_dump(by_alias=True))

    def to_json(self) -> str:
        """Returns the JSON representation of the model using alias"""
        # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
        return json.dumps(self.to_dict())

    ... many more boilerplate methods below...
```

The OpenAPI Generator class also has to include many boilerplate methods for Pydantic, which is done for every model in the schema. Speakeasy models are more concise.

### Open enums

Speakeasy allows adding the custom attribute `x-speakeasy-unknown-values` to an OpenAPI field to allow [open enums](/post/open-enums).

```yaml
status:
    type: string
    x-speakeasy-unknown-values: allow
    description: pet status in the store
    enum:
    - available
    - pending
    - sold
```

The SDK code for an open enum doesn't change much from a standard enum. It's shown below.

```py
class Status(str, Enum, metaclass=utils.OpenEnumMeta):
    r"""pet status in the store"""
    AVAILABLE = 'available'
    PENDING = 'pending'
    SOLD = 'sold'
```

Adding `x-speakeasy-unknown-values` will result in a Python SDK that allows values for `status` that are outside the given list of `available`, `pending`, and `sold`. There will no longer be a conflict between a new version of an API sending an unknown enum value to an older version of an SDK.

The disadvantage of this open enum technique is that it allows clients to send mistaken and incorrect values to the server.

The OpenAPI Specification has no way to handle enum conflicts between schema versions currently, and so OpenAPI Generator has no similar feature to open enums. A standard solution is still being [debated on GitHub](https://github.com/OAI/OpenAPI-Specification/issues/1552).

## SDK dependencies

OpenAPI Generator and Speakeasy use almost identical Python packages.

OpenAPI Generator has the following dependencies in its `requirements.txt` file.

```text
python_dateutil >= 2.5.3
setuptools >= 21.0.0
urllib3 >= 1.25.3, < 2.1.0
pydantic >= 2
typing-extensions >= 4.7.1
```

OpenAPI Generator also has a `pyproject.toml` file, though OpenAPI Generator does not mention this file in the installation instructions.

Speakeasy has the following dependencies in its `pyproject.toml` file.

```py
httpx = "^0.27.0"
jsonpath-python = "^1.0.6"
pydantic = "^2.7.1"
python-dateutil = "^2.9.0.post0"
typing-inspect = "^0.9.0"
```

### HTTP client library

The OpenAPI Generator SDK uses `urllib3` in its HTTP clients, while the Speakeasy SDK uses the `urllib3` and the [HTTPX](https://www.python-httpx.org/) library.

HTTPX is future-proofed for HTTP/2 and asynchronous method calls. As per the Speakeasy SDK readme, you can call the server using synchronous or asynchronous calls, as shown below.

```py
import asyncio
from openapi import SDK

async def main():
    res = await s.pet.update_pet_form_async(request={
        "name": "doggie",
    ...
```

## Supported Python versions

At the time of compiling this comparison, the Speakeasy SDK required at least Python version 3.8, which is supported until October 2024. The OpenAPI Generator SDK required at least Python version 3.7, which ended support halfway through 2023.

We recommend you use the latest Python version with both SDKs.

## Retries

The SDK created by Speakeasy can automatically retry failed network requests or retry requests based on specific error responses. This provides a simpler developer experience for error handling.

To enable this feature, use the Speakeasy `x-speakeasy-retries` extension in your schema. We'll update the PetStore schema to add retries to the `addPet` operation as a test.

Edit `petstore31.yaml` and add the following to the `addPet` operation:

```yaml
      x-speakeasy-retries:
        strategy: backoff
        backoff:
          initialInterval: 500        # 500 milliseconds
          maxInterval: 60000          # 60 seconds
          maxElapsedTime: 3600000     # 5 minutes
          exponent: 1.5
```

Add this snippet to the operation:

```yaml
#...
paths:
  /pet:
    # ...
    post:
      #...
      operationId: addPet
      x-speakeasy-retries:
        strategy: backoff
        backoff:
          initialInterval: 500        # 500 milliseconds
          maxInterval: 60000          # 60 seconds
          maxElapsedTime: 3600000     # 5 minutes
          exponent: 1.5
```

Now we can rerun the Speakeasy creator to enable retries for failed network requests when creating a new pet. It is also possible to enable retries for the SDK as a whole by adding global `x-speakeasy-retries` at the root of the schema.

## Created documentation

Both Speakeasy and OpenAPI Generator create a `docs` directory with documentation and usage examples.

We found the usage examples in the Speakeasy SDK worked flawlessly, while the examples in the OpenAPI Generator SDK don't always include required fields when instantiating objects. For instance, the `Pet.md` example has the code below.

```py
# TODO update the JSON string below
json = "{}"
```

The OpenAPI Generator SDK's documentation is especially thorough regarding models and has a table of fields and their types for each model. The Speakeasy SDK's documentation is focused on usage, with a usage example for each operation for each model.

Speakeasy also creates appropriate example strings based on a field's `format` in the OpenAPI schema.

For example, if we add `format: uri` to the item for a pet's photo URLs, we can compare each SDK's usage documentation for this field.

The SDK created by Speakeasy includes a helpful example of this field that lists multiple random URLs:

```python
# Speakeasy SDK Usage Example
pet = shared.Pet(
    # ...
    photo_urls=[
        'https://salty-stag.name',
        'https://moral-star.info',
        'https://present-giggle.info',
    ]
)
```

The OpenAPI Generator SDK's documentation uses a single random string in its example:

```python
# Python SDK Usage Example
pet = Pet(
    # ...
    photo_urls=[
        "photo_urls_example"
    ]
)
```

## Automation

This comparison focuses on installing and using Speakeasy and OpenAPI Generator using the command line, but both tools can also run as part of a CI workflow. For example, you can set up a [GitHub Action](https://github.com/speakeasy-api/sdk-generation-action) to ensure your Speakeasy SDK is always up to date when your API schema changes.

## Unsupported features

At the time of writing, OpenAPI Generator does not support:

- [Data types null, any, union, and UUID](https://openapi-generator.tech/docs/generators/python/#data-type-feature).
- [OAuth and OpenID security](https://openapi-generator.tech/docs/generators/python/#security-feature).
- [Multiple servers](https://openapi-generator.tech/docs/generators/python/#global-feature) (the place where clients call your SDK) and server URLs with parameters.
- [Callbacks](https://openapi-generator.tech/docs/generators/python/#global-feature) (allowing your server to call a client).
- [Link objects](https://openapi-generator.tech/docs/generators/python/#global-feature) (relating operations to each other to indicate a workflow).

Speakeasy supports all the features above. Nullable fields in a Speakeasy SDK are marked as `Optional[]`.

Neither Speakeasy nor OpenAPI Generator supports XML requests and responses.

## Summary

Open-source tooling can be a great way to experiment, but if you're working on production code, the Speakeasy Python SDK creator will help ensure that you create reliable and performant Python SDKs.

The Speakeasy Python SDK creator uses data classes to provide good runtime performance and exceptional readability, and automatic retries for network issues make error handling straightforward. The Speakeasy-created documentation includes a working usage example for each operation. Finally, unlike other Python SDK creators, Speakeasy can publish your created SDK to PyPI and run it as part of your CI workflows.
