The API surface of your client library must have the most thought as it is the primary interaction that the consumer has with your service.
✅ DO support 100% of the features provided by the Azure service the client library represents. Gaps in functionality cause confusion and frustration among developers.
Namespaces
✅ DO implement your library as a subpackage in the azure
namespace.
✅ DO pick a package name that allows the consumer to tie the namespace to the service being used. As a default, use the compressed service name at the end of the namespace. The namespace does NOT change when the branding of the product changes. Avoid the use of marketing names that may change.
A compressed service name is the service name without spaces. It may further be shortened if the shortened version is well known in the community. For example, “Azure Media Analytics” would have a compressed service name of mediaanalytics
, and “Azure Service Bus” would become servicebus
. Separate words using an underscore if necessary. If used, mediaanalytics
would become media_analytics
✔️ YOU MAY include a group name segment in your namespace (for example, azure.<group>.<servicename>
) if your service or family of services have common behavior (for example, shared authentication types).
If you want to use a group name segment, use one of the following groups:
Namespace Group | Functional Area |
---|---|
ai |
Artificial intelligence, including machine learning |
analytics |
Gathering data for metrics or usage |
data |
Dealing with structured data stores like databases |
diagnostics |
Gathering data for diagnosing issues |
digitaltwins |
Digital Twins, digital representations of physical spaces and IoT devices |
identity |
Authentication and authorization |
iot |
Internet of things |
management |
Control Plane (Azure Resource Manager) |
media |
Audio, video, or mixed reality |
messaging |
Messaging services, like push notifications or pub-sub |
search |
Search technologies |
security |
Security and cryptography |
storage |
Storage of unstructured data |
✅ DO place management (Azure Resource Manager) APIs in the mgmt
group. Use the grouping azure.mgmt.<servicename>
for the namespace. Since more services require control plane APIs than data plane APIs, other namespaces may be used explicitly for control plane only.
✅ DO register the chosen namespace with the Architecture Board. Open an issue to request the namespace. See the registered namespace list for a list of the currently registered namespaces.
✅ DO use an .aio
suffix added to the namespace of the sync client for async clients.
Example:
# Yes:
from azure.exampleservice.aio import ExampleServiceClient
# No: Wrong namespace, wrong client name...
from azure.exampleservice import AsyncExampleServiceClient
Clients
Your API surface will consist of one or more service clients that the consumer will instantiate to connect to your service, plus a set of supporting types.
✅ DO name service client types with a Client suffix.
# Yes
class CosmosClient(object) ...
# No
class CosmosProxy(object) ...
# No
class CosmosUrl(object) ...
✅ DO expose the service clients the user is more likely to interact with from the root namespace of your package.
Constructors and factory methods
✅ DO provide a constructor that takes binding parameters (for example, the name of, or a URL pointing to the service instance), a credentials
parameter, a transport
parameter, and **kwargs
for passing settings through to individual HTTP pipeline policies.
Only the minimal information needed to connect and interact with the service should be required. All additional information should be optional.
The constructor must not take a connection string.
✅ DO use a separate factory method ExampleServiceClient.from_connection_string
to create a client from a connection string (if the client supports connection strings).
The method should parse the connection string and pass the values to the constructor. Provide a from_connection_string
factory method only if the Azure portal exposes a connection string for your service.
Async support
The asyncio
library has been available since Python 3.4, and the async
/await
keywords were introduced in Python 3.5. Despite such availability, most Python developers aren’t familiar with or comfortable using libraries that only provide asynchronous methods.
✅ DO provide both sync and async versions of your APIs
✅ DO use the async
/await
keywords (requires Python 3.5+). Don’t use the yield from coroutine or asyncio.coroutine syntax.
✅ DO provide two separate client classes for synchronous and asynchronous operations. Don’t combine async and sync operations in the same class.
# Yes
# In module azure.example
class ExampleClient(object):
def some_service_operation(self, name, size) ...
# In module azure.example.aio
class ExampleClient:
# Same method name as sync, different client
async def some_service_operation(self, name, size) ...
# No
# In module azure.example
class ExampleClient(object):
def some_service_operation(self, name, size) ...
class AsyncExampleClient: # No async/async pre/postfix.
async def some_service_operation(self, name, size) ...
# No
# In module azure.example
class ExampleClient(object): # Don't mix'n match with different method names
def some_service_operation(self, name, size) ...
async def some_service_operation_async(self, name, size) ...
✅ DO use the same client name for sync and async packages
Example:
Sync/async | Namespace | Package name | Client name |
---|---|---|---|
Sync | azure.sampleservice |
azure-sampleservice |
azure.sampleservice.SampleServiceClient |
Async | azure.sampleservice.aio |
azure-sampleservice-aio |
azure.sampleservice.aio.SampleServiceClient |
✅ DO use the same namespace for the synchronous client as the synchronous version of the package with .aio
appended.
☑️ YOU SHOULD ship a separate package for async support if the async version requires additional dependencies.
✅ DO use the same name for the asynchronous version of the package as the synchronous version of the package with -aio
appended.
✅ DO use aiohttp
as the default HTTP stack for async operations. Use azure.core.pipeline.transport.AioHttpTransport
as the default transport
type for the async client.
Hierarchical services
Many services have resources with nested child (or sub) resources. For example, Azure Storage provides an account that contains zero or more containers, which in turn contains zero or more blobs.
✅ DO create a client type corresponding to each level in the hierarchy except for leaf resource types. You may omit creating a client type for leaf node resources.
✅ DO make it possible to directly create clients for each level in the hierarchy. The constructor can be called directly or via the parent.
class ChildClient:
# Yes:
__init__(self, parent, name, credentials, **kwargs) ...
class ChildClient:
# Yes:
__init__(self, url, credentials, **kwargs) ...
✅ DO provide a get_<child>_client(self, name, **kwargs)
method to retrieve a client for the named child. The method must not make a network call to verify the existence of the child.
✅ DO provide method create_<child>(...)
that creates a child resource. The method should return a client for the newly created child resource.
☑️ YOU SHOULD provide method delete_<child>(...)
that deletes a child resource.
Service operations
☑️ YOU SHOULD prefer the usage one of the preferred verbs for method names.
Verb | Parameters | Returns | Comments |
---|---|---|---|
create_\<noun> |
key, item, [allow_overwrite=True] |
Created item | Create new item. Fails if item already exists. |
upsert_\<noun> |
key, item | item | Create new item, or update existing item. Verb is primarily used in database-like services |
set_\<noun> |
key, item | item | Create new item, or update existing item. Verb is primarily used for dictionary-like properties of a service |
update_\<noun> |
key, partial item | item | Fails if item doesn’t exist. |
replace_\<noun> |
key, item | item | Completely replaces an existing item. Fails if the item doesn’t exist. |
append_\<noun> |
item | item | Add item to a collection. Item will be added last. |
add_\<noun> |
index, item | item | Add item to a collection. Item will be added at the given index. |
get_\<noun> |
key | item | Raises an exception if item doesn’t exist |
list_\<noun> |
azure.core.Pageable[Item] |
Return an iterable of items. Returns iterable with no items if no items exist (doesn’t return None or throw) |
|
\<noun>\_exists |
key | bool |
Return True if the item exists. Must raise an exception if the method failed to determine if the item exists (for example, the service returned an HTTP 503 response) |
delete_\<noun> |
key | None |
Delete an existing item. Must succeed even if item didn’t exist. |
remove_\<noun> |
key | removed item or None |
Remove a reference to an item from a collection. This method doesn’t delete the actual item, only the reference. |
✅ DO standardize verb prefixes outside the list of preferred verbs for a given service across language SDKs. If a verb is called download
in one language, we should avoid naming it fetch
in another.
✅ DO prefix methods with begin_
for long running operations. Long running operations must return a Poller object.
✅ DO support the common arguments for service operations:
Name | Description | Applies to | Notes | |
---|---|---|---|---|
timeout |
Timeout in seconds | All service methods | ||
headers |
Custom headers to include in the service request | All requests | Headers are added to all requests made (directly or indirectly) by the method. | |
continuation_token |
Opaque token indicating the first page to retrieve. Retrieved from a previous Paged return value. |
list operations. |
||
client_request_id |
Caller specified identification of the request. | Service operations for services that allow the client to send a client-generated correlation ID. | Examples of this include x-ms-client-request-id headers. |
The client library must use this value if provided, or generate a unique value for each request when not specified. |
response_hook |
callable that is called with (response, headers) for each operation. |
All service methods |
✅ DO accept a Mapping (dict
-like) object in the same shape as a serialized model object for parameters.
# Yes:
class Model(object):
def __init__(self, name, size):
self.name = name
self.size = size
def do_something(model: "Model"):
...
do_something(Model(name='a', size=17)) # Works
do_something({'name': 'a', 'size', '17'}) # Does the same thing...
✅ DO use “flattened” named arguments for update_
methods. May additionally take the whole model instance as a named parameter. If the caller passes both a model instance and individual key=value parameters, the explicit key=value parameters override whatever was specified in the model instance.
class Model(object):
def __init__(self, name, size, description):
self.name = name
self.size = size
self.description = description
class Client(object):
def update_model(self, name=None, size=None, model=None): ...
model = Model(name='hello', size=4711, description='This is a description...')
client.update_model(model=model, size=4712) # Will send a request to the service to update the model's size to 4712
model.description = 'Updated'
model.size = -1
# Will send a request to the service to update the model's size to 4713 and description to 'Updated'
client.update_model(name='hello', size=4713, model=model)
Response formats
Requests to the service fall into two basic groups - methods that make a single logical request, or a deterministic sequence of requests. An example of a single logical request is a request that may be retried inside the operation. An example of a deterministic sequence of requests is a paged operation.
The logical entity is a protocol neutral representation of a response. For HTTP, the logical entity may combine data from headers, body, and the status line. For example, you may wish to expose an ETag
header as a property on the logical entity.
✅ DO optimize for returning the logical entity for a given request. The logical entity MUST represent the information needed in the 99%+ case.
✅ DO return a value that implements the Paged protocol for operations that return collections. The Paged protocol allows the user to iterate through all items in a returned collection, and also provides a method that gives access to individual pages.
✅ DO return a value that implements the Poller protocol for long running operations.
Models
✅ DO implement __repr__
for model types. The representation must include the type name and any key properties (that is, properties that help identify the model instance).
✅ DO truncate the output of __repr__
after 1024 characters.
Authentication
✅ DO use the credentials classes in azure-core
whenever possible.
✔️ YOU MAY add additional credential types if required by the service. Contact @adparch for guidance if you believe you have need to do so.
✅ DO support all authentication methods that the service supports.