Basic Usage

This guide covers the fundamental usage patterns of HTTP Client.

Making Requests

HTTP Client supports all standard HTTP methods.

GET Requests

Simple GET request:

from requestforge import create_client

client = create_client('https://api.example.com')
response = client.get('/users')

print(response.status_code)  # 200
print(response.json())       # Parsed JSON response

GET with query parameters:

response = client.get('/users', params={
    'page': 1,
    'limit': 10,
    'active': 'true'
})

# Requests: https://api.example.com/users?page=1&limit=10&active=true

GET with custom headers:

response = client.get('/users', headers={
    'X-API-Version': '2.0',
    'Accept-Language': 'en-US'
})

POST Requests

POST with JSON data:

response = client.post('/users', json_data={
    'name': 'John Doe',
    'email': 'john@example.com',
    'age': 30
})

if response.status_code == 201:
    created_user = response.json()
    print(f"Created user ID: {created_user['id']}")

POST with form data:

response = client.post('/login', data={
    'username': 'john',
    'password': 'secret123'
})

POST with file upload:

response = client.post('/upload', files={
    'file': open('document.pdf', 'rb'),
    'thumbnail': open('thumb.jpg', 'rb')
})

PUT Requests

Update a resource:

response = client.put('/users/123', json_data={
    'name': 'Jane Doe',
    'email': 'jane@example.com'
})

PATCH Requests

Partial update:

response = client.patch('/users/123', json_data={
    'email': 'newemail@example.com'
})

DELETE Requests

Delete a resource:

response = client.delete('/users/123')

if response.status_code == 204:
    print("User deleted successfully")

Working with Responses

Response Object

Every request returns an HttpResponse object:

response = client.get('/users/1')

# Status code
print(response.status_code)  # 200

# Headers
print(response.headers)  # Dict of headers
print(response.headers['Content-Type'])  # 'application/json'

# Body content
print(response.content)  # bytes
print(response.text)     # str

# Metadata
print(response.elapsed_ms)  # Request duration in milliseconds
print(response.url)         # Final URL after redirects
print(response.encoding)    # Response encoding

Status Checks

Convenient properties for checking response status:

response = client.get('/users/1')

# Status categories
if response.is_success:        # 2xx
    print("Success!")

if response.is_client_error:   # 4xx
    print("Client error")

if response.is_server_error:   # 5xx
    print("Server error")

# Specific status codes
if response.status_code == 200:
    print("OK")
elif response.status_code == 404:
    print("Not found")

Parsing JSON

response = client.get('/users/1')

# Parse JSON (raises ResponseParseException on invalid JSON)
user = response.json()
print(user['name'])

# Safe JSON parsing (returns None on error)
data = response.json_or_none()
if data:
    print(data['name'])
else:
    print("Invalid JSON response")

Request Configuration

Per-Request Configuration

Override client defaults for specific requests:

from requestforge import HttpClient, HttpClientConfigBuilder

# Client with default timeout of 30s
config = HttpClientConfigBuilder().with_timeout(30.0).build()
client = HttpClient(config)

# Override timeout for this request only
response = client.get('/slow-endpoint', timeout=60.0)

# Override headers
response = client.get('/users', headers={
    'X-Custom-Header': 'special-value'
})

Base URL Handling

Absolute vs. Relative URLs:

# With base URL
client = create_client('https://api.example.com')

# Relative URL (uses base URL)
response = client.get('/users')
# Requests: https://api.example.com/users

# Absolute URL (ignores base URL)
response = client.get('https://other-api.com/data')
# Requests: https://other-api.com/data

URL Construction:

# Leading/trailing slashes are handled automatically
client = create_client('https://api.example.com/')

# All of these work the same:
client.get('/users')
client.get('users')
client.get('/users/')
# All request: https://api.example.com/users

Using HttpRequest Objects

For more control, create HttpRequest objects explicitly:

from requestforge import HttpRequest, HttpMethod

request = HttpRequest(
    method=HttpMethod.POST,
    url='/users',
    headers={'X-Custom': 'value'},
    params={'notify': 'true'},
    json_data={'name': 'John'},
    timeout=60.0
)

response = client.request(request)

Modifying Requests:

# Create base request
request = HttpRequest(method=HttpMethod.GET, url='/users')

# Add headers (returns new request object)
authenticated_request = request.with_headers({
    'Authorization': 'Bearer token123'
})

# Original request is unchanged (immutable)
print(request.headers)              # None
print(authenticated_request.headers)  # {'Authorization': 'Bearer token123'}

Converting to cURL:

request = HttpRequest(
    method=HttpMethod.POST,
    url='https://api.example.com/users',
    headers={'Authorization': 'Bearer token'},
    json_data={'name': 'John'}
)

# Generate cURL command for debugging
curl_command = request.to_curl()
print(curl_command)
# Output: curl -X 'POST' -H 'Authorization: Bearer token' --data '{"name": "John"}' 'https://api.example.com/users'

Resource Management

Using Context Managers

Always close clients to release resources:

# Automatic cleanup with context manager (recommended)
with create_client('https://api.example.com') as client:
    response = client.get('/users')
    users = response.json()
# Client automatically closed here

Manual Cleanup:

client = create_client('https://api.example.com')
try:
    response = client.get('/users')
finally:
    client.close()

Session Lifecycle

Understanding session lifecycle:

from requestforge import HttpClient, HttpClientConfigBuilder

config = HttpClientConfigBuilder().with_base_url('https://api.example.com').build()
client = HttpClient(config)

# Sessions are created lazily per thread
response1 = client.get('/users')  # Creates session
response2 = client.get('/posts')  # Reuses same session

# Connection pooling is automatic
# Multiple requests reuse connections

# Close to release all connections
client.close()

Best Practices

  1. Use Context Managers

    # Good ✅
    with create_client('https://api.example.com') as client:
        response = client.get('/users')
    
    # Avoid ❌
    client = create_client('https://api.example.com')
    response = client.get('/users')
    # Forgot to close!
    
  2. Reuse Client Instances

    # Good ✅ - Connection pooling benefits
    client = create_client('https://api.example.com')
    for user_id in range(1, 100):
        response = client.get(f'/users/{user_id}')
    client.close()
    
    # Avoid ❌ - Creates new connection for each request
    for user_id in range(1, 100):
        client = create_client('https://api.example.com')
        response = client.get(f'/users/{user_id}')
        client.close()
    
  3. Check Response Status

    # Good ✅
    response = client.get('/users/1')
    if response.is_success:
        user = response.json()
    else:
        print(f"Error: {response.status_code}")
    
    # Avoid ❌ - Assumes success
    response = client.get('/users/1')
    user = response.json()  # May fail if status is 404/500
    
  4. Handle Exceptions

    # Good ✅
    from requestforge import TimeoutException, HttpClientException
    
    try:
        response = client.get('/users/1')
    except TimeoutException:
        print("Request timed out")
    except HttpClientException as e:
        print(f"Request failed: {e}")
    
  5. Use Appropriate Timeouts

    # Good ✅ - Different timeouts for different endpoints
    fast_response = client.get('/health', timeout=5.0)
    slow_response = client.get('/reports/generate', timeout=120.0)
    

Common Patterns

Pagination

def fetch_all_users(client):
    page = 1
    all_users = []

    while True:
        response = client.get('/users', params={'page': page, 'limit': 100})

        if not response.is_success:
            break

        users = response.json()
        if not users:
            break

        all_users.extend(users)
        page += 1

    return all_users

Batch Operations

def create_users_batch(client, users_data):
    results = []

    for user_data in users_data:
        response = client.post('/users', json_data=user_data)
        results.append({
            'data': user_data,
            'success': response.is_success,
            'response': response.json() if response.is_success else None
        })

    return results

Download Files

def download_file(client, url, output_path):
    response = client.get(url)

    if response.is_success:
        with open(output_path, 'wb') as f:
            f.write(response.content)
        return True
    return False

Next Steps