atomhttp 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
atomhttp/__init__.py ADDED
@@ -0,0 +1,76 @@
1
+ """
2
+ AtomHTTP - Professional HTTP Client for Python
3
+ ===============================================
4
+
5
+ A comprehensive, asynchronous HTTP client for Python with features including:
6
+ - Full HTTP method support (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
7
+ - Request and response interceptors
8
+ - Upload and download progress tracking
9
+ - FormData (multipart/form-data) with file uploads
10
+ - Blob, ArrayBuffer, and stream response types
11
+ - Concurrent request helpers (all, spread)
12
+ - Base URL configuration
13
+ - Automatic JSON serialization/deserialization
14
+ - Comprehensive error handling with typed exceptions
15
+ - Type hints for better IDE support
16
+ - Mock adapter for testing
17
+ - Keep-alive connection pooling
18
+ - Proxy support
19
+ - Unix socket path support
20
+ - Configurable timeouts and redirect limits
21
+
22
+ This module exports the main AtomHTTP client class along with all necessary
23
+ types, exceptions, and utilities for making HTTP requests.
24
+
25
+ Example:
26
+ >>> import asyncio
27
+ >>> from atomhttp import AtomHTTP
28
+ >>>
29
+ >>> async def main():
30
+ ... client = AtomHTTP({'baseURL': 'https://api.example.com'})
31
+ ... response = await client.get('/users')
32
+ ... print(response.status, response.data)
33
+ ... await client.close()
34
+ >>>
35
+ >>> asyncio.run(main())
36
+ """
37
+
38
+ from .client import AtomHTTP
39
+ from .errors.http_errors import (
40
+ AtomHTTPError,
41
+ AtomHTTPRequestError,
42
+ AtomHTTPNetworkError,
43
+ AtomHTTPTimeoutError
44
+ )
45
+ from .core.response import Response
46
+ from .core.config import RequestConfig
47
+ from .core.form_data import FormData
48
+ from .core.adapters import HTTPAdapter, MockAdapter
49
+ from .interceptors.manager import InterceptorManager
50
+
51
+ __version__ = "2.0.0"
52
+
53
+ __all__ = [
54
+ # Main client
55
+ "AtomHTTP",
56
+
57
+ # Exception classes
58
+ "AtomHTTPError",
59
+ "AtomHTTPRequestError",
60
+ "AtomHTTPNetworkError",
61
+ "AtomHTTPTimeoutError",
62
+
63
+ # Core types
64
+ "Response",
65
+ "RequestConfig",
66
+
67
+ # Interceptor management
68
+ "InterceptorManager",
69
+
70
+ # Data types
71
+ "FormData",
72
+
73
+ # Adapters
74
+ "HTTPAdapter",
75
+ "MockAdapter",
76
+ ]
@@ -0,0 +1,4 @@
1
+ from .http_adapter import HTTPAdapter
2
+ from .mock_adapter import MockAdapter
3
+
4
+ __all__ = ['HTTPAdapter', 'MockAdapter']
@@ -0,0 +1,102 @@
1
+ """
2
+ HTTP Adapter Module
3
+ -------------------
4
+ This module provides the base HTTP adapter for making asynchronous HTTP requests
5
+ using aiohttp. It handles request execution, timeout management, proxy support,
6
+ and response processing.
7
+
8
+ The HTTPAdapter is the default transport layer for the AtomHTTP client,
9
+ responsible for converting RequestConfig objects into actual HTTP requests
10
+ and wrapping responses into AtomHTTP Response objects.
11
+ """
12
+
13
+ import aiohttp
14
+ from typing import Optional
15
+ from ..core.config import RequestConfig
16
+ from ..core.response import Response
17
+
18
+
19
+ class HTTPAdapter:
20
+ """
21
+ HTTP adapter for making asynchronous HTTP requests using aiohttp.
22
+
23
+ This adapter serves as the transport layer for the AtomHTTP client,
24
+ handling the low-level details of HTTP communication including connection
25
+ management, timeout handling, proxy configuration, and response parsing.
26
+
27
+ Attributes:
28
+ proxy (Optional[str]): Proxy server URL to use for requests.
29
+ Format: 'http://proxy.example.com:8080'
30
+ """
31
+
32
+ def __init__(self, proxy: Optional[str] = None):
33
+ """
34
+ Initialize the HTTP adapter with optional proxy configuration.
35
+
36
+ Args:
37
+ proxy (Optional[str]): Proxy server URL. If provided, all requests
38
+ will be routed through this proxy.
39
+ """
40
+ self.proxy = proxy
41
+
42
+ async def send(self, config: RequestConfig) -> Response:
43
+ """
44
+ Execute an HTTP request based on the provided configuration.
45
+
46
+ This method performs the actual HTTP request using aiohttp,
47
+ handling all aspects of the request lifecycle including connection
48
+ establishment, timeout management, request headers and body,
49
+ redirect following, and response parsing.
50
+
51
+ Args:
52
+ config (RequestConfig): Request configuration object containing all
53
+ request parameters such as URL, method,
54
+ headers, data, timeout, etc.
55
+
56
+ Returns:
57
+ Response: A AtomHTTP Response object containing the parsed response
58
+ data, status code, headers, and original configuration.
59
+
60
+ Raises:
61
+ aiohttp.ClientError: If a network-related error occurs
62
+ asyncio.TimeoutError: If the request exceeds the configured timeout
63
+ json.JSONDecodeError: If response_type='json' and response is invalid JSON
64
+ """
65
+ # Configure timeout with total request duration limit
66
+ timeout = aiohttp.ClientTimeout(total=config.timeout)
67
+
68
+ # Create TCP connector if proxy is configured
69
+ connector = None
70
+ if self.proxy:
71
+ connector = aiohttp.TCPConnector()
72
+
73
+ # Create a new client session for this request
74
+ # Using async context manager ensures proper cleanup
75
+ async with aiohttp.ClientSession(connector=connector) as session:
76
+ # Execute the HTTP request with all configured parameters
77
+ async with session.request(
78
+ method=config.method,
79
+ url=config.url,
80
+ headers=config.headers,
81
+ params=config.params,
82
+ data=config.data,
83
+ timeout=timeout,
84
+ max_redirects=config.maxRedirects
85
+ ) as response:
86
+ # Parse response body based on expected response type
87
+ # JSON responses are automatically deserialized to dict/list
88
+ # Non-JSON responses are returned as plain text
89
+ if config.responseType == 'json':
90
+ data = await response.json()
91
+ else:
92
+ data = await response.text()
93
+
94
+ # Wrap the aiohttp response in AtomHTTP's Response object
95
+ return Response(
96
+ data=data,
97
+ status=response.status,
98
+ status_text=response.reason,
99
+ headers=dict(response.headers),
100
+ config=config,
101
+ request=config
102
+ )
@@ -0,0 +1,130 @@
1
+ """
2
+ Mock Adapter Module
3
+ -------------------
4
+ This module provides a mock HTTP adapter for testing purposes.
5
+
6
+ The MockAdapter allows developers to simulate HTTP responses without making
7
+ actual network requests. This is useful for unit testing, integration testing,
8
+ and development scenarios where external APIs are unavailable or undesirable
9
+ to call directly.
10
+ """
11
+
12
+ from typing import Dict, Any, Optional
13
+ from ..core.config import RequestConfig
14
+ from ..core.response import Response
15
+
16
+
17
+ class MockAdapter:
18
+ """
19
+ Mock adapter for testing HTTP requests without actual network calls.
20
+
21
+ This adapter stores predefined responses for specific request patterns
22
+ and returns them when matching requests are made. It enables isolated
23
+ testing of code that depends on HTTP responses without external dependencies.
24
+
25
+ Features:
26
+ - Register mock responses for specific HTTP methods and URLs
27
+ - Customizable response data, status codes, and status texts
28
+ - Automatic 404 response for unregistered endpoints
29
+ - No actual network connections are established
30
+
31
+ Attributes:
32
+ _responses (Dict[str, Dict]): Internal storage mapping request keys
33
+ to their configured mock responses.
34
+
35
+ Example:
36
+ >>> mock = MockAdapter()
37
+ >>> mock.on('GET', 'https://api.test/users', {'users': []}, 200)
38
+ >>> response = await mock.send(config)
39
+ """
40
+
41
+ def __init__(self):
42
+ """
43
+ Initialize an empty mock adapter with no predefined responses.
44
+
45
+ Use the on() method to register mock responses before sending requests.
46
+ """
47
+ self._responses: Dict[str, Dict] = {}
48
+
49
+ def on(self, method: str, url: str, response_data: Any, status: int = 200):
50
+ """
51
+ Register a mock response for a specific HTTP method and URL.
52
+
53
+ When a request with the matching method and URL is sent through this
54
+ adapter, the registered response will be returned instead of making
55
+ an actual network request.
56
+
57
+ Args:
58
+ method (str): HTTP method (GET, POST, PUT, DELETE, etc.)
59
+ url (str): Full URL or path that this mock should respond to
60
+ response_data (Any): Data to be returned as the response body.
61
+ Can be dict, list, string, or any JSON-serializable type.
62
+ status (int): HTTP status code to return. Defaults to 200.
63
+
64
+ Note:
65
+ If multiple responses are registered for the same method and URL,
66
+ the last registration will overwrite previous ones.
67
+
68
+ Example:
69
+ >>> mock = MockAdapter()
70
+ >>> mock.on('GET', 'https://api.test/users', {'users': []}, 200)
71
+ >>> mock.on('POST', 'https://api.test/users', {'id': 1}, 201)
72
+ >>> mock.on('GET', 'https://api.test/users/1', {'name': 'John'}, 200)
73
+ """
74
+ # Create a unique key combining method and URL for lookup
75
+ key = f"{method}:{url}"
76
+
77
+ # Store the mock response configuration
78
+ self._responses[key] = {
79
+ 'data': response_data,
80
+ 'status': status,
81
+ 'status_text': 'OK' if status == 200 else 'Error'
82
+ }
83
+
84
+ async def send(self, config: RequestConfig) -> Response:
85
+ """
86
+ Send a mock request and return a predefined response.
87
+
88
+ This method looks up the request method and URL in the registered
89
+ responses. If a match is found, the corresponding mock response is
90
+ returned. Otherwise, a 404 Not Found response is returned.
91
+
92
+ Args:
93
+ config (RequestConfig): Request configuration containing method and URL.
94
+ Other config fields (headers, data, etc.) are
95
+ ignored in mock mode.
96
+
97
+ Returns:
98
+ Response: A AtomHTTP Response object containing either the registered
99
+ mock data or a 404 error response.
100
+
101
+ Note:
102
+ No actual HTTP request is made. The response is constructed entirely
103
+ from in-memory mock data, making this adapter suitable for tests
104
+ that require deterministic, repeatable responses.
105
+ """
106
+ # Build lookup key from request configuration
107
+ key = f"{config.method}:{config.url}"
108
+
109
+ # Check if a mock response is registered for this request
110
+ if key in self._responses:
111
+ mock = self._responses[key]
112
+ # Return the registered mock response
113
+ return Response(
114
+ data=mock['data'],
115
+ status=mock['status'],
116
+ status_text=mock['status_text'],
117
+ headers={},
118
+ config=config,
119
+ request=config
120
+ )
121
+ else:
122
+ # Return default 404 response for unregistered endpoints
123
+ return Response(
124
+ data={'error': 'No mock found'},
125
+ status=404,
126
+ status_text='Not Found',
127
+ headers={},
128
+ config=config,
129
+ request=config
130
+ )
@@ -0,0 +1,4 @@
1
+ from .basic_auth import BasicAuth
2
+ from .bearer_auth import BearerAuth
3
+
4
+ __all__ = ['BasicAuth', 'BearerAuth']
@@ -0,0 +1,90 @@
1
+ """
2
+ Basic Authentication Module
3
+ ---------------------------
4
+ This module provides Basic HTTP Authentication support for the AtomHTTP client.
5
+
6
+ Basic Authentication is a simple authentication scheme built into the HTTP
7
+ protocol. The client sends the username and password encoded in base64 within
8
+ the Authorization header.
9
+ """
10
+
11
+ import base64
12
+ from typing import Dict
13
+
14
+
15
+ class BasicAuth:
16
+ """
17
+ Basic Authentication handler for HTTP requests.
18
+
19
+ This class implements the HTTP Basic Authentication scheme as defined in
20
+ RFC 7617. It handles the encoding of username and password credentials
21
+ into the standard Basic authentication header format.
22
+
23
+ How Basic Auth Works:
24
+ 1. Credentials are combined as "username:password"
25
+ 2. The combined string is encoded using Base64
26
+ 3. The encoded string is prefixed with "Basic " and sent in the
27
+ Authorization header
28
+
29
+ Security Considerations:
30
+ - Base64 encoding is NOT encryption; it's easily reversible
31
+ - Basic Auth should ONLY be used over HTTPS connections
32
+ - Credentials are sent with every request
33
+
34
+ Attributes:
35
+ username (str): The authentication username
36
+ password (str): The authentication password
37
+
38
+ Example:
39
+ >>> auth = BasicAuth('admin', 'secret123')
40
+ >>> headers = auth.get_header()
41
+ >>> response = await client.get('/protected', headers=headers)
42
+ """
43
+
44
+ def __init__(self, username: str, password: str):
45
+ """
46
+ Initialize Basic Authentication with username and password.
47
+
48
+ Args:
49
+ username (str): The username for authentication
50
+ password (str): The password for authentication
51
+
52
+ Note:
53
+ Username and password are stored in plain text in memory.
54
+ Always use HTTPS when using Basic Authentication.
55
+ """
56
+ self.username = username
57
+ self.password = password
58
+
59
+ def get_header(self) -> Dict[str, str]:
60
+ """
61
+ Generate the HTTP Authorization header for Basic Authentication.
62
+
63
+ This method encodes the username and password credentials according
64
+ to the Basic Authentication scheme and returns a dictionary ready
65
+ to be merged into request headers.
66
+
67
+ The encoding process:
68
+ 1. Combine username and password with a colon: "user:pass"
69
+ 2. Encode the result to bytes using UTF-8
70
+ 3. Apply Base64 encoding to the bytes
71
+ 4. Decode back to string and prefix with "Basic "
72
+
73
+ Returns:
74
+ Dict[str, str]: A dictionary containing the Authorization header.
75
+ Format: {'Authorization': 'Basic <base64-credentials>'}
76
+
77
+ Example:
78
+ >>> auth = BasicAuth('john', 'secret')
79
+ >>> header = auth.get_header()
80
+ >>> print(header)
81
+ {'Authorization': 'Basic am9objpzZWNyZXQ='}
82
+ """
83
+ # Combine username and password with colon separator as per RFC 7617
84
+ credentials = f"{self.username}:{self.password}"
85
+
86
+ # Encode to bytes, then apply Base64 encoding
87
+ encoded = base64.b64encode(credentials.encode()).decode()
88
+
89
+ # Return the complete Authorization header
90
+ return {'Authorization': f'Basic {encoded}'}
@@ -0,0 +1,83 @@
1
+ """
2
+ Bearer Token Authentication Module
3
+ ----------------------------------
4
+ This module provides Bearer Token authentication support for the AtomHTTP client.
5
+
6
+ Bearer Token authentication (also known as Token-based authentication) is widely
7
+ used in RESTful APIs, particularly with OAuth 2.0. The client sends a token
8
+ in the Authorization header to prove identity.
9
+ """
10
+
11
+ from typing import Dict
12
+
13
+
14
+ class BearerAuth:
15
+ """
16
+ Bearer Token authentication handler for HTTP requests.
17
+
18
+ This class implements the Bearer Token authentication scheme as defined in
19
+ RFC 6750. It adds an Authorization header with the Bearer token to HTTP
20
+ requests, which is the standard way to authenticate with OAuth 2.0 and
21
+ many modern REST APIs.
22
+
23
+ How Bearer Auth Works:
24
+ 1. Client obtains a token (JWT, opaque string, etc.) from an auth server
25
+ 2. Token is sent in the Authorization header as "Bearer <token>"
26
+ 3. Server validates the token and grants access if valid
27
+
28
+ Security Considerations:
29
+ - Tokens should be kept secure and never exposed in logs or URLs
30
+ - Tokens typically have expiration times for security
31
+ - Always use HTTPS to prevent token interception
32
+ - Consider implementing token refresh mechanisms
33
+
34
+ Attributes:
35
+ token (str): The bearer token string used for authentication
36
+
37
+ Example:
38
+ >>> auth = BearerAuth('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...')
39
+ >>> headers = auth.get_header()
40
+ >>> response = await client.get('/api/protected', headers=headers)
41
+ """
42
+
43
+ def __init__(self, token: str):
44
+ """
45
+ Initialize Bearer Token authentication with a token string.
46
+
47
+ Args:
48
+ token (str): The bearer token string. This is typically a JWT
49
+ (JSON Web Token) or an opaque string provided by
50
+ the authentication server.
51
+
52
+ Note:
53
+ The token is stored in plain text in memory. Ensure proper
54
+ handling and cleanup of tokens when no longer needed.
55
+
56
+ Example:
57
+ >>> auth = BearerAuth('your-api-token-here')
58
+ """
59
+ self.token = token
60
+
61
+ def get_header(self) -> Dict[str, str]:
62
+ """
63
+ Generate the HTTP Authorization header for Bearer Token authentication.
64
+
65
+ This method creates the standard Authorization header format required
66
+ by RFC 6750 for Bearer token authentication. The token is prefixed
67
+ with "Bearer " to indicate the authentication scheme.
68
+
69
+ The resulting header format:
70
+ Authorization: Bearer <token>
71
+
72
+ Returns:
73
+ Dict[str, str]: A dictionary containing the Authorization header.
74
+ Format: {'Authorization': 'Bearer <token>'}
75
+
76
+ Example:
77
+ >>> auth = BearerAuth('abc123xyz')
78
+ >>> header = auth.get_header()
79
+ >>> print(header)
80
+ {'Authorization': 'Bearer abc123xyz'}
81
+ """
82
+ # Return the Authorization header with Bearer scheme prefix
83
+ return {'Authorization': f'Bearer {self.token}'}