Utilizing Configuration Files for HTTP Clients in Python

Using a configuration file offers flexibility by allowing configuration changes without modifying the code, promotes separation of concerns, and facilitates easier maintenance. Additionally, it enhances security by keeping sensitive information, such as API keys, out of the source code. In this blog, we'll explore the way of Utilizing Configuration Files for HTTP Clients in Python.

We will cover:

  1. Creating a configurations file for a python Project
  2. Reading data from a configuration file
  3. Making HTTP requests
If you're looking for how to write various HTTP methods in Python, like GET, POST, PATCH, PUT, and DELETE, then this article will provide each method code with explaination.

and,

If you're keen on building a resilient HTTP client-server application in Go that handles retries for failed requests caused by server internal errors or network issues, I highly recommend checking out this article.

Creating a Config File for Python Project

Let's create a Python HTTP client that dynamically reads various configurations, such as HTTP request URLs, from a configuration file.

Let's create a configuration file to store details about an API we wish to utilize, including its web address and a unique token required for access.

{
    "my-cool-api-config": {
        "url": "https://api.coindesk.com/v1/bpi/currentprice.json",
        "token": "my-test-token-for-api-example-com"
    }
}

Reading Configuration Data from a Config File

After defining the configuration file, we proceed to read it within our python code. To ensure a structured approach, we create a class named SessionBasedHTTPClient, dedicated to interacting with HTTP resources.

class SessionBasedHTTPClient:
    def __init__(self):
        self.configs = {}

    def load_config(self, config):
        """Load configuration from file."""
        try:
            home_dir = os.path.expanduser("~")
            file_path = f'{home_dir}{config}'
            with open(file_path) as f:
                return json.load(f)
        except FileNotFoundError:
            raise FileNotFoundError(f"Configuration file '{file_path}' not found.")
        except json.JSONDecodeError:
            raise ValueError(f"Error decoding JSON in file '{file_path}'.")

and then read the config file,

if __name__ == "__main__":
    http_client = SessionBasedHTTPClient()
    http_client.configs = http_client.load_config('/work/python/utils-python/http-utils/configs/configs.json')

    print(http_client.configs)

Making HTTP GET Requests in Python

In this example, we demonstrate sending an HTTP GET request to a public API, specifically https://api.coindesk.com, to retrieve the current price of Bitcoin.

It utilizes the requests.Session() object for session management and connection pooling, enhancing performance. After executing the request, the method handles the response, ensuring it's successful and processing the retrieved JSON data to extract and display the current Bitcoin prices in USD, GBP, and EUR.

def make_get_request(self):
        """Make GET request."""
        try:
            url = self.configs['my-cool-api-config']['url']
            token = self.configs['my-cool-api-config']['token']

            api_headers = {'Authorization': f'Bearer {token}', 'Accept': 'application/json'}
            api_params = {'offset': 0, 'limit': 10}

            with requests.Session() as session:
                """ 
                Use requests.Session() to leverage session management and connection pooling, 
                which can improve performance if you're making multiple requests to the same host.
                """
                response = session.get(url, headers=api_headers, params=api_params)
                print(f"GET request to {url} with params {api_params} completed with status code {response.status_code}")

                response.raise_for_status()
                """
                Use response.raise_for_status() to raise an exception for bad responses (status codes 4xx and 5xx), simplifying error handling.
                """
                res = response.json()
                print(json.dumps(res, indent=4))
                print(f'Current BTC price (USD): {res["bpi"]["USD"]["rate"]}')
                print(f'Current BTC price (GBP): {res["bpi"]["GBP"]["rate"]}')
                print(f'Current BTC price (EUR): {res["bpi"]["EUR"]["rate"]}')
        except (requests.RequestException, FileNotFoundError, ValueError) as e:
            print(f"GET request failed: {e}")

In this brief tutorial, we've explored the process of organizing various configurations within a JSON config file and leveraging them to execute an HTTP request to a public API, specifically https://api.coindesk.com, to retrieve the current price of Bitcoin.

Full Code

import requests
import os
import json

class SessionBasedHTTPClient:
    def __init__(self):
        self.configs = {}

    def load_config(self, config):
        """Load configuration from file."""
        try:
            home_dir = os.path.expanduser("~")
            file_path = f'{home_dir}{config}'
            with open(file_path) as f:
                return json.load(f)
        except FileNotFoundError:
            raise FileNotFoundError(f"Configuration file '{file_path}' not found.")
        except json.JSONDecodeError:
            raise ValueError(f"Error decoding JSON in file '{file_path}'.")

    def make_get_request(self):
        """Make GET request."""
        try:
            url = self.configs['my-cool-api-config']['url']
            token = self.configs['my-cool-api-config']['token']

            api_headers = {'Authorization': f'Bearer {token}', 'Accept': 'application/json'}
            api_params = {'offset': 0, 'limit': 10}

            with requests.Session() as session:
                """ 
                Use requests.Session() to leverage session management and connection pooling, 
                which can improve performance if you're making multiple requests to the same host.
                """
                response = session.get(url, headers=api_headers, params=api_params)
                print(f"GET request to {url} with params {api_params} completed with status code {response.status_code}")

                response.raise_for_status()
                """
                Use response.raise_for_status() to raise an exception for bad responses (status codes 4xx and 5xx), simplifying error handling.
                """
                res = response.json()
                print(json.dumps(res, indent=4))
                print(f'Current BTC price (USD): {res["bpi"]["USD"]["rate"]}')
                print(f'Current BTC price (GBP): {res["bpi"]["GBP"]["rate"]}')
                print(f'Current BTC price (EUR): {res["bpi"]["EUR"]["rate"]}')
        except (requests.RequestException, FileNotFoundError, ValueError) as e:
            print(f"GET request failed: {e}")

if __name__ == "__main__":
    http_client = SessionBasedHTTPClient()
    http_client.configs = http_client.load_config('/work/python/utils-python/http-utils/configs/configs.json')

    http_client.make_get_request()
© 2024 Solution Toolkit . All rights reserved.