Skip to content

Overview

Combadge generates a service client implementation from a user service interface declared by a protocol class or an abstract base class.

Checks Coverage Code style Python Version License

Sneak peek

quickstart_httpx.py
from typing import List

from httpx import Client
from pydantic import BaseModel, Field, validate_call
from typing_extensions import Annotated, Protocol

from combadge.core.binder import bind
from combadge.core.markers.method import wrap_with
from combadge.support.http.markers import QueryParam, http_method, path
from combadge.support.httpx.backends.sync import HttpxBackend


# 1️⃣ Declare the response models:
class CurrentCondition(BaseModel):
    humidity: int
    temperature: Annotated[float, Field(alias="temp_C")]


class Weather(BaseModel):
    current: Annotated[List[CurrentCondition], Field(alias="current_condition")]


# 2️⃣ Declare the protocol:
class SupportsWttrIn(Protocol):
    @http_method("GET")
    @path("/{in_}")
    @wrap_with(validate_call)
    def get_weather(
        self,
        *,
        in_: Annotated[str, Field(min_length=1)],
        format_: Annotated[str, Field(min_length=1), QueryParam("format")] = "j1",
    ) -> Weather:
        ...


# 3️⃣ Bind the service:
service = HttpxBackend(Client(base_url="https://wttr.in"))[SupportsWttrIn]

# 🚀 Call the service:
response = service.get_weather(in_="amsterdam")
assert response.current[0].humidity == 71
assert response.current[0].temperature == 8.0
quickstart_zeep.py
from typing import Literal, Protocol, Union

import zeep
from pydantic import BaseModel, Field, RootModel
from pytest import raises
from typing_extensions import Annotated

from combadge.core.interfaces import SupportsService
from combadge.core.response import ErrorResponse, SuccessfulResponse
from combadge.support.http.markers import Payload
from combadge.support.soap.markers import operation_name
from combadge.support.zeep.backends.sync import ZeepBackend


# 1️⃣ Declare the request model:
class NumberToWordsRequest(BaseModel, populate_by_name=True):
    number: Annotated[int, Field(alias="ubiNum")]


# 2️⃣ Declare the response model:
class NumberToWordsResponse(RootModel, SuccessfulResponse):
    root: str


# 3️⃣ Optionally, declare the error response models:
class NumberTooLargeResponse(RootModel, ErrorResponse):
    root: Literal["number too large"]


# 4️⃣ Declare the interface:
class SupportsNumberConversion(SupportsService, Protocol):
    @operation_name("NumberToWords")
    def number_to_words(
        self,
        request: Annotated[NumberToWordsRequest, Payload(by_alias=True)],
    ) -> Union[NumberTooLargeResponse, NumberToWordsResponse]:
        ...


# 5️⃣ Bind the service:
client = zeep.Client(wsdl="tests/integration/wsdl/NumberConversion.wsdl")
service = ZeepBackend(client.service)[SupportsNumberConversion]

# 🚀 Call the service:
response = service.number_to_words(NumberToWordsRequest(number=42))
assert response.unwrap().root == "forty two "

# ☢️ Error classes are automatically derived for error models:
response = service.number_to_words(NumberToWordsRequest(number=-1))
with raises(NumberTooLargeResponse.Error):
    response.raise_for_result()