diff --git a/PyFetch/__init__.py b/PyFetch/__init__.py index a32c66c..28f189b 100644 --- a/PyFetch/__init__.py +++ b/PyFetch/__init__.py @@ -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 diff --git a/PyFetch/__main__.py b/PyFetch/__main__.py index b68fccf..a2ae84d 100644 --- a/PyFetch/__main__.py +++ b/PyFetch/__main__.py @@ -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 diff --git a/PyFetch/cli.py b/PyFetch/cli.py index b3906ef..a16d634 100644 --- a/PyFetch/cli.py +++ b/PyFetch/cli.py @@ -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 @@ -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: @@ -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", @@ -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|"): @@ -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() diff --git a/PyFetch/exceptions.py b/PyFetch/exceptions.py index fbaf51a..da2bc3e 100644 --- a/PyFetch/exceptions.py +++ b/PyFetch/exceptions.py @@ -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. + """ diff --git a/PyFetch/http_client.py b/PyFetch/http_client.py index 100e10a..70ba8ba 100644 --- a/PyFetch/http_client.py +++ b/PyFetch/http_client.py @@ -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 @@ -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 @@ -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, @@ -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"" @@ -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)}" @@ -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) diff --git a/README.md b/README.md index c2c6f12..ccb5359 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ -# PyFetch: A Lightweight HTTP CLI +# PyFetch: A Lightweight HTTP CLI and Library -A lightweight command-line interface for making HTTP requests. Built with Python, -this tool provides an easy way to make GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS requests with support for JSON data, customizable timeouts, automatic retries on failures, and a verbose mode for detailed logging. +A lightweight command-line interface and Python library for making HTTP requests. +Built with Python, this tool provides an easy way to make GET, POST, PUT, PATCH, +DELETE, HEAD, and OPTIONS requests with support for JSON data, customizable timeouts, +automatic retries on failures, and a verbose mode for detailed logging. ## Features - Simple command-line interface +- Can be used as a Python library - Case-insensitive commands (GET/get, POST/post etc.) - Support for GET, PUT, POST, PATCH, DELETE, HEAD, and OPTIONS requests - JSON data handling for POST requests @@ -54,7 +57,51 @@ source venv/bin/activate pip install -e . ``` -## Usage +## Library Usage + +You can also use PyFetch as a library in your Python projects. + +### Creating a Client + +First, import and create an instance of the `HTTPClient`: + +```python +from PyFetch.http_client import HTTPClient + +client = HTTPClient(timeout=30, retries=3, verbose=False) +``` + +### Making Requests + +You can then use the client to make HTTP requests: + +```python +# Make a GET request +response = client.get("https://httpbin.org/get") +print(response.json()) + +# Make a POST request with JSON data +data = {"key": "value"} +response = client.post("https://httpbin.org/post", json=data) +print(response.json()) +``` + +### Error Handling + +The client raises specific exceptions for different types of errors: + +```python +from PyFetch.exceptions import HTTPClientError, HTTPConnectionError + +try: + response = client.get("https://a-non-existent-domain.com") +except HTTPConnectionError as e: + print(f"Connection failed: {e}") +except HTTPClientError as e: + print(f"An error occurred: {e}") +``` + +## CLI Usage ### Example Commands