Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 12 additions & 1 deletion PyFetch/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
"""HTTP CLI client library for making HTTP requests."""
"""A lightweight and flexible HTTP client library for Python.

This package provides the `HTTPClient` for making HTTP requests and custom
exceptions for handling errors. It is designed to be used both as a
command-line tool and as a library in other Python applications.

Public API:
- `HTTPClient`: The main client for making HTTP requests.
- `HTTPClientError`: Base exception for client errors.
- `HTTPConnectionError`: Exception for connection-related issues.
- `ResponseError`: Exception for bad HTTP responses.
"""

from PyFetch.exceptions import HTTPClientError, HTTPConnectionError, ResponseError
from PyFetch.http_client import HTTPClient
Expand Down
7 changes: 6 additions & 1 deletion PyFetch/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
"""Entry point for the HTTP CLI application."""
"""Main entry point for the PyFetch command-line application.

This module allows the PyFetch application to be executed as a package
by running `python -m PyFetch`. It handles the initial execution and
catches common exceptions like `KeyboardInterrupt`.
"""

import sys

Expand Down
56 changes: 50 additions & 6 deletions PyFetch/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
"""Command-line interface for making HTTP requests with support for common HTTP methods."""
"""Command-line interface for making HTTP requests.

This module provides a command-line interface (CLI) for making HTTP requests
using the PyFetch HTTP client. It supports common HTTP methods, custom headers,
JSON data, and other features.
"""

import argparse
import json
Expand All @@ -10,7 +15,17 @@


def show_examples(suppress_output=False):
"""Show examples of how to use the HTTP CLI client."""
"""Prints usage examples for the PyFetch CLI.

This function displays a list of common commands to guide the user.

Args:
suppress_output (bool, optional): If True, the output is not printed
to the console. Defaults to False.

Returns:
str: A string containing the usage examples.
"""
examples = """
Examples:
1. Normal GET request:
Expand Down Expand Up @@ -52,7 +67,14 @@ def show_examples(suppress_output=False):


def add_common_arguments(parser):
"""Add arguments that are common to multiple commands"""
"""Adds common command-line arguments to the given parser.

This function standardizes the arguments for URL, timeout, headers, and verbosity
across different sub-commands.

Args:
parser (argparse.ArgumentParser): The parser to which the arguments will be added.
"""
parser.add_argument("url", help="Target URL")
parser.add_argument(
"-t",
Expand All @@ -73,11 +95,24 @@ def add_common_arguments(parser):
action="store_true",
help="Enable verbose logging for debugging.",
)


def create_parser():
"""Create an argument parser for the HTTP CLI client."""
"""Creates and configures the argument parser for the CLI.

This function sets up the main parser and subparsers for each supported
HTTP method, defining the available commands and their arguments.

Returns:
argparse.ArgumentParser: The configured argument parser.
"""

class CustomFormatter(argparse.HelpFormatter):
"""Custom formatter for the HTTP CLI client."""
"""Custom help formatter to support multi-line help messages.

This formatter allows help text to be split into multiple lines
by prefixing it with "R|".
"""

def _split_lines(self, text, width):
if text.startswith("R|"):
Expand Down Expand Up @@ -163,7 +198,16 @@ def _split_lines(self, text, width):


def main(suppress_output=False):
"""Main function for the HTTP CLI client."""
"""The main entry point for the PyFetch CLI.

This function parses command-line arguments, initializes the HTTP client,
and executes the requested HTTP command. It also handles response printing
and error reporting.

Args:
suppress_output (bool, optional): If True, suppresses all output to the
console, which is useful for testing. Defaults to False.
"""
parser = create_parser()
args = parser.parse_args()

Expand Down
24 changes: 20 additions & 4 deletions PyFetch/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
"""Custom exceptions for the HTTP CLI client."""
"""Custom exceptions for the PyFetch HTTP client.

This module defines a hierarchy of custom exceptions to provide more specific
error information when using the PyFetch client.
"""


class HTTPClientError(Exception):
"""Base exception for HTTP client errors"""
"""Base exception for all errors raised by the PyFetch client.

This exception serves as the base for more specific client-related errors,
allowing for consolidated error handling.
"""


class HTTPConnectionError(HTTPClientError):
"""Raised when connection fails"""
"""Raised when a connection to the target server fails.

This error typically occurs due to network issues, DNS failures, or if the
server is unreachable.
"""


class ResponseError(HTTPClientError):
"""Raised when response is invalid"""
"""Raised when the server returns an unsuccessful HTTP status code.

This error is triggered for responses with 4xx (client error) or 5xx
(server error) status codes.
"""
145 changes: 133 additions & 12 deletions PyFetch/http_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
"""HTTP client implementation for making HTTP requests."""
"""HTTP client for making asynchronous HTTP requests with retries.

This module provides a flexible HTTP client for making RESTful API calls,
with support for customizable timeouts, automatic retries on failures,
and optional progress bars for large downloads.
"""

import requests
from tqdm import tqdm
Expand All @@ -7,9 +12,29 @@


class HTTPClient:
"""HTTP client for making HTTP requests."""
"""A versatile HTTP client for making requests to a web server.

This client supports common HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
and includes features like configurable timeouts, retries, and verbose logging.

Attributes:
timeout (int): The request timeout in seconds.
retries (int): The number of times to retry a failed request.
verbose (bool): If True, enables detailed logging of requests and responses.
show_progress (bool): If True, displays a progress bar for large downloads.
allowed_methods (list): A list of supported HTTP methods.
MIN_SIZE_FOR_PROGRESS (int): The minimum file size in bytes to trigger the progress bar.
"""

def __init__(self, timeout=30, retries=3, verbose=False, show_progress=False):
"""Initializes the HTTPClient with configuration options.

Args:
timeout (int, optional): The timeout for HTTP requests in seconds. Defaults to 30.
retries (int, optional): The number of retry attempts for failed requests. Defaults to 3.
verbose (bool, optional): Whether to enable verbose logging. Defaults to False.
show_progress (bool, optional): Whether to show a progress bar for large downloads. Defaults to False.
"""
self.timeout = timeout
self.retries = retries
self.verbose = verbose
Expand All @@ -26,7 +51,18 @@ def __init__(self, timeout=30, retries=3, verbose=False, show_progress=False):
self.MIN_SIZE_FOR_PROGRESS = 5 * 1024 * 1024 # 5MB

def _create_progress_bar(self, total, desc):
"""Create a progress bar for file transfer."""
"""Creates a `tqdm` progress bar if conditions are met.

A progress bar is created if `show_progress` is True and the file size (`total`)
is greater than or equal to `MIN_SIZE_FOR_PROGRESS`.

Args:
total (int): The total size of the file transfer in bytes.
desc (str): A description to display with the progress bar.

Returns:
tqdm.tqdm or None: A `tqdm` progress bar instance or `None` if the conditions are not met.
"""
if self.show_progress and total >= self.MIN_SIZE_FOR_PROGRESS:
return tqdm(
total=total,
Expand All @@ -38,7 +74,18 @@ def _create_progress_bar(self, total, desc):
return None

def _stream_response(self, response, progress_bar=None):
"""Stream response content with progress indication."""
"""Streams the response content and updates the progress bar.

This method iterates over the response content in chunks, allowing for efficient
handling of large responses and real-time progress updates.

Args:
response (requests.Response): The HTTP response object.
progress_bar (tqdm.tqdm, optional): The progress bar instance to update. Defaults to None.

Returns:
bytes: The full response content as a byte string.
"""
chunk_size = 8192
content = b""

Expand All @@ -54,7 +101,25 @@ def _stream_response(self, response, progress_bar=None):
return content

def make_request(self, method, url, **kwargs):
"""Make an HTTP request with automatic retries and progress indication."""
"""Makes an HTTP request with retry logic and error handling.

This is the core method for all HTTP operations performed by the client. It handles
request creation, response validation, retries, and exception mapping.

Args:
method (str): The HTTP method to use (e.g., 'GET', 'POST').
url (str): The URL to send the request to.
**kwargs: Additional keyword arguments to pass to the `requests.request` function.

Returns:
requests.Response: The HTTP response object.

Raises:
ValueError: If the specified HTTP method is not supported.
HTTPConnectionError: If a connection error occurs after all retries.
ResponseError: If an HTTP error status code is received after all retries.
HTTPClientError: For other request-related errors.
"""
if method.upper() not in self.allowed_methods:
raise ValueError(
f"Unsupported HTTP method. Allowed methods: {', '.join(self.allowed_methods)}"
Expand Down Expand Up @@ -110,29 +175,85 @@ def make_request(self, method, url, **kwargs):
raise HTTPClientError(f"Request failed: {str(e)}") from e

def get(self, url, **kwargs):
"""Make a GET request."""
"""Sends a GET request to the specified URL.

Args:
url (str): The URL to send the GET request to.
**kwargs: Additional keyword arguments for the request.

Returns:
requests.Response: The HTTP response object.
"""
return self.make_request("GET", url, **kwargs)

def post(self, url, **kwargs):
"""Make a POST request."""
"""Sends a POST request to the specified URL.

Args:
url (str): The URL to send the POST request to.
**kwargs: Additional keyword arguments for the request, such as `json` or `data`.

Returns:
requests.Response: The HTTP response object.
"""
return self.make_request("POST", url, **kwargs)

def put(self, url, **kwargs):
"""Make a PUT request."""
"""Sends a PUT request to the specified URL.

Args:
url (str): The URL to send the PUT request to.
**kwargs: Additional keyword arguments for the request, such as `json` or `data`.

Returns:
requests.Response: The HTTP response object.
"""
return self.make_request("PUT", url, **kwargs)

def patch(self, url, **kwargs):
"""Make a PATCH request."""
"""Sends a PATCH request to the specified URL.

Args:
url (str): The URL to send the PATCH request to.
**kwargs: Additional keyword arguments for the request, such as `json` or `data`.

Returns:
requests.Response: The HTTP response object.
"""
return self.make_request("PATCH", url, **kwargs)

def delete(self, url, **kwargs):
"""Make a DELETE request."""
"""Sends a DELETE request to the specified URL.

Args:
url (str): The URL to send the DELETE request to.
**kwargs: Additional keyword arguments for the request.

Returns:
requests.Response: The HTTP response object.
"""
return self.make_request("DELETE", url, **kwargs)

def head(self, url, **kwargs):
"""Make a HEAD request."""
"""Sends a HEAD request to the specified URL.

Args:
url (str): The URL to send the HEAD request to.
**kwargs: Additional keyword arguments for the request.

Returns:
requests.Response: The HTTP response object.
"""
return self.make_request("HEAD", url, **kwargs)

def options(self, url, **kwargs):
"""Make an OPTIONS request."""
"""Sends an OPTIONS request to the specified URL.

Args:
url (str): The URL to send the OPTIONS request to.
**kwargs: Additional keyword arguments for the request.

Returns:
requests.Response: The HTTP response object.
"""
return self.make_request("OPTIONS", url, **kwargs)
Loading