Skip to content
iscai-msft edited this page Feb 22, 2021 · 27 revisions

Python Low-Level Client

What is a Low-Level Client

Our low-level clients provide simple, reliable connection to raw HTTP for previews and advanced usage. We provide caller send_request on the client to send requests to the service. Calls through this method fully harness the power of azure-core and azure-identity. We provide both synchronous and asynchronous low-level clients.

The basic structure of calls with low-level clients is:

  1. Initialize your client
  2. Create a request
  3. Send the request
  4. Handle the response

We will go into each step in the following sections

1. Initialize Your Client

First you import your client from the namespace of your package. For example, let's say your namespace is azure.pets and your client's name is PetsClient. Your import would look like

from azure.pets import PetsClient

Most clients require authenticating through their credential parameter. Depending on what authentication support your library is using, you can either authenticate-with-aad or authenticate with an AzureKeyCredential.

Additionally, most of our clients accept an endpoint parameter at initialization, usually a link to your own resource.

Authenticating with AAD

Depending on your library, our clients support authenticating with an Azure Active Directory (AAD) token credential. We always recommend using a credential type obtained from the azure-identity library for AAD authentication. For this example, we use the most common DefaultAzureCredential.

As an installation note, the azure-identity library is not a dependency of this library. Please pip install azure-identity before using AAD authentication

pip install azure-identity

The following code snippet shows you how to authenticate with a DefaultAzureCredential.

from azure.identity import DefaultAzureCredential
from azure.pets import PetsClient

client = PetsClient(
    endpoint="https://myPets",
    credential=DefaultAzureCredential()
)

Authenticating with AzureKeyCredential

Some libraries support authenticating with an AzureKeyCredential. The following code snippet shows you how to authenticate with an AzureKeyCredential

from azure.core.credentials import AzureKeyCredential
from azure.pets import PetsClient

credential: str = "myCredential"
client = PetsClient(
    endpoint="https://myPets",
    credential=AzureKeyCredential(credential)
)

2. Create a Request

Next, you need to create the request you want to be sent to the service.

You have two options here: create the HttpRequest fully by yourself, or enlist the help of our request preparers.

When creating your HttpRequest, always refer to your service's REST API docs to see what they should look like.

Create Your Own HttpRequest

Creating an HttpRequest by yourself offers you the most flexibility, and is always a great fallback option.

One thing to note for the url you pass to your request: it can either by absolute, or you can pass in a relative url. Relative urls will be relative to the endpoint parameter you pass your client.

Below is a code example for how to create an HttpRequest. Here we POST a json body to a service's 3.0 endpoint.

from azure.core.pipeline.transport import HttpRequest


# Can use any of the following urls, the request will end up the same
# Relative urls are relative to the `endpoint` param you pass your client

absolute_url = "https://myPets/v3.0/cutePets"
relative_url = "/v3.0/cutePets"

request = HttpRequest("POST", relative_url,
    json={
        "pets": [
            {
                "name": "Fido",
                "age": 42,
                "favoriteFood": "meat"
            },
            {
                "name": "Bessie",
                "age": 1,
                "favoriteFood": "fish"
            }
        ]
    }
)

# now we format the query parameters
request.format_parameters(
    {
        "petOwnerName": "azure",
    }
)

Use our Request Preparers

We also offer request preparers to help you create requests for each of the documented endpoints. These preparers are included in the protocol folder of our libraries. With the request preparers, we handle things like the URL path and formatting query and header parameters. We also provide better intellisense, so you know just what to pass to our preparers.

API versions are an important part of our request preparers, so they are always tied to our request preparers. With different API versions, the arguments to your method calls may also change, so please be aware of this behavior.

We allow two ways to assert the API version you would like when using our request preparers. You can see our following code snippets and choose which one works best for you.

Let's make the same call as in the previous example.

  1. Directly import the API version of the request preparers you want to use

    from azure.pets.protocol.v3_0 import PetsPreparers
    from azure.core.pipeline.transport import HttpRequest
    
    request: HttpRequest = PetsPreparers.prepare_cute_pets(
        body={
            "pets": [
                {
                    "name": "Fido",
                    "age": 42,
                    "favoriteFood": "meat"
                },
                {
                    "name": "Bessie",
                    "age": 1,
                    "favoriteFood": "fish"
                }
            ]
        },
        pet_owner_name="azure",
    )
  2. Import our multi-API request preparers, and pass the API version through the required positional parameter api_version

    from azure.pets.protocol import PetsPreparers
    from azure.core.pipeline.transport import HttpRequest
    
    request: HttpRequest = PetsPreparers.prepare_cute_pets(
        api_version="v3.0",
        body={
            "pets": [
                {
                    "name": "Fido",
                    "age": 42,
                    "favoriteFood": "meat"
                },
                {
                    "name": "Bessie",
                    "age": 1,
                    "favoriteFood": "fish"
                }
            ]
        },
        pet_owner_name="azure",
    )

3. Send the Request

Now, we pass this request to your client's send_request method.

from azure.pets import PetsClient
from azure.core.pipeline.transport import HttpResponse

# Create your request. See above examples for how to.

response: HttpResponse = client.send_request(request)

4. Handle the Response

Our send_request call returns an HttpResponse.

Error handling

The response you get back from send_request will not automatically raise if your response is an error. If you wish to raise an error if your response is bad, call .raise_for_status() on your returned response. This will raise an exception defined in azure-core if the response is bad. Otherwise, nothing will happen.

JSON response

If the response you get back should be a json object, you can call .json() on your response to get it json-deserialized.

Putting this all together, see our code snippets for how you can deal with your response object

response: HttpResponse = client.send_request(request)

response.raise_for_status()  # raises an error if your response is not good

json_response = response.json()  # get your response as a json object

# Now play with your JSON response!

Stream Downloading

We also offer a way to get a generator for streaming yor response body's data, it is method stream_download() on your response object.

response: HttpResponse = client.send_request(request)

response.raise_for_status()  # raises an error if your response is not good

# iterate over the chunks in your stream
for chunk in response.stream_download(None):
    ...

Async Clients

All of our low-level client libraries also include async clients. A lot of the code you write using sync and async clients will be the same (i.e. request preparation is completely sansio, so this portion of your code will look the same). This section will only include example code for the sections that are different

Initializing Your Async Client

Initializing and authenticating an async client is largely the same as doing so for a sync client, what changes is the import location. Async clients reside in the aio folder of the namespace.

Authenticating with Azure Active Directory (AAD) token credential also differs, as you will have to use the async version of each credential.

Below, we create an async client and authenticate with async DefaultAzureCredential

from azure.identity.aio import DefaultAzureCredential
from azure.pets.aio import PetsClient


client = PetsClient(
    endpoint="https://myPets",
    credential=DefaultAzureCredential()
)

Make sure to close your clients and credentials after using to avoid unclosed client sessions!

Async send_request

As you are making an async network call, you need to await the response of .send_request().

from azure.pets.aio import PetsClient
from azure.core.pipeline.transport import AsyncHttpResponse

# Create your request. See above examples for how to.

response: AsyncHttpResponse = await client.send_request(request)

Async Response Handling

The main difference between sync and async response handling is the need to first load the whole response so following sync methods can access the response's body.

response: AsyncHttpResponse = await client.send_request(request)

response.raise_for_status()  # raises an error if your response is not good

await response.load_body()  # loads the memory into the body, so the following sync methods can access it

json_response = response.json()  # get your response as a json object

# Now play with your JSON response!

Async Stream Downloading

Async stream downloading differs from sync stream downloading, as users have to asynchronously loop over the chunks in your stream. This is because async network calls will be made to retrieve your chunks.

response: AsyncHttpResponse = await client.send_request(request)

response.raise_for_status()  # raises an error if your response is not good

# iterate over the chunks in your stream
async for chunk in response.stream_download(None):
    ...

Troubleshooting

Errors

All errors thrown by .raise_for_error() are exceptions defined in azure-core.

Logging

Our low-level clients also have logging support. They use the standard logging library for logging. Basic information about HTTP sessions (URLs, headers, etc.) is logged at INFO level.

Detailed DEBUG level logging, including request/response bodies and un-redacted headers, can be enabled on a client with the logging_enable keyword argument.

from azure.identity import DefaultAzureCredential
from azure.pets import PetsClient

client = PetsClient(
    endpoint="https://myPets",
    credential=DefaultAzureCredential(),
    logging_enable=True
)
Clone this wiki locally