The Azure SDK should be designed to enhance the productivity of developers connecting to Azure services. Other qualities (such as completeness, extensibility, and performance) are important but secondary. Productivity is achieved by adhering to the principles described below:azu
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.
These guidelines were written primarily with a HTTP based request/response in mind, but many general guidelines apply to other types of services as well. This includes, but is not limited to, packaging and naming, tools and project structures.
Please contact theArchitecture board for more guidance on non HTTP/REST based services.
✅DO support Python 3.8+.
Your API surface will consist of one or moreservice clients that the consumer will instantiate to connect to your service, plus a set of supporting types.
The service client is the primary entry point for users of the library. A service client exposes one or more methods that allow them to interact with the service.
✅DO expose the service clients the user is more likely to interact with from the root namespace of your package. Specialized service clients may be placed in sub-namespaces.
✅DO name service client types with aClient suffix.
✅DO provide separate sync and async clients. See theAsync Support section for more information.
# YesclassCosmosClient:...# NoclassCosmosProxy:...# NoclassCosmosUrl:...✅DO make the service client immutable. See theClient Immutability section for more information.
Only the minimal information needed to connect and interact with the service should be required in order to construct a client instance. All additional information should be optional and passed in as optional keyword-only arguments.
✅DO provide a constructor that takes positional binding parameters (for example, the name of, or a URL pointing to the service instance), a positionalcredential parameter, atransport keyword-only parameter, and keyword-only arguments for passing settings through to individual HTTP pipeline policies. See theAuthentication section for more information on thecredential parameter.
⛔️DO NOT use an “options bag” object to group optional parameters. Instead, pass as individual keyword-only arguments.
✅DO accept optional default request options as keyword arguments and pass them along to its pipeline policies. SeeCommon service operation parameters for more information.
# Change default number of retries to 18 and overall timeout to 2s.client=ExampleClient('https://contoso.com/xmpl',DefaultAzureCredential(),max_retries=18,timeout=2)✅DO allow users to pass in atransport keyword-only argument that allows the caller to specify a specific transport instance. The default value should be theRequestsTransport for synchronous clients and theAioHttpTransport for async clients.
✅DO use a separate factory classmethodfrom_connection_string to create a client from a connection string (if the client supports connection strings). Thefrom_connection_string factory method should take the same set of arguments (excluding information provided in the connection string) as the constructor. The constructor (__init__ method)must not take a connection string, even if it means that using thefrom_connection_string is the only supported method to create an instance of the client.
The methodshould parse the connection string and pass the values along with any additional keyword-only arguments exceptcredential to the constructor. Only provide afrom_connection_string factory method if the Azure portal exposes a connection string for your service.
classExampleClientWithConnectionString:@classmethoddef_parse_connection_string(cls,connection_string):...@classmethoddeffrom_connection_string(cls,connection_string,**kwargs):endpoint,credential=cls._parse_connection_string(connection_string)returncls(endpoint,credential,**kwargs)"""Example client using some of the most common API patterns"""importmodelsimportazure.core.pipeline.transportastransportsclassThing:"""A simple model type representing a Thing. :ivar name: The name of the thing. :vartype name: str :ivar size: The size of the thing. :vartype size: int """def__init__(self,name:str,size:int)->None:"""Create a new Thing :param name: The name of the thing :type name: str :param size: The size of the thing :type size: int """# Please note that we are using attributes rather than properties.self.name=nameself.size=size@classmethoddeffrom_response(self,response:"azure.core.rest.HttpResponse")->"Thing":"""Factory method to, given a response, construct a ~Thing """returnThing(**response.context['deserialized_data'])def__repr__(self):# For simple model types, we can just dump our __dict__ and# truncate the output at 1024 characters.returnjson.dumps(self.__dict__)[:1024]classExampleClient:def__init__(self,endpoint:str,credential:"azure.core.credentials.TokenCredential",**kwargs)->None:"""Create a new example client instance :param endpoint: Endpoint to connect to. :type endpoint str: :param credential: Credentials to use when connecting to the service. :type credential: ~azure.core.credentials.TokenCredential :keyword api_version: API version to use when talking to the service. Default is '2020-12-31' :paramtype api_version: str :keyword transport: HttpTransport to use. Default is ~transports.RequestsHttpTransport. :paramtype transport: ~azure.core.pipeline.transport.HttpTransport """self._api_version=kwargs.pop('api_version','2020-12-31')transport=kwargs.pop('transport',None)ortransports.RequestsTransport(**kwargs)# continue to build up your client...self._pipeline=[...,# List of policies for this specific clienttransport]@classmethoddeffrom_connection_string(cls,connection_string:str,**kwargs)->"Thing":"""Optional factory method if the service supports connection strings :param connection_string: Connection string containing endpoint and credentials :type connection_string: str :returns: The newly created client. :rtype: ~ExampleClient """endpoint,credential=_parse(connection_string)returncls(endpoint,credential,**kwargs)defget_thing(self,name:str,**kwargs)->"Thing":"""Get the Thing with name `name`. :param name: The name of the ~Thing to get :type name: str :rtype: ~Thing """model_factory=kwargs.pop('cls',Thing.from_response)request=self._build_get_thing_request(name)# Pass along all policy parameters when making the requestresponse=self._pipeline.send(request,**kwargs)returnmodel_factory(response)deflist_things(self,**kwargs)->"azure.core.paging.ItemPaged[Thing]":"""List all things. :rtype: ~azure.core.ItemPaged[~Thing] """...returnazure.core.paging.ItemPaged(...)defbegin_restart_thing(self,name:str,**kwargs)->"azure.core.polling.LROPoller[bool]":"""Restart the thing :param name: The name of the thing to restart :type name: str """model=kwargs.pop('cls',dict)request=self._build_begin_restart_thing(name)# Pass along all policy parameters when making the requestresponse=self._pipeline.send(request,**kwargs)# TODO: show how to construct the poller instancereturnazure.core.polling.LROPoller(...)✔️YOU MAY use a separate factory classmethodfrom_<resource type>_url (e.g.from_blob_url) to create a client from a URL (if the service relies on passing URLs to resources around - e.g. Azure Blob Storage). Thefrom_url factory method should take the same set of optional keyword arguments as the constructor.
✅DO accept an optionalapi_version keyword-only argument of type string. If specified, the provided api version MUST be used when interacting with the service. If the parameter is not provided, the default value MUST be the latest non-preview API version understood by the client library (if there the service has a non-preview version) or the latest preview API version understood by the client library (if the service does not have any non-preview API versions yet). This parameter MUST be available even if there is only one API version understood by the service in order to allow library developers to lock down the API version they expect to interact with the service with.
fromazure.identityimportDefaultAzureCredential# By default, use the latest supported API versionlatest_known_version_client=ExampleClient('https://contoso.com/xmpl',DefaultAzureCredential())# ...but allow the caller to specify a specific API version as welllspecific_api_version_client=ExampleClient('https://contoso.com/xmpl',DefaultAzureCredential(),api_version='1971-11-01')✅DO document the service API version that is used by default.
✅DO document in which API version a feature (function or parameter) was introduced in if not all service API versions support it.
✔️YOU MAY validate the inputapi_version value against a list of supported API versions.
✔️YOU MAY include all service API versions that are supported by the client library in aServiceVersion enumerated value.
| Name | Description |
|---|---|
credential | Credentials to use when making service requests (SeeAuthentication) |
application_id | Name of the client application making the request. Used for telemetry |
api_version | API version to use when making service requests (SeeService Version) |
transport | Override the default HTTP transport (SeeClient Configuration) |
✅DO design the client to be immutable. This does not mean that you need to use read-only properties (attributes are still acceptable), but rather that the there should not be any scenarios that require callers to change properties/attributes of the client.
☑️YOU SHOULD prefer the usage one of the preferred verbs for method names. You should have a good (articulated) reason to have an alternate verb for one of these operations.
| Verb | Parameters | Returns | Comments |
|---|---|---|---|
create_\<noun> | key, item,[allow_overwrite=False] | 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.ItemPaged[Item] | Return an iterable ofItems. Returns an iterable with no items if no items exist (doesn’t returnNone or throw) | |
\<noun>\_exists | key | bool | ReturnTrue 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 orNone | 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 calleddownload in one language, we should avoid naming itfetch in another.
✅DO prefix methods withbegin_ forlong running operations.
✅DO prefix methods withlist_ for methods that enumerate (lists) resources
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 anETag header as anetag attribute on the logical entity. For more information seeModel Types.
✅DO optimize for returning the logical entity for a given request. The logical entity MUST represent the information needed in the 99%+ case.
✅DO raise an exception if the method call failed to accomplish the user specified task. This includes both situations where the service actively responded with a failure as well as when no response was received. SeeExceptions for more information.
client=ComputeClient(...)try:# Please note that there is no status code etc. as part of the response.# If the call fails, you will get an exception that will include the status code# (if the request was made)virtual_machine=client.get_virtual_machine('example')print(f'Virtual machine instance looks like this:{virtual_machine}')exceptazure.core.exceptions.ServiceRequestErrorase:print(f'Failed to make the request - feel free to retry. But the specifics are here:{e}')exceptazure.core.exceptions.ServiceResponseErrorase:print(f'The request was made, but the service responded with an error. Status code:{e.status_code}')Do not returnNone or aboolean to indicate errors:
# Yestry:resource=client.create_resource(name)exceptazure.core.errors.ResourceExistsException:print('Failed - we need to fix this!')# Noresource=client.create_resource(name):ifnotresource:print('Failed - we need to fix this!')⛔️DO NOT throw an exception for “normal responses”.
Consider anexists method. The methodmust distinguish between the service returned a client error 404/NotFound and a failure to even make a request:
# Yestry:exists=client.resource_exists(name):ifnotexists:print("The resource doesn't exist...")exceptazure.core.errors.ServiceRequestError:print("We don't know if the resource exists - so it was appropriate to throw an exception!")# Notry:client.resource_exists(name)exceptazure.core.errors.ResourceNotFoundException:print("The resource doesn't exist... but that shouldn't be an exceptional case for an 'exists' method")✅DO provide an optional keyword argumenttimeout to allow callers to specify how long they are willing to wait for the method to complete. Thetimeout is in seconds, and should be honored to the best extent possible.
✅DO use the standardasyncio.Task.cancel method to cancel async methods.
✅DO provide optional operation-specific arguments as keyword only. Seepositional and keyword-only arguments for more information.
✅DO provide keyword-only arguments that override per-request policy options. The name of the parameters MUST mirror the name of the arguments provided in the client constructor or factory methods.For a full list of supported optional arguments used for pipeline policy and transport configuration (both at the client constructor and per service operation), see theAzure Core developer documentation.
✅DO qualify a service parameter name if it conflicts with any of the documented pipeline policy or transport configuration options used with all service operations and client constructors.
# Set the default number of retries to 18 and timeout to 2s for this client instance.client=ExampleClient('https://contoso.com/xmpl',DefaultAzureCredential(),max_retries=18,timeout=2)# Override the client default timeout for this specific call to 32s (but max_retries is kept to 18)client.do_stuff(timeout=32)The service client will have several methods that send requests to the service.Service parameters are directly passed across the wire to an Azure service.Client parameters aren’t passed directly to the service, but used within the client library to fulfill the request. Parameters that are used to construct a URI, or a file to be uploaded are examples of client parameters.
✅DO validate client parameters. Validation is especially important for parameters used to build up the URL since a malformed URL means that the client library will end up calling an incorrect endpoint.
# No:defget_thing(name:str)->str:url=f'https://<host>/things/{name}'returnrequests.get(url).json()try:thing=get_thing('')# Ooops - we will end up calling '/things/' which usually lists 'things'. We wanted a specific 'thing'.exceptValueError:print('We called with some invalid parameters. We should fix that.')# Yes:defget_thing(name:str)->str:ifnotname:raiseValueError('name must be a non-empty string')url=f'https://<host>/things/{name}'returnrequests.get(url).json()try:thing=get_thing('')exceptValueError:print('We called with some invalid parameters. We should fix that.')⛔️DO NOT validate service parameters. Don’t do null checks, empty string checks, or other common validating conditions on service parameters. Let the service validate all request parameters.
✅DO verify that the developer experience when the service parameters are invalid to ensure appropriate error messages are generated by the service. Work with the service team if the developer experience is compromised because of service-side error messages.
✅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. | |
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 includex-ms-client-request-id headers. | The client librarymust 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:classThing:def__init__(self,name,size):self.name=nameself.size=sizedefdo_something(thing:"Thing"):...do_something(Thing(name='a',size=17))# Worksdo_something({'name':'a','size','17'})# Does the same thing...✅DO use “flattened” named arguments forupdate_ 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.
classThing:def__init__(self,name,size,description):self.name=nameself.size=sizeself.description=descriptiondef__repr__(self):returnjson.dumps({"name":self.name,"size":self.size,"description":self.description})[:1024]classClient:defupdate_thing(self,name=None,size=None,thing=None):...thing=Thing(name='hello',size=4711,description='This is a description...')client.update_thing(thing=thing,size=4712)# Will send a request to the service to update the model's size to 4712thing.description='Updated'thing.size=-1# Will send a request to the service to update the model's size to 4713 and description to 'Updated'client.update_thing(name='hello',size=4713,thing=thing)Services may require multiple requests to retrieve the complete set of items in large collections. This is generally done by the service returning a partial result, and in the response providing a token or link that the client can use to retrieve the next batch of responses in addition to the set of items.
In Azure SDK for Python cilent libraries, this is exposed to users through theItemPaged protocol. TheItemPaged protocol optimizes for retrieving the full set of items rather than forcing users to deal with the underlying paging.
✅DO return a value that implements theItemPaged protocol for operations that return collections. TheItemPaged protocol allows the user to iterate through all items in a returned collection, and also provides a method that gives access to individual pages.
client=ExampleClient(...)# List all things - paging happens transparently in the# background.forthinginclient.list_things():print(thing)# The protocol also allows you to list things by page...forpage_no,pageinenumerate(client.list_things().by_page()):print(page_no,page)✔️YOU MAY expose aresults_per_page keyword-only parameter where supported by the service (e.g. an OData$top query parameter).
⚠️YOU SHOULD NOT expose a continuation parameter in thelist_ client method - this is supported in theby_page() function.
client=ExampleClient(...)# No - don't pass in the continuation token directly to the method...forthinginclient.list_things(continuation_token='...'):print(thing)# Yes - provide a continuation_token to in the `by_page` method...forpageinclient.list_things().by_page(continuation_token='...'):print(page)✅DO return a value that implements theItemPaged protocol even if the service API currently do not support server driven paging. This allows server driven paging to be added to the service API without introducing breaking changes in the client library.
Service operations that take a long time (currently defined in theMicrosoft REST API Guidelines as not completing in 0.5s in P99) to complete are modeled by services as long running operations.
Python client libraries abstracts the long running operation using theLong running operation Poller protocol.In cases where a service API is not explicitly implemented as a long-running operation, but the common usage pattern requires a customer to sleep or poll a status - it’s likely that these API’s should still be represented in the SDK using the Poller protocol.
✅DO return an object that implements thePoller protocol for long running operations.
✅DO use abegin_ prefix for all long running operations.
✅DO add a keyword-onlymatch_condition parameter for service methods that support conditional requests. The parameter should support theazure.core.MatchConditions type defined inazure-core as input.
✅DO add a keyword-onlyetag parameter for service methods that support conditional requests. For service methods that take a model instance that has anetag property, the explicitetag value passed in overrides the value in the model instance.
classThing:def__init__(self,name,etag):self.name=nameself.etag=etagthing=client.get_thing('theName')# Uses the etag from the retrieved thing instance....client.update_thing(thing,name='updatedName',match_condition=azure.core.MatchConditions.IfNotModified)# Uses the explicitly provided etag.client.update_thing(thing,name='updatedName2',match_condition=azure.core.MatchConditions.IfNotModified,etag='"igotthisetagfromsomewhereelse"')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. Youmay 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.
classChildClient:# Yes:__init__(self,parent,name,credentials,**kwargs)...classChildClient:# Yes:__init__(self,url,credentials,**kwargs)...✅DO provide aget_<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 methodcreate_<child>(...) that creates a child resource. The methodshould return a client for the newly created child resource.
☑️YOU SHOULD provide methoddelete_<child>(...) that deletes a child resource.
Client libraries represent entities transferred to and from Azure services as model types. Certain types are used for round-trips to the service. They can be sent to the service (as an addition or update operation) and retrieved from the service (as a get operation). These should be named according to the type. For example, aConfigurationSetting in App Configuration, or aVirtualMachine on for Azure Resource Manager.
Data within the model type can generally be split into two parts - data used to support one of the champion scenarios for the service, and less important data. Given a typeFoo, the less important details can be gathered in a type calledFooDetails and attached toFoo as thedetails attribute.
✅DO support dicts as alternative inputs to model types.
✅DO craft a constructor for models that are intended to be instantiated by a user (i.e. non-result types) with minimal required information and optional information as keyword-only arguments.
✔️YOU MAY expose models from the generated layer by adding to the root__init__.py (and__all__) if they otherwise meet the guidelines.
⛔️DO NOT duplicate models between the root andaio namespace.
In order to facilitate round-trip of responses (common in get resource -> conditionally modify resource -> set resource workflows), output model types should use the input model type (e.g.ConfigurationSetting) whenever possible. TheConfigurationSetting type should include both server generated (read-only) attributes even though they will be ignored when used as input to the set resource method.
<model>Item for each item in an enumeration if the enumeration returns a partial schema for the model. For example, GetBlobs() return an enumeration of BlobItem, which contains the blob name and metadata, but not the content of the blob.<operation>Result for the result of an operation. The<operation> is tied to a specific service operation. If the same result can be used for multiple operations, use a suitable noun-verb phrase instead. For example, useUploadBlobResult for the result fromUploadBlob, butContainerChangeResult for results from the various methods that change a blob container.✅DO use a simple Mapping (e.g.dict) rather than creating a<operation>Result class if the<operation>Result class is not used as an input parameter for other APIs.
The following table enumerates the various models you might create:
| Type | Example | Usage |
|---|---|---|
| Secret | The full data for a resource | |
| SecretDetails | Less important details about a resource. Attached to | |
| SecretItem | A partial set of data returned for enumeration | |
| AddSecretResult | A partial or different set of data for a single operation | |
| SecretChangeResult | A partial or different set of data for multiple operations on a model |
# An example of a model type.classConfigurationSetting:"""Model type representing a configuration setting :ivar name: The name of the setting :vartype name: str :ivar value: The value of the setting :vartype value: object """def__init__(self,name:str,value:object):self.name=nameself.value=valuedef__repr__(self)->str:returnjson.dumps(self.__dict__)[:1024]✅DO use extensible enumerations.
✅DO use UPPERCASE names for enum names.
# YesclassMyGoodEnum(str,Enum):ONE='one'TWO='two'# NoclassMyBadEnum(str,Enum):One='one'# No - using PascalCased name.two='two'# No - using all lower case name.☑️YOU SHOULD prefer raisingexisting exception types from theazure-core package over creating new exception types.
⛔️DO NOT create new exception types when abuilt-in exception type will suffice.
⚠️YOU SHOULD NOT create a new exception type unless the developer can handle the error programmatically. Specialized exception types related to service operation failures should be based on existing exception types from theazure-core package.
For higher-level methods that use multiple HTTP requests, either the last exception or an aggregate exception of all failures should be produced.
✅DO include any service-specific error information in the exception. Service-specific error information must be available in service-specific properties or fields.
✅DO document the errors that are produced by each method. Don’t document commonly thrown errors that wouldn’t normally be documented in Python (e.g.ValueError,TypeError,RuntimeError etc.)
✅DO use the credentials classes inazure-core whenever possible.
✅DO use authentication policy implementations inazure-core whenever possible.
✔️YOU MAY add additional credential types if required by the service. Contact theArchitecture board for guidance if you believe you have need to do so.
✅DO support all authentication methods that the service supports.
In the guidelines below, the term “namespace” is used to denote a python package or module (i.e. something that you wouldimport in your code). The term “distribution package” is used to describe the artifact you publish to and install from your package manager (i.e. something that you wouldpip install).
✅DO implement your library as a sub-package of theazure root namespace.
Note: You MUST NOT use
microsoftas your root namespace. If you need to includemicrosoftin the namespace (e.g. because of policy requirements for extensions to other projects such asopentelemetry), you should concatenate it with the package specific namespace with an underscore (e.g.microsoft_myservice). You may still usemicrosoft-myserviceas the distribution package name in this scenario.
✅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 ofmediaanalytics, and “Azure Service Bus” would becomeservicebus. Separate words using an underscore if necessary. For example,mediaanalytics could be separated intomedia_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).
✅DO avoid introducing new distribution packages that only differ in name. For existing packages, this means that you should not change the name of the package just to introduce a group name.
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 |
containers | Services related to containers |
communication | Communication services |
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 and video technologies |
messaging | Messaging services, like push notifications or pub-sub |
mixedreality | Mixed reality technologies |
monitor | Services that are offered by Azure Monitor |
quantum | Quantum computing technologies |
search | Search technologies |
security | Security and cryptography |
storage | Storage of unstructured data |
✅DO place management (Azure Resource Manager) APIs in themgmt group. Use the groupingazure.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 theArchitecture Board. Open anissue to request the namespace. Seethe 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:fromazure.exampleservice.aioimportExampleServiceClient# No: Wrong namespace, wrong client name...fromazure.exampleserviceimportAsyncExampleServiceClientHere are some examples of namespaces that meet these guidelines:
azure.storage.blobazure.keyvault.certificatesazure.ai.textanalyticsazure.mgmt.servicebusTheasyncio library has been available since Python 3.4, and theasync/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 theasync/await keywords (requires Python 3.5+). Do not use theyield from coroutine or asyncio.coroutine syntax.
✅DO provide two separate client classes for synchronous and asynchronous operations. Do not combine async and sync operations in the same class.
# Yes# In module azure.exampleclassExampleClient:defsome_service_operation(self,name,size)...# In module azure.example.aioclassExampleClient:# Same method name as sync, different clientasyncdefsome_service_operation(self,name,size)...# No# In module azure.exampleclassExampleClient:defsome_service_operation(self,name,size)...classAsyncExampleClient:# No async/async pre/postfix.asyncdefsome_service_operation(self,name,size)...# No# In module azure.exampleclassExampleClient:# Don't mix'n match with different method namesdefsome_service_operation(self,name,size)...asyncdefsome_service_operation_async(self,name,size)...✅DO use the same client name for sync and async packages
Example:
| Sync/async | Namespace | Distribution 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.
Example:
fromazure.storage.blobimportBlobServiceClient# Sync clientfromazure.storage.blob.aioimportBlobServiceClient# Async client☑️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 useaiohttp as the default HTTP stack for async operations. Useazure.core.pipeline.transport.AioHttpTransport as the defaulttransport type for the async client.
✅DO name your package after the namespace of your main client class. For example, if your main client class is in theazure.data.tables namespace, your package name should be azure-data-tables.
✅DO use all lowercase in your package name with a dash (-) as a separator.
⛔️DO NOT use underscore (_) or period (.) in your package name. If your namespace includes underscores, replace them with dash (-) in the distribution package name.
✅DO follow the specific package guidance from theazure-sdk-packaging wiki
✅DO follow thenamespace package recommendations for Python 3.x for packages that only need to target 3.x.
✅DO provide both source distributions (sdist) and wheels.
✅DO publish both source distributions (sdist) and wheels to PyPI.
✅DO test correct behavior for both CPython and PyPy forpure anduniversal Python wheels.
✅DO depend onazure-nspkg for Python 2.x.
✅DO depend onazure-<group>-nspkg for Python 2.x if you are using namespace grouping.
✅DO include__init__.py for the namespace(s) in sdists
There are occasions when common code needs to be shared between several client libraries. For example, a set of cooperating client libraries may wish to share a set of exceptions or models.
✅DO gainArchitecture Board approval prior to implementing a common library.
✅DO minimize the code within a common library. Code within the common library is available to the consumer of the client library and shared by multiple client libraries within the same namespace.
A common library will only be approved if:
Let’s take two examples:
Implementing two Cognitive Services client libraries, we find that they both rely on the same business logic. This is a candidate for choosing a common library.
Two Cognitive Services client libraries have models (data classes) that are the same in shape, but has no or minimal logic associated with them. This is not a good candidate for a shared library. Instead, implement two separate classes.
✅DO usesemantic versioning for your package.
✅DO use thebN pre-release segment forbeta releases.
Don’t use pre-release segments other than the ones defined inPEP440 (aN,bN,rcN). Build tools, publication tools, and index servers may not sort the versions correctly.
✅DO change the version number ifanything changes in the library.
✅DO increment the patch version if only bug fixes are added to the package.
✅DO increment the minor version if any new functionality is added to the package.
✅DO increment (at least) the minor version if the default REST API version is changed, even if there’s no public API change to the library.
⛔️DO NOT increment the major version for a new REST API version unless it requires breaking API changes in the python library itself.
✅DO increment the major version if there are breaking changes in the package. Breaking changes require prior approval from theArchitecture Board.
✅DO select a version number greater than the highest version number of any other released Track 1 package for the service in any other scope or language.
The bar to make a breaking change is extremely high for stable client libraries. We may create a new package with a different name to avoid diamond dependency issues.
✅DO only pick external dependencies from the following list of well known packages for shared functionality:
| Package | Usage |
|---|---|
requests | Synchronous HTTP |
aiohttp | Asynchronous HTTP |
aiodns | Asynchronous DNS resolution |
typing-extensions | Backports for Python type hints |
cryptography | Cryptographic recipes and primitives |
certifi | Mozilla CA bundle |
⛔️DO NOT use external dependencies outside the list of well known dependencies. To get a new dependency added, contact theArchitecture Board.
⛔️DO NOT vendor dependencies unless approved by theArchitecture Board.
When you vendor a dependency in Python, you include the source from another package as if it was part of your package.
⛔️DO NOT pin a specific version of a dependency unless that is the only way to work around a bug in said dependencies versioning scheme.
Only applications are expected to pin exact dependencies. Libraries are not. A library should use acompatible release identifier for the dependency.
✅DO seek approval by theArchitecture Board before implementing a binary extension.
✅DO support Windows, Linux (manylinux - seePEP513,PEP571), and MacOS. Support the earliest possible manylinux to maximize your reach.
✅DO support both x86 and x64 architectures.
✅DO follow thedocumentation guidelines unless explicitly overridden in this document.
✅DO provide docstrings for all public modules, types, constants and functions.
✅DO document any**kwargs directly consumed by a method. You may refer to the signature of a called method if the**kwargs are passed through.
Example:
defrequest(method,url,headers,**kwargs):...defget(*args,**kwargs):"Calls `request` with the method "GET" and forwards all other arguments."returnrequest("GET",*args,**kwargs)✅DO document exceptions that may be raised explicitly in the method and any exceptions raised by the called method.
✅DO include example code snippets alongside your library’s code within the repository. The snippets should clearly and succinctly demonstrate the operations most developers need to perform with your library. Include snippets for every common operation, and especially for those that are complex or might otherwise be difficult for new users of your library. At a bare minimum, include snippets for the champion scenarios you’ve identified for the library.
✅DO build and test your example code snippets using the repository’s continuous integration (CI) to ensure they remain functional.
✅DO include the example code snippets in your library’s docstrings so they appear in its API reference. If the language and its tools support it, ingest these snippets directly into the API reference from within the docstrings. Each sample should be a validpytest.
Use theliteralinclude directive in Python docstrings to instruct Sphinx to [ingest the snippets automatically][1].
⛔️DO NOT combine more than one operation in a code snippet unless it’s required for demonstrating the type or member, or it’sin addition to existing snippets that demonstrate atomic operations. For example, a Cosmos DB code snippet should not include both account and container creation operations–create two different snippets, one for account creation, and one for container creation.
There are several documentation deliverables that must be included in or as a companion to your client library. Beyond complete and helpful API documentation within the code itself (docstrings), you need a great README and other supporting documentation.
README.md - Resides in the root of your library’s directory within the SDK repository; includes package installation and client library usage information. ([example][https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/appconfiguration/azure-appconfiguration/README.md])API reference - Generated from the docstrings in your code; published on docs.microsoft.com.Code snippets - Short code examples that demonstrate single (atomic) operations for the champion scenarios you’ve identified for your library; included in your README, docstrings, and Quickstart.Quickstart - Article on docs.microsoft.com that is similar to but expands on the README content; typically written by your service’s content developer.Conceptual - Long-form documentation like Quickstarts, Tutorials, How-to guides, and other content on docs.microsoft.com; typically written by your service’s content developer.✅DO include your service’s content developer in the adparch review for your library. To find the content developer you should work with, check with your team’s Program Manager.
✅DO follow theAzure SDK Contributors Guide. (MICROSOFT INTERNAL)
✅DO adhere to the specifications set forth in the Microsoft style guides when you write public-facing documentation. This applies to both long-form documentation like a README and the docstrings in your code. (MICROSOFT INTERNAL)
☑️YOU SHOULD attempt to document your library into silence. Preempt developers’ usage questions and minimize GitHub issues by clearly explaining your API in the docstrings. Include information on service limits and errors they might hit, and how to avoid and recover from those errors.
As you write your code,doc it so you never hear about it again. The less questions you have to answer about your client library, the more time you have to build new features for your service.
Code samples are small applications that demonstrate a certain feature that is relevant to the client library. Samples allow developers to quickly understand the full usage requirements of your client library. Code samples shouldn’t be any more complex than they needed to demonstrate the feature. Don’t write full applications. Samples should have a high signal to noise ratio between useful code and boilerplate code for non-related reasons.
✅DO include code samples alongside your library’s code within the repository. The samples should clearly and succinctly demonstrate the code most developers need to write with your library. Include samples for all common operations. Pay attention to operations that are complex or might be difficult for new users of your library. Include samples for the champion scenarios you’ve identified for the library.
✅DO place code samples within the /samples directory within the client library root directory. The samples will be packaged into the resulting distribution package.
✅DO ensure that each sample file is runnable.
✅DO avoid using features newer than the Python 3 baseline support. The current supported Python version is 3.8.
✅DO ensure that code samples can be easily grafted from the documentation into a users own application. For example, don’t rely on variable declarations in other samples.
✅DO write code samples for ease of reading and comprehension over code compactness and efficiency.
✅DO ensure that samples can run in Windows, macOS, and Linux development environments.
⛔️DO NOT combine multiple scenarios in a code sample unless it’s required for demonstrating the type or member. For example, a Cosmos DB code sample doesn’t include both account and container creation operations. Create a sample for account creation, and another sample for container creation.
Combined scenarios require knowledge of additional operations that might be outside their current focus. The developer must first understand the code surrounding the scenario they’re working on, and can’t copy and paste the code sample into their project.