Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
nylas-python Changelog
======================

Unreleased
----------------
* Added response headers to all responses from the Nylas API

v6.5.0
----------------
* Added support for Scheduler APIs
Expand Down
107 changes: 107 additions & 0 deletions examples/response_headers_demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Response Headers Demo

This example demonstrates how to access and use response headers from various Nylas API responses. It shows how headers are available in different types of responses:

1. List responses (from methods like `list()`)
2. Single-item responses (from methods like `find()`)
3. Error responses (when API calls fail)

## What You'll Learn

- How to access response headers from successful API calls
- How to access headers from error responses
- Common headers you'll encounter in Nylas API responses
- How headers differ between list and single-item responses

## Headers Demonstrated

The example will show various headers that Nylas includes in responses, such as:

- `request-id`: Unique identifier for the API request
- `x-ratelimit-limit`: Your rate limit for the endpoint
- `x-ratelimit-remaining`: Remaining requests within the current window
- `x-ratelimit-reset`: When the rate limit window resets
- And more...

## Prerequisites

Before running this example, make sure you have:

1. A Nylas API key
2. A Nylas grant ID
3. Python 3.7 or later installed
4. The Nylas Python SDK installed

## Setup

1. First, install the SDK in development mode:
```bash
cd /path/to/nylas-python
pip install -e .
```

2. Set up your environment variables:
```bash
export NYLAS_API_KEY="your_api_key"
export NYLAS_GRANT_ID="your_grant_id"
```

## Running the Example

Run the example with:
```bash
python examples/response_headers_demo/response_headers_example.py
```

The script will:
1. Demonstrate headers from a list response by fetching messages
2. Show headers from a single-item response by fetching one message
3. Trigger and catch an error to show error response headers

## Example Output

You'll see output similar to this:

```
Demonstrating Response Headers
============================

Demonstrating List Response Headers
----------------------------------
✓ Successfully retrieved messages

Response Headers:
------------------------
request-id: req_abcd1234
x-ratelimit-limit: 1000
x-ratelimit-remaining: 999
...

Demonstrating Find Response Headers
----------------------------------
✓ Successfully retrieved single message

Response Headers:
------------------------
request-id: req_efgh5678
...

Demonstrating Error Response Headers
---------------------------------
✓ Successfully caught expected error
✗ Error Type: invalid_request
✗ Request ID: req_ijkl9012
✗ Status Code: 404

Error Response Headers:
------------------------
request-id: req_ijkl9012
...
```

## Error Handling

The example includes proper error handling and will show you how to:
- Catch `NylasApiError` exceptions
- Access error details and headers
- Handle different types of API errors gracefully
139 changes: 139 additions & 0 deletions examples/response_headers_demo/response_headers_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""
Nylas SDK Example: Response Headers Demo

This example demonstrates how to access and use response headers from various Nylas API
responses, including successful responses and error cases.

Required Environment Variables:
NYLAS_API_KEY: Your Nylas API key
NYLAS_GRANT_ID: Your Nylas grant ID

Usage:
First, install the SDK in development mode:
cd /path/to/nylas-python
pip install -e .

Then set environment variables and run:
export NYLAS_API_KEY="your_api_key"
export NYLAS_GRANT_ID="your_grant_id"
python examples/response_headers_demo/response_headers_example.py
"""

import os
import sys
from typing import Optional

from nylas import Client
from nylas.models.errors import NylasApiError


def get_env_or_exit(var_name: str) -> str:
"""Get an environment variable or exit if not found."""
value = os.getenv(var_name)
if not value:
print(f"Error: {var_name} environment variable is required")
sys.exit(1)
return value


def print_response_headers(headers: dict, prefix: str = "") -> None:
"""Helper function to print response headers."""
print(f"\n{prefix} Response Headers:")
print("------------------------")
for key, value in headers.items():
print(f"{key}: {value}")


def demonstrate_list_response_headers(client: Client, grant_id: str) -> None:
"""Demonstrate headers in list responses."""
print("\nDemonstrating List Response Headers")
print("----------------------------------")

try:
# List messages to get a ListResponse
messages = client.messages.list(identifier=grant_id)

print("✓ Successfully retrieved messages")
print_response_headers(messages.headers)
print(f"Total messages count: {len(messages.data)}")

except NylasApiError as e:
print("\nError occurred while listing messages:")
print(f"✗ Error Type: {e.type}")
print(f"✗ Provider Error: {e.provider_error}")
print(f"✗ Request ID: {e.request_id}")
print_response_headers(e.headers, "Error")


def demonstrate_find_response_headers(client: Client, grant_id: str) -> None:
"""Demonstrate headers in find/single-item responses."""
print("\nDemonstrating Find Response Headers")
print("----------------------------------")

try:
# Get the first message to demonstrate single-item response
messages = client.messages.list(identifier=grant_id)
if not messages.data:
print("No messages found to demonstrate find response")
return

message_id = messages.data[0].id
message = client.messages.find(identifier=grant_id, message_id=message_id)

print("✓ Successfully retrieved single message")
print_response_headers(message.headers)

except NylasApiError as e:
print("\nError occurred while finding message:")
print(f"✗ Error Type: {e.type}")
print(f"✗ Provider Error: {e.provider_error}")
print(f"✗ Request ID: {e.request_id}")
print_response_headers(e.headers, "Error")


def demonstrate_error_response_headers(client: Client, grant_id: str) -> None:
"""Demonstrate headers in error responses."""
print("\nDemonstrating Error Response Headers")
print("---------------------------------")

try:
# Attempt to find a non-existent message
message = client.messages.find(
identifier=grant_id,
message_id="non-existent-id-123"
)

except NylasApiError as e:
print("✓ Successfully caught expected error")
print(f"✗ Error Type: {e.type}")
print(f"✗ Provider Error: {e.provider_error}")
print(f"✗ Request ID: {e.request_id}")
print(f"✗ Status Code: {e.status_code}")
print_response_headers(e.headers, "Error")


def main():
"""Main function demonstrating response headers."""
# Get required environment variables
api_key = get_env_or_exit("NYLAS_API_KEY")
grant_id = get_env_or_exit("NYLAS_GRANT_ID")

# Initialize Nylas client
client = Client(
api_key=api_key,
)

print("\nDemonstrating Response Headers")
print("============================")

# Demonstrate different types of responses and their headers
demonstrate_list_response_headers(client, grant_id)
demonstrate_find_response_headers(client, grant_id)
demonstrate_error_response_headers(client, grant_id)

print("\nExample completed!")


if __name__ == "__main__":
main()
28 changes: 16 additions & 12 deletions nylas/handler/api_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ def list(
request_body=None,
overrides=None,
) -> ListResponse:
response_json = self._http_client._execute(
response_json, response_headers = self._http_client._execute(
"GET", path, headers, query_params, request_body, overrides=overrides
)

return ListResponse.from_dict(response_json, response_type)
return ListResponse.from_dict(response_json, response_type, response_headers)


class FindableApiResource(Resource):
Expand All @@ -33,11 +33,11 @@ def find(
request_body=None,
overrides=None,
) -> Response:
response_json = self._http_client._execute(
response_json, response_headers = self._http_client._execute(
"GET", path, headers, query_params, request_body, overrides=overrides
)

return Response.from_dict(response_json, response_type)
return Response.from_dict(response_json, response_type, response_headers)


class CreatableApiResource(Resource):
Expand All @@ -50,11 +50,11 @@ def create(
request_body=None,
overrides=None,
) -> Response:
response_json = self._http_client._execute(
response_json, response_headers = self._http_client._execute(
"POST", path, headers, query_params, request_body, overrides=overrides
)

return Response.from_dict(response_json, response_type)
return Response.from_dict(response_json, response_type, response_headers)


class UpdatableApiResource(Resource):
Expand All @@ -68,11 +68,11 @@ def update(
method="PUT",
overrides=None,
):
response_json = self._http_client._execute(
response_json, response_headers = self._http_client._execute(
method, path, headers, query_params, request_body, overrides=overrides
)

return Response.from_dict(response_json, response_type)
return Response.from_dict(response_json, response_type, response_headers)


class UpdatablePatchApiResource(Resource):
Expand All @@ -86,11 +86,11 @@ def patch(
method="PATCH",
overrides=None,
):
response_json = self._http_client._execute(
response_json, response_headers = self._http_client._execute(
method, path, headers, query_params, request_body, overrides=overrides
)

return Response.from_dict(response_json, response_type)
return Response.from_dict(response_json, response_type, response_headers)


class DestroyableApiResource(Resource):
Expand All @@ -106,7 +106,11 @@ def destroy(
if response_type is None:
response_type = DeleteResponse

response_json = self._http_client._execute(
response_json, response_headers = self._http_client._execute(
"DELETE", path, headers, query_params, request_body, overrides=overrides
)
return response_type.from_dict(response_json)

# Check if the response type is a dataclass_json class
if hasattr(response_type, "from_dict") and not hasattr(response_type, "headers"):
return response_type.from_dict(response_json)
return response_type.from_dict(response_json, headers=response_headers)
13 changes: 7 additions & 6 deletions nylas/handler/http_client.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import sys
from typing import Union
from typing import Union, Tuple, Dict
from urllib.parse import urlparse, quote

import requests
from requests import Response
from requests.structures import CaseInsensitiveDict

from nylas._client_sdk_version import __VERSION__
from nylas.models.errors import (
Expand All @@ -16,7 +17,7 @@
)


def _validate_response(response: Response) -> dict:
def _validate_response(response: Response) -> Tuple[Dict, CaseInsensitiveDict]:
json = response.json()
if response.status_code >= 400:
parsed_url = urlparse(response.url)
Expand All @@ -26,10 +27,10 @@ def _validate_response(response: Response) -> dict:
or "connect/revoke" in parsed_url.path
):
parsed_error = NylasOAuthErrorResponse.from_dict(json)
raise NylasOAuthError(parsed_error, response.status_code)
raise NylasOAuthError(parsed_error, response.status_code, response.headers)

parsed_error = NylasApiErrorResponse.from_dict(json)
raise NylasApiError(parsed_error, response.status_code)
raise NylasApiError(parsed_error, response.status_code, response.headers)
except (KeyError, TypeError) as exc:
request_id = json.get("request_id", None)
raise NylasApiError(
Expand All @@ -41,9 +42,9 @@ def _validate_response(response: Response) -> dict:
),
),
status_code=response.status_code,
headers=response.headers,
) from exc

return json
return (json, response.headers)

def _build_query_params(base_url: str, query_params: dict = None) -> str:
query_param_parts = []
Expand Down
Loading
Loading