Customize SDK Creation
Readmes
The SDK Generator will generate a README.md file and attempt to keep it up to date with the latest changes to the SDK. A README.md file will be generated from scratch if one does not exist. If one does exist, the generator will attempt to update it with the latest changes to the SDK. The generator will not overwrite any changes you have made to the README.md file.
The README.md file will contain various sections as seen in the example below:
# github.com/client-sdk
<!-- Start SDK Installation -->
## SDK Installation
```bash
go get github.com/client-sdk
```
<!-- End SDK Installation -->
<!-- Start SDK Example Usage -->
## SDK Example Usage
```go
package main
<!-- End SDK Example Usage -->
import (
"context"
"fmt"
"log"
"os"
"github.com/client-sdk"
"github.com/client-sdk/pkg/models/shared"
"github.com/client-sdk/pkg/models/operations"
)
func main() {
ctx := context.Background()
opts := []sdk.SDKOption{
sdk.WithSecurity(shared.Security{
APIKey: shared.SchemeAPIKey{
APIKey: "YOUR_API_KEY",
},
}),
}
s := sdk.New(opts...)
res, err := s.ListPets(ctx)
if err != nil {
log.Fatal(err)
}
if res.Pets != nil {
// handle response
}
}
```
<!-- Start SDK Available Operations -->
## SDK Available Operations
* `ListPets` - List all pets
<!-- End SDK Available Operations -->
The README can be modified by adding additional content before or after any of the three main sections (and their content) above:
## SDK Installation
- A installation snippet based on the package name provided in thegen.yaml
file.## SDK Example Usage
- An example usage snippet based on the first operation in the OpenAPI document.## SDK Available Operations
- A list of all the generated operations from the OpenAPI document.
The generator will not overwrite any content you have added to the README.md file. The generator will only update the content within the three main sections above.
If your would like to modify any of the autogenerated sections above, and not have them overwritten on the next generation you can remove the <!-- Start {SECTION_NAME} -->
and <!-- End {SECTION_NAME} -->
comments from the README.md file. The generator will only overwrite the content between these comments.
Usage Examples
Methods
By default, the SDK's README.md
will include a usage example from a random operation in the OpenAPI document.
To specify a particular operation to be used as the usage example, you can add the x-speakeasy-usage-example
extension to the relevant operation in the OpenAPI document. The usage example's response object handling can also be specified with the same extension (if the operation has more than one).
For example:
paths:
/pets:
get:
x-speakeasy-usage-example: true
summary: List all pets
operationId: ListPets
tags:
- pets
responses:
"200":
description: A paged array of pets
content:
application/json:
x-speakeasy-usage-example: true
schema:
$ref: "#/components/schemas/Pets"
application/xml:
schema:
$ref: "#/components/schemas/Pets"
Values
By default when generating usage examples we will use any example
values provided for schemas within your OpenAPI document. If examples aren't present we will try to determine the most relevant example to generate
from the format
field of a schema or the property name of a schema in an object. For example if the schema has format: email
or is within a property called email
we will generate a random email address as an example value.
For Security Schemes, the OpenAPI specification doesn't allow specify examples for the values needed to populate the security details when initialising the SDKs. To allow you to provide custom examples for these values, you can add the x-speakeasy-example
extension to the Security Scheme in your OpenAPI document. For example:
components:
securitySchemes:
apiKey:
type: apiKey
name: api_key
in: header
x-speakeasy-example: YOUR_API_KEY
x-speakeasy-example
just needs to be a string value, and will be used as the example value for the Security Scheme. If the Security Scheme is a basic auth scheme, the example value will be a key/value pair for the username and password split by a ;
character. For example: YOUR_USERNAME;YOUR_PASSWORD
.
## Code Comments
The Speakeasy CLI as part of its SDK generation will generate comments for Operations and Models in the generated SDKs. These comments are generated from the OpenAPI specification and are based on the summary/description of the Operations or Schemas. The comments will be generated in the target language's docstring format. For example, in Python the comments will be generated as [PEP 257](https://www.python.org/dev/peps/pep-0257/) compliant docstrings.
By default comments will be generated for all Operations and Models. To disable comment generation for your SDK, modify your `gen.yaml` file to disable them like so:
```yaml
# ...
generation:
comments:
disabled: true
Comments
Operation Comments
Comments for each method in the generated SDK will be generated from the summary/description of the Operation. For example, if you have an Operation like so:
paths:
/pets:
get:
operationId: listPets
summary: List all pets
description: Get a list of all pets in the system
responses:
"200":
description: A list of pets
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Pet"
The generated SDK will have a method commented like so:
- Go
- Python
- Typescript
- Java
// ListPets - List all pets
// Get a list of all pets in the system
func (s *SDK) ListPets(ctx context.Context) (*operations.ListPetsResponse, error) {
// ...
}
def list_pets(self) -> operations.ListPetsResponse:
r"""List all pets
Get a list of all pets in the system
"""
# ...
/**
* ListPets - List all pets
*
* Get a list of all pets in the system
**/
ListPets(
config?: AxiosRequestConfig
): Promise<operations.ListPetsResponse> {
// ...
}
/**
* ListPets - List all pets
*
* Get a list of all pets in the system
**/
public ListPetsResponse listPets() {
// ...
}
If both the summary and description are present, the summary will be used as the first line of the comment and the description will be used as the second line of the comment. If just the description is present, it will be used as the first line of the comment. If both are present but you would like to omit the description as it might be too verbose, you can use the omitdescriptionifsummarypresent
option in your gen.yaml
file like so:
# ...
generation:
comments:
omitDescriptionIfSummaryPresent: true
Model Comments
Comments for each model in the generated SDK will be generated from the description of the Schema. For example, if you have a Schema like so:
components:
schemas:
Pet:
type: object
description: A pet sold in the pet store
properties:
id:
type: integer
format: int64
name:
type: string
The generated SDK will have a model commented like so:
- Go
- Python
- Typescript
- Java
// Pet
// A pet sold in the pet store
type Pet struct {
// ...
@dataclass_json
@dataclass
class Pet:
r"""Pet
A pet sold in the pet store
"""
# ...
// Pet
/**
* A pet sold in the pet store
**/
export class Pet extends SpeakeasyBase {
// ...
/**
* Pet
*
* A pet sold in the pet store
**/
public class Pet {
// ...
Class Names
By default The Speakeasy SDKs will be generated with the Class Name SDK
. However a custom class name can be configured by modifying your gen.yaml
file to include:
generation:
sdkClassName: "myClassName"
Yields a package like:
package petshop
import (
"net/http"
"openapi/pkg/utils"
)
var ServerList = []string{
"http://petstore.speakeasy.io/v1",
}
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
type PetShop struct {
Pets *Pets
_defaultClient HTTPClient
_securityClient HTTPClient
_serverURL string
_language string
_sdkVersion string
_genVersion string
}
Namespaces
The Speakeasy CLI, as part of its SDK generation, will generate namespaces which you can use to group related operations. These namespaces are generated from the OpenAPI specification and are based on the tags associated to the operations in the spec.
By default namespaces will be generated for all tags in the spec. To disable namespace generation for your SDK, modify your gen.yaml
file to disable them like so:
# ...
generation:
tagNamespacingDisabled: true
Operation Tags
Namespaces are generated for each tag specified at the root level of the OpenAPI spec. If an operation does not have an associated tag, then it will be added to the root sdk class of the generated client library. In the case where multiple tags are associated, the operation will appear as a method in multiple classes. The example below shows one method added to a namespace, and another left to the default class:
paths:
/stores:
get:
operationId: listStores
summary: List all stores
description: Get a list of all stores in the system
responses:
"200":
description: A list of stores
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Store"
/pets:
get:
operationId: listPets
summary: List all pets
description: Get a list of all pets in the system
tags:
- pets
responses:
"200":
description: A list of pets
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Pet"
tags:
- name: pets
description: Everything about Pets
The generated SDK will have methods invocable like so:
// ListPets - List all pets
// Get a list of all pets in the system
sdk.Pets.ListPets()
sdk.ListStores()
Override Names
Speakeasy uses your OpenAPI schema to infer names for class types, methods, and parameters. However, you can override these names to tailor the generated SDK to your preferences.
The x-speakeasy-name-override
extension can be used to override the name of a class, method, or parameter. We'll detail the various locations
this extension can be placed in your OpenAPI schema to override these names at different scopes (globally vs. per operation/parameter) in the following sections.
Method Names
The x-speakeasy-name-override
extension may override the generated name for the method generated from an operation.
This extension may be used globally if placed at the root of the OpenAPI schema, where all methods with an operationId
that match the provided
operationId
regex will be overridden with methodNameOverride
.
openapi: 3.1.0
info:
title: Test
version: 0.0.1
servers:
- url: https://httpbin.org
security:
- basicAuth: []
x-speakeasy-name-override:
- operationId: get*
methodNameOverride: get
- operationId: post*
methodNameOverride: create
paths:
/test:
get:
operationId: getTest
responses:
"200":
description: OK
post:
operationId: postTest
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/Test"
responses:
"200":
description: OK
Since getTest
and postTest
match the get*
and post*
regexes defined by the global x-speakeasy-name-override
extension, these method names
will be generated as get
and create
, respectively.
Alternatively, x-speakeasy-name-override
may be placed at the operation level, where it will override the generated name for the method pertaining
only to that operation. This can be combined with the global extension above and will take precedence over it. Consider the same schema shown above but
with an operation-level extension added to the get
operation:
---
get:
operationId: getTest
x-speakeasy-name-override: getRandomTest
responses:
"200":
description: OK
Now, postTest
would still be generated as create
as before, but getTest
would be generated as getRandomTest
.
Parameter Names
The x-speakeasy-name-override
extension may override the generated name for the parameters generated from an operation.
This extension may be used globally if placed at the root of the OpenAPI schema, where all parameters with a name
that match the provided
parameterName
regex will be overridden with parameterNameOverride
.
openapi: 3.1.0
info:
title: Test
version: 0.0.1
servers:
- url: https://httpbin.org
security:
- basicAuth: []
x-speakeasy-name-override:
- parameterName: test*
parameterNameOverride: testIdentifier
paths:
/test:
get:
operationId: getTest
parameters:
- in: query
name: testId
schema:
type: integer
responses:
"200":
description: OK
/test/{testCategory}/count:
get:
operationId: getTestCountByCategory
parameters:
- in: path
name: testCategory
schema:
type: string
- in: query
name: limit
schema:
type: integer
responses:
"200":
description: OK
Since testId
and testCategory
match the test*
regex defined by the global x-speakeasy-name-override
extension, these parameter names
will both be generated as testIdentifier
across methods.
Alternatively, x-speakeasy-name-override
may be placed at the parameter level, where it will override the generated name for a single parameter.
This can be combined with the global extension above and will take precedence over it. Consider the same schema shown above but with a parameter-level
extension added to the testCategory
parameter:
---
/test/{testCategory}/count:
get:
operationId: getTestCountByCategory
parameters:
- in: path
name: testCategory
x-speakeasy-name-override: testCategoryIdentifier
schema:
type: string
- in: query
name: limit
schema:
type: integer
responses:
"200":
description: OK
Now, testId
would still be generated as testIdentifier
as before, but testCategory
would be generated as testCategoryIdentifier
.
Class Names
The x-speakeasy-name-override
extension may override the generated name for the class generated from a schema. This extension can only be used within a schema
block
for an object.
openapi: 3.1.0
info:
title: Test
version: 0.0.1
servers:
- url: https://httpbin.org
security:
- basicAuth: []
paths:
/test:
get:
operationId: getTest
responses:
"200":
description: OK
content:
application/json:
schema:
title: Res
x-speakeasy-name-override: TestResponse # The extension added at the schema level will override the generated class name for an object schema
type: object
properties:
id:
type: integer
name:
type: string
The class generated for the getTest
method's response will be named TestResponse
.
Server Configuration
Declare Base Server URL
By default, the OpenAPI spec allows you define an array of servers that can be used to make requests to the API. These servers are generally used to define different environments available for the API, but the OpenAPI spec while allowing you to add a description doesn't provide a strongly typed way to define which server to use by default (normally the production environment). The Speakeasy SDK Generator will by default use the first url in the array, meaning that someone using the SDK will be hitting that first defined server.
openapi: 3.0.3
info:
title: Example
version: 0.0.1
servers:
- url: https://prod.example.com # Used as the default URL by the SDK
description: Our production environment
- url: https://sandbox.example.com
description: Our sandbox environment
If you your OpenAPI document doesn't define any servers (either globally or per operation) then you will need to define the base server URL in the SDK Generator configuration. This is done by adding a baseServerUrl
property to the gen.yaml
file.
# ...
generation:
baseServerUrl: "https://prod.example.com"
While the SDK generator will use the first server in the array (or the configured baseServerUrl
in the gen.yaml
file) as the base URL for all requests, you can configure the SDK at runtime to use a different server like below:
- Go
- Python
- Typescript
- Java
// With a custom address
opts := []sdk.SDKOption{
sdk.WithServerURL("https://example.com"),
}
// or with one of the servers in the generated servers array
opts := []sdk.SDKOption{
sdk.WithServerURL(sdk.Servers[0]),
}
s := sdk.New(opts)
s = sdk.SDK()
# With a custom address
s.config_server_url("https://example.com")
# or with one of the servers in the generated servers array
s.config_server_url(sdk.SERVERS[0])
// With a custom address
const opts = [WithServerURL("https://example.com")];
// or with one of the servers in the generated servers array
const opts = [WithServerURL(Servers[0])];
const s = new SDK(opts);
SDK.Builder builder = SDK.builder();
// With a custom address
builder.setServerURL("https://example.com");
// or with one of the servers in the generated servers array
builder.setServerURL(SDK.SERVERS[0]);
SDK sdk = builder.build();
Server URLs also support templating to allow you to define variables in the URL that can be replaced at runtime. For example, if you have a server URL like https://{variable}.example.com
you can replace the {variable}
variable with a value like below:
- Go
- Python
- Typescript
- Java
// Pass a map of variables to replace those matching the server URL
opts := []sdk.SDKOption{
sdk.WithServerURL("https://{variable}.example.com", map[string]string{
"variable": "value",
}),
}
# Pass a dict of variables to replace those matching the server URL
s.config_server_url("https://{variable}.example.com", {"variable": "value"})
// With an object of variables to replace those matching the server URL
const opts = [
WithServerURL("https://{variable}.example.com", {
variable: "value",
}),
];
SDK.Builder builder = SDK.builder();
// With an object of variables to replace those matching the server URL
builder.setServerURL("https://{variable}.example.com", new HashMap<String, String>() {{
put("variable", "value");
}});
Declare Multiple Servers
Passing URLs or index numbers to a URL in the servers array can be cumbersome and doesn't provide a great developer experience. To make this easier, Speakeasy provides an extension to the OpenAPI spec that allows you to define an ID for each server in the Servers array. This can be done using the x-speakeasy-server-id
property on the server object.
openapi: 3.0.3
info:
title: Example
version: 0.0.1
servers:
- url: https://prod.example.com # Used as the default URL by the SDK
description: Our production environment
x-speakeasy-server-id: prod
- url: https://sandbox.example.com
description: Our sandbox environment
x-speakeasy-server-id: sandbox
This extension then enables the below configuration of the SDK at runtime:
- Go
- Python
- Typescript
- Java
// With a constant containing the server ID
opts := []sdk.SDKOption{
sdk.WithServer(sdk.ServerProd),
}
s := sdk.New(opts)
s = sdk.SDK()
# With a constant containing the server ID
s.config_server(sdk.SERVER_PROD)
// With a constant containing the server ID
const opts = [WithServer(ServerProd)];
SDK.Builder builder = SDK.builder();
// With an enum containing the server ID
builder.setServer(SDK.Servers.PROD);
A map/dict can still be passed as an optional second argument if the server URLs are templated and the methods to define a specific server URL are also still available.
Ignores
When generating a SDK you may want to exclude parts of your OpenAPI specification from being used to generate your SDK. This is can be indicated on the spec with the
x-speakeasy-ignore
extension. This extensions can be added to any part of the specification such as Paths, Operations,Parameters, Request Bodies, Responses, Headers, Schemas,
Properties, Links, Callbacks, Examples, Request Body Examples, and Response Examples. Here is an example with several instances of x-speakeasy-ignore: true
being used across the specification.
/anything/ignores:
post:
operationId: ignoresPost
tags:
- generation
parameters:
- name: testParam
in: query
schema:
type: string
- name: test_param
in: query
x-speakeasy-ignore: true
schema:
type: string
requestBodies:
application/json:
schema:
type: object
properties:
testProp:
type: string
test_prop:
x-speakeasy-ignore: true
type: string
callbackUrl:
type: string
format: uri
application/xml:
x-speakeasy-ignore: true
schema:
type: object
properties:
testProp:
type: string
callbacks:
cb:
x-speakeasy-ignore: true
"{$request.body#/callbackUrl}":
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
testProp:
type: string
required: true
responses:
"200":
description: OK
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/httpBinSimpleJsonObject"
application/xml:
x-speakeasy-ignore: true
schema:
$ref: "#/components/schemas/httpBinSimpleJsonObject"
text/plain:
x-speakeasy-ignore: true
schema:
$ref: "#/components/schemas/httpBinSimpleJsonObject"
"201":
x-speakeasy-ignore: true
description: Created
get:
x-speakeasy-ignore: true
operationId: ignoresGet
tags:
- generation
responses:
"200":
description: OK
/anything/ignoreAll:
x-speakeasy-ignore: true
get:
operationId: ignoreAllGet
tags:
- generation
responses:
"200":
description: OK
Custom HTTP Clients
The SDKs will be default use their own HTTP Client instance created at runtime. But it is also possible to configure the SDK to use a custom HTTP Client instance, provided to it when the SDK is initialised. This allows you to use HTTP Clients that may be setup to use proxies, provide custom telemetry or be preconfigured with global headers or configuration.
- Go
- Python
- Typescript
- Java
The Go SDK will accept a client that provides the same interface as the standard library's http.Client
// Your custom HTTP Client
c := &http.Client{}
opts := []sdk.SDKOption{
sdk.WithClient(c),
}
s := sdk.New(opts)
The Python SDK will accept a client from the requests
package: https://requests.readthedocs.io/en/latest/
s = sdk.SDK()
# Your custom HTTP Client
client = requests.Session()
s.config_client(client)
The Typescript SDK will accept a client from the axios
package: https://axios-http.com/docs/intro
// Your custom HTTP Client
const client = axios.create();
const opts = [WithClient(client)];
const s = new SDK(opts);
The Java SDK will accept a client that implements the HTTPClient
interface in the utils
package. This will wrap a java.net.http.HttpClient
instance and the call to send
.
// Your custom HTTP Client
YouHttpClient client = new YourHttpClient();
SDK.Builder builder = SDK.builder();
builder.setClient(client);
SDK sdk = builder.build();
Globals
Using the x-speakeasy-globals
extension it is possible to define parameters that can be configured globally on the main SDK instance and then get populated automatically for any operations
that use them. This is useful for things like customer IDs or other global configuration that is required for all operations.
openapi: 3.1.0
info:
title: Test
version: 0.0.1
servers:
- url: https://httpbin.org
x-speakeasy-globals:
parameters:
- name: customerId
in: path
schema:
type: string
paths:
/api/{customerId}:
get:
operationId: getTest
parameters:
- name: customerId # If this matches a global parameter it will be populated automatically
in: path
schema:
type: string
responses:
"200":
description: OK
If the global parameter's name
, in
and schema
match a parameter in an operation then the global parameter will be populated automatically. If the global parameter is not used in the operation then it will be ignored.
Globals will work with in: query
or in: path
and are only supported for "primitive" schemas of type string
, number
, integer
, and boolean
.
This will generate output like the following:
import example
from example.models import shared
s = example.SDK(
security=shared.Security(
api_key="YOUR_API_KEY_HERE",
),
customer_id=548814, # customerId can be set when instantiating the SDK and is used for all compatible operations
)
res = s.getTest()