Skip to content

Making external requests

This page covers guidelines and requirements for making HTTP requests to external APIs from your Lime CRM solutions and packages.

Always specify timeout for external HTTP requests

Breaking change in lime-crm 2.1135.0

Starting with lime-crm 2.1135.0 (released September 2025), using Python requests library without the timeout parameter will raise an error in pytest and emit a UserWarning at runtime. This change ensures that HTTP requests don't hang indefinitely and helps maintain system stability.

When making HTTP requests to external APIs using the requests library, you must always specify a timeout parameter. This prevents requests from hanging indefinitely if the external service is slow or unresponsive.

Note

This requirement applies to all Python code in your solution or package, including event handlers, custom endpoints, scheduled tasks, and custom limeobjects.

Non-compliant code (will cause errors):

import requests

# This will raise an error in pytest and emit a warning at runtime
response = requests.get('https://api.example.com/data')
response = requests.post('https://api.example.com/create', json=data)

Compliant code (recommended):

import requests

# Always specify a timeout value (in seconds)
response = requests.get('https://api.example.com/data', timeout=30)
response = requests.post('https://api.example.com/create', json=data, timeout=30)

# For long-running requests, adjust the timeout accordingly
response = requests.get('https://api.example.com/large-file', timeout=120)

The timeout value should be chosen based on the expected response time of the external API. A typical value is 30 seconds, but adjust this based on your specific use case. For APIs that process large amounts of data or perform complex operations, you may need a higher timeout value.

Testing external requests

When testing code that makes external HTTP requests, use pytest-httpserver to mock the external service. This allows you to test your code without making actual network calls and ensures tests are fast and reliable.

Testing retry logic

Here's an example of how to test that your code properly handles retries when the external service returns a 429 (Too Many Requests) error:

import pytest
import requests
from pytest_httpserver import HTTPServer
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter


def test_request_retries_gives_retry_error(httpserver: HTTPServer):
    # Simulate a 429 (Too Many Requests) response
    httpserver.expect_request("/api/data").respond_with_data("", status=429)

    # Configure session with retry logic
    session = requests.Session()
    retry_strategy = Retry(
        total=1,
        backoff_factor=1,
        status_forcelist=[429, 503, 504, 509],
        respect_retry_after_header=True,
    )
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("http://", adapter)

    # Make the request - should raise RetryError after exhausting retries
    with pytest.raises(requests.exceptions.RetryError):
        session.get(httpserver.url_for("/api/data"), timeout=30)

Testing error handling

Here's an example of how to test that your code properly handles HTTP errors that should not be retried (like 401 Unauthorized):

def test_request_no_retries_gives_http_error(httpserver: HTTPServer):
    # Simulate a 401 (Unauthorized) response
    httpserver.expect_request("/api/data").respond_with_data("", status=401)

    # Configure session with retry logic (401 is NOT in the retry list)
    session = requests.Session()
    retry_strategy = Retry(
        total=1,
        backoff_factor=1,
        status_forcelist=[429, 503, 504, 509],
        respect_retry_after_header=True,
    )
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("http://", adapter)

    # Make the request - should raise HTTPError immediately without retries
    response = session.get(httpserver.url_for("/api/data"), timeout=30)
    with pytest.raises(requests.HTTPError):
        response.raise_for_status()