Python SDK
Official Python SDK for Raceway - Race condition detection and distributed tracing for Python applications.
Features
- Automatic context propagation using contextvars
- Flask and FastAPI middleware support
- Manual instrumentation API
- Distributed tracing across service boundaries (W3C Trace Context)
- Race condition and concurrency bug detection
- Automatic batching and background flushing
Installation
pip install racewayQuick Start
Flask Integration
from flask import Flask, request
from raceway import RacewayClient, Config
from raceway.middleware import flask_middleware
app = Flask(__name__)
# Initialize client
raceway = RacewayClient(Config(
endpoint="http://localhost:8080",
service_name="my-service",
instance_id="instance-1"
))
# Add middleware
middleware = flask_middleware(raceway)
@app.before_request
def init_raceway():
middleware.before_request()
@app.after_request
def finish_raceway(response):
return middleware.after_request(response)
@app.route("/transfer", methods=["POST"])
def transfer():
data = request.get_json()
raceway.track_function_call("transfer", data)
# Track state changes
balance = accounts[data["from"]]["balance"]
raceway.track_state_change(
f"{data['from']}.balance",
None,
balance,
"Read"
)
accounts[data["from"]]["balance"] -= data["amount"]
raceway.track_state_change(
f"{data['from']}.balance",
balance,
accounts[data["from"]]["balance"],
"Write"
)
return {"success": True}
if __name__ == "__main__":
app.run(port=3000, threaded=True)FastAPI Integration
from fastapi import FastAPI, Request
from raceway import RacewayClient, Config
from raceway.middleware import FastAPIMiddleware
app = FastAPI()
raceway = RacewayClient(Config(
endpoint="http://localhost:8080",
service_name="my-service"
))
# Add middleware
app.add_middleware(FastAPIMiddleware, client=raceway)
@app.post("/transfer")
async def transfer(request: Request):
data = await request.json()
raceway.track_function_call("transfer", data)
# Your logic here
return {"success": True}Distributed Tracing
The SDK implements W3C Trace Context and Raceway vector clocks for distributed tracing across services.
Propagating Trace Context
Use propagation_headers() when calling downstream services:
import requests
from flask import Flask
from raceway import RacewayClient, Config
app = Flask(__name__)
raceway = RacewayClient(Config(
endpoint="http://localhost:8080",
service_name="api-gateway"
))
@app.route("/checkout", methods=["POST"])
def checkout():
order_id = request.get_json()["orderId"]
# Get propagation headers
headers = raceway.propagation_headers()
# Call downstream services
inventory_result = requests.post(
"http://inventory-service/reserve",
json={"orderId": order_id},
headers=headers
)
payment_result = requests.post(
"http://payment-service/charge",
json={"orderId": order_id},
headers=headers
)
return {"success": True}What Gets Propagated
The middleware automatically:
- Parses incoming
traceparent,tracestate, andraceway-clockheaders - Generates new span IDs for this service
- Returns headers for downstream calls via
propagation_headers()
Headers propagated:
traceparent: W3C Trace Context (trace ID, span ID, trace flags)tracestate: W3C vendor-specific stateraceway-clock: Raceway vector clock for causality tracking
Cross-Service Trace Merging
Events from all services sharing the same trace ID are automatically merged by the Raceway backend. The backend recursively follows distributed edges to construct complete traces across arbitrary service chain lengths.
Authentication
If your Raceway server is configured with API key authentication, provide the key when initializing the SDK:
import os
from raceway import RacewayClient, Config
raceway = RacewayClient(Config(
endpoint="http://localhost:8080",
service_name="my-service",
api_key=os.environ.get("RACEWAY_API_KEY") # Read from environment variable
))Best Practices:
- Store API keys in environment variables, never hardcode them
- Use different keys for different environments (dev, staging, production)
- Rotate keys periodically for security
- The SDK will include the API key in the
Authorizationheader:Bearer <your-api-key>
Without Authentication:
If your Raceway server doesn't require authentication, simply omit the api_key parameter:
raceway = RacewayClient(Config(
endpoint="http://localhost:8080",
service_name="my-service"
))Using the Request Wrapper
For convenience, use the request() wrapper that automatically adds propagation headers:
# Instead of:
headers = raceway.propagation_headers()
requests.post(url, json=data, headers=headers)
# Use:
response = raceway.request("POST", url, json=data)Configuration
from dataclass import dataclass
from typing import Optional
@dataclass
class Config:
endpoint: str = "http://localhost:8080" # Raceway server URL
service_name: str = "unknown-service" # Service name
instance_id: Optional[str] = None # Instance ID (default: hostname-PID)
environment: str = "development" # Environment
batch_size: int = 50 # Event batch size
flush_interval: float = 1.0 # Flush interval in seconds
debug: bool = False # Debug modeAPI Reference
Core Tracking Methods
All tracking methods use the current context (set by middleware).
track_state_change(variable, old_value, new_value, access_type)
Track a variable read or write.
# Track a read
raceway.track_state_change("counter", None, 5, "Read")
# Track a write
raceway.track_state_change("counter", 5, 6, "Write")track_function_call(function_name, args)
Track a function call.
raceway.track_function_call("process_payment", {
"userId": 123,
"amount": 50
})track_http_request(method, url, headers=None, body=None)
Track an HTTP request (automatically called by middleware).
raceway.track_http_request("POST", "/api/users")track_http_response(status, headers=None, body=None, duration_ms=0)
Track an HTTP response.
raceway.track_http_response(200, duration_ms=45)Distributed Tracing Methods
propagation_headers(extra_headers=None)
Generate headers for downstream service calls.
headers = raceway.propagation_headers({"x-custom": "value"})
requests.post("http://downstream/api", headers=headers, json=data)Returns: Dictionary with traceparent, tracestate, and raceway-clock headers.
Raises: RuntimeError if called outside request context.
request(method, url, **kwargs)
Convenience wrapper around requests.request that automatically adds propagation headers.
response = raceway.request("POST", "http://downstream/api", json=data)Lock Tracking Methods
track_lock_acquire(lock_id, lock_type="Mutex")
Manually track lock acquisition.
raceway.track_lock_acquire("account_lock", "Mutex")track_lock_release(lock_id, lock_type="Mutex")
Manually track lock release.
raceway.track_lock_release("account_lock", "Mutex")tracked_lock(lock, lock_id, lock_type="Mutex")
Context manager for automatic lock tracking.
from threading import Lock
from raceway.lock_helpers import tracked_lock
account_lock = Lock()
with tracked_lock(raceway, account_lock, "account_lock", "Mutex"):
# Lock is automatically acquired and tracked
account["balance"] -= amount
# Lock is automatically released and tracked, even if exception occursBenefits:
- Automatic acquire/release tracking
- Exception-safe (lock released even if error occurs)
- Works with
threading.Lock,asyncio.Lock, or any lock withacquire()/release()
Function Decorators
@track_function(client)
Decorator to automatically track function entry/exit and duration.
from raceway.decorators import track_function
@track_function(raceway)
def process_payment(user_id, amount):
# Function call is automatically tracked with args
# Duration is measured automatically
return charge_card(user_id, amount)What it tracks:
- Function entry with arguments
- Function duration (in nanoseconds)
- Return value (optional)
- Exceptions (if any)
Lifecycle Methods
flush()
Manually flush buffered events to the server.
raceway.flush()Use this when you need to ensure events are sent immediately (e.g., before process exit, after critical operations).
shutdown()
Flush remaining events and stop background thread.
raceway.shutdown()Note: shutdown() calls flush() internally before stopping the background thread.
Context Propagation
The SDK uses Python's contextvars for automatic context propagation across:
- HTTP requests
- Async operations (asyncio)
- Thread pools (with proper context copying)
The middleware handles context initialization automatically.
Best Practices
- Use middleware: Set up Flask or FastAPI middleware for automatic trace initialization
- Track shared state: Focus on tracking shared variables accessed by concurrent requests
- Propagate headers: Always use
propagation_headers()orrequest()when calling downstream services - Graceful shutdown: Call
shutdown()before process exit:pythonimport atexit atexit.register(raceway.shutdown) - Use unique instance IDs: Set
instance_idto differentiate service instances
Distributed Example
Complete Flask example with distributed tracing:
from flask import Flask, request
import requests
from raceway import RacewayClient, Config
from raceway.middleware import flask_middleware
app = Flask(__name__)
raceway = RacewayClient(Config(
endpoint="http://localhost:8080",
service_name="api-gateway",
instance_id="gateway-1"
))
middleware = flask_middleware(raceway)
@app.before_request
def init_raceway():
middleware.before_request()
@app.after_request
def finish_raceway(response):
return middleware.after_request(response)
@app.route("/order", methods=["POST"])
def create_order():
data = request.get_json()
order_id = data["orderId"]
raceway.track_function_call("createOrder", {"orderId": order_id})
# Call downstream services with automatic header propagation
inventory_result = raceway.request(
"POST",
"http://inventory-service:3001/reserve",
json={"orderId": order_id}
)
payment_result = raceway.request(
"POST",
"http://payment-service:3002/charge",
json={
"orderId": order_id,
"amount": inventory_result.json()["total"]
}
)
return {"success": True, "orderId": order_id}
if __name__ == "__main__":
import atexit
atexit.register(raceway.shutdown)
app.run(port=3000, threaded=True)All services in the chain will share the same trace ID, and Raceway will merge their events into a single distributed trace.
Event Types
Supported event types:
- StateChange: Variable reads and writes
- FunctionCall: Function entry points
- FunctionReturn: Function exits with return values
- AsyncSpawn: Spawning async tasks
- AsyncAwait: Awaiting async operations
- HTTPRequest: HTTP requests
- HTTPResponse: HTTP responses
- LockAcquire: Acquiring locks
- LockRelease: Releasing locks
- Error: Exceptions and errors
Troubleshooting
Events not appearing
- Check server is running:
curl http://localhost:8080/health - Enable debug mode:
Config(debug=True) - Verify middleware is properly configured
- Call
shutdown()to flush remaining events
Distributed traces not merging
- Ensure all services use
propagation_headers()when calling downstream - Verify
traceparentheader is being sent (enable debug mode) - Check that all services report to the same Raceway server
- Verify instance IDs are unique per service instance
Context not available errors
- Ensure middleware is set up (
@app.before_requestfor Flask) - Verify
propagation_headers()is called within a request context - For background tasks, context does not propagate automatically
Performance
The SDK is designed for minimal overhead:
- Events are batched (default: 50 events per batch)
- Background thread auto-flushes every 1 second
- Non-blocking event transmission
- Automatic retry on network failures
- Thread-safe operations
Development
Building from Source
To build the Python SDK package:
# Install build tools
python3 -m pip install build
# Build the package
python3 -m build
# Output in dist/:
# - raceway-0.1.0.tar.gz
# - raceway-0.1.0-py3-none-any.whlNext Steps
- TypeScript SDK - Node.js integration
- Go SDK - Go integration
- Rust SDK - Rust integration
- Security - Best practices
- Distributed Tracing - Cross-service tracing
