bravado

repository·master·Indexed 20 days ago

https://github.com/yelp/bravado

A Python library for interacting with REST APIs defined by the OpenAPI Specification (Swagger) version 2.0. Bravado acts as a dynamic client that replaces manual code generation by allowing developers to call API endpoints as native Python methods. It supports both synchronous (via requests) and asynchronous (via fido) HTTP clients, provides strict request and response validation against the schema, and maps Swagger models directly to Python types.

Tokens
7.2K
Snippets
27
Records
33
Agent score
70%

What's inside bravado

  1. What is Bravado?

    master

    Bravado is a Python client library designed for interacting with Swagger 2.0 services. Unlike traditional tools that require a code generation step, Bravado dynamically generates a client at runtime based on your Swagger schema.

    Key capabilities include:

    • Dynamic Client Generation: No pre-generation step required.
    • HTTP Clients: Supports both Synchronous (via requests) and Asynchronous (via fido) clients.
    • Validation: Performs strict validation to ensure Swagger Schema v2.0 compatibility and validates both HTTP requests and responses against the schema.
    • Type Mapping: Maps Swagger models directly to Python types, allowing you to work with objects instead of raw JSON.
    • Schema Ingestion: Can ingest Swagger schemas via local file paths or HTTP URLs.
    • Developer Experience: Provides REPL-friendly navigation with docstrings for Resources, Operations, and Models.
  2. Make API calls using resources and operations

    master

    After initializing a SwaggerClient via from_url or from_spec, you interact with the API using a hierarchical method structure: client.resource.operation(operation_params).

    • Resources: Generated from the tags in your Swagger spec. If an operation has no tags, the first element of its path is used (e.g., /pet/find becomes the pet resource).
    • Operations: Named after the operationId in the spec. If no operationId is provided, Bravado generates one.
    • Parameters: Pass keyword arguments to the operation method. The keys must match the (sanitized) parameter names from the spec, and values should use corresponding Python types (e.g., use bool for boolean).

    Use dir(client) to discover available resources and dir(client.resource) to discover available operations.

    # Example pattern
    client = SwaggerClient.from_url('http://api.example.com/swagger.json')
    # Accessing a resource and calling an operation
    response_future = client.pet.find(petId=123)
  3. How Bravado sanitizes names

    master

    Because Swagger specs may contain characters invalid in Python identifiers (like spaces or hyphens), Bravado sanitizes resource, operation, and parameter names using the following rules:

    1. Any character that is not a letter or number is converted to an underscore (_).
    2. Multiple consecutive underscores are collapsed into one.
    3. Leading and trailing underscores are removed.
    4. Leading numbers are removed.
  4. Inspect operation and model docstrings

    master

    Bravado provides docstrings for operations and models to help identify parameter and response types.

    Note: The standard Python help() function may not work as expected for operation docstrings. Instead, use the ? method in your interactive console to view the detailed Docstring section, which includes parameter types, return types, and possible HTTP status codes.

    To inspect a model, use the ? method on the object returned by get_model().

    >> petstore.pet.getPetById?
    
    >> pet_model = petstore.get_model('Pet')
    >> pet_model?
  5. Customize the HTTP client

    master

    By default, bravado uses the requests library. You can provide your own HTTP client instance by passing it to the http_client argument in SwaggerClient.from_url or SwaggerClient.from_spec.

    Using RequestsClient

    If using the default RequestsClient, you can customize SSL/TLS behavior using:

    • ssl_verify: Identical to the verify option in requests.
    • ssl_cert: Identical to the cert option in requests.

    Note that bravado also honors the REQUESTS_CA_BUNDLE environment variable.

    Using FidoClient

    Bravado ships with bravado.fido_client.FidoClient. Note that the FidoClient currently does not support customizing SSL/TLS behavior or following redirects.

    Customizing Adapters

    You can specify custom future and response adapter classes using the future_adapter_class and response_adapter_class arguments.

  6. Mocking Bravado clients for unit testing

    master

    To write robust unit tests without making actual network requests, you should mock the SwaggerClient. This is typically done by patching SwaggerClient.from_url to return a mock.Mock object. This allows you to control the behavior of the client and its nested API methods in your tests.

    import mock
    import pytest
    from bravado.client import SwaggerClient
    
    @pytest.fixture
    def mock_client():
        mock_client = mock.Mock(name='mock SwaggerClient')
        with mock.patch.object(SwaggerClient, 'from_url', return_value=mock_client):
            yield mock_client
  7. Handle HttpFuture and BravadoResponse

    master

    Calling an operation method returns an HttpFuture. To retrieve the actual result, you must call the .response method on the future, which is a blocking call.

    Successful Responses (HTTP 100-299)

    If the request succeeds, .response returns a BravadoResponse instance. You can access the parsed Swagger data via the .result attribute.

    Error Responses (HTTP 300+)

    By default, if the server returns an HTTP status code of 300 or higher, calling .response will raise a subclass of HTTPError. You can catch this exception to access:

    • .swagger_result: The parsed Swagger error body.
    • .response: The raw HTTP response object.
    future = client.pet.find(petId=123)
    
    try:
        # .response() blocks until the request completes
        bravado_response = future.response
        print(bravado_response.result)
    except HTTPError as e:
        # Handle 300+ status codes
        print(f"Error status: {e.response.status_code}")
        print(f"Error body: {e.swagger_result}")
  8. Initialize a SwaggerClient from a URL

    master

    Use SwaggerClient.from_url() to create a client by providing the URL to an OpenAPI Specification (Swagger) JSON file. Once initialized, you can access API resources and methods directly as attributes on the client object. To access the actual data returned by the API, call .response().result on the returned object.

    from bravado.client import SwaggerClient
    client = SwaggerClient.from_url('http://petstore.swagger.io/v2/swagger.json')
    pet = client.pet.getPetById(petId=1).response().result
  9. Make your first GET request with SwaggerClient

    master

    Use SwaggerClient.from_url to initialize a client from a Swagger/OpenAPI JSON definition. You can then call API methods using dot notation. Bravado dynamically creates model instances (e.g., bravado.model.Pet) for the response results, allowing you to access attributes directly.

    from bravado.client import SwaggerClient
    
    client = SwaggerClient.from_url("http://petstore.swagger.io/v2/swagger.json")
    pet = client.pet.getPetById(petId=42).response().result
    # Access attributes directly on the returned model
    print(pet.category.id)
  10. Load swagger.json from a file path

    master

    You can initialize a SwaggerClient using a local file path in two ways:

    1. Using a file:// URL with from_url.
    2. Using the load_file helper with from_spec.
    # Option 1: Using from_url
    client = SwaggerClient.from_url('file:///some/path/swagger.json')
    
    # Option 2: Using load_file helper
    from bravado.swagger_model import load_file
    client = SwaggerClient.from_spec(load_file('/path/to/swagger.json'))
  11. Use fallback results for error handling

    master

    Instead of catching exceptions for timeouts or server errors, you can use the fallback_result argument in the .response() method of an HttpFuture.

    Static Fallback

    Pass a direct value (like an empty list or a model instance) to return when an error occurs.

    Dynamic Fallback

    Pass a callable that accepts the exception as its only argument. This allows you to return different results based on the error type (e.g., returning cached data on BravadoTimeoutError vs. an empty list on a 5XX error).

    Customizing Error Types

    By default, fallbacks trigger on timeouts or 5XX errors. To change this, specify the exceptions_to_catch argument in .response().

    Testing Fallbacks

    To force a fallback for testing, set force_fallback_result=True in the request configuration. This will pass a ForcedFallbackResultError to your fallback callable.

    # Static fallback example
    response = petstore.pet.findPetsByStatus(status=['available']).response(
        timeout=0.5,
        fallback_result=[],
    )
    
    # Dynamic fallback example
    def pet_status_fallback(exc):
        if isinstance(exc, BravadoTimeoutError):
            return pet_status_cache
        return []
    
    response = petstore.pet.findPetsByStatus(status=['available']).response(
        timeout=0.5,
        fallback_result=pet_status_fallback,
    )
    
    # Check if a fallback was used
    if response.metadata.is_fallback_result:
        print("Using fallback data")