Skip to main content

WebSDK is a library for quickly and easily creating SDKs for integration with third-party APIs.

Project description

Web SDK

WebSDK is a library for quickly and easily creating SDKs for integration with third-party APIs.

CI Coverage pypi downloads versions Web SDK alfa

Installation

REST installation

Install using pip install web-sdk[rest] or uv add web-sdk[rest]

If you want to use web_sdk.sdks.rest.XmlResponse

Install using pip install web-sdk[rest,xml] or uv add web-sdk[rest,xml]

SOAP installation

Install using pip install web-sdk[soap] or uv add web-sdk[soap]

Minimal example

Let's imagine that we have the following data schemas

# docs/examples/home/minimal/dtos.py

from pydantic import BaseModel


# Response short data structure
class ShortData(BaseModel):
    pk: int = 1
    q: bool | None = None


# Response data structure
class Data(ShortData):
    nested: ShortData

To make the example simpler, we'll write the server part using fastapi.

# docs/examples/home/minimal/server.py

from fastapi import FastAPI

# Create FastAPI app
app = FastAPI(root_path="/api/v1")


[@app](https://github.com/app).get("/data/{pk}/info")
async def get_data_info(pk: int, q: bool | None = None) -> Data:
    """Return full data info."""
    return Data(pk=pk, q=q, nested=ShortData(pk=pk, q=q))


# route with short data return
[@app](https://github.com/app).get("/data/{pk}/info/short")
async def get_short_data_info(pk: int, q: bool | None = None) -> ShortData:
    """Return short data info."""
    return ShortData(pk=pk, q=q)

To link with the routes declare in server code, you only need the following client code

# docs/examples/home/minimal/client.py

from web_sdk.core.fields import APath
from web_sdk.enums import HTTPMethod
from web_sdk.sdks.rest import Client, ClientService, JsonResponse, Method, Service, Settings, get_res


# declare service for group of methods
class FooService(Service, path="data/{pk}/info"):
    get_data = Method[JsonResponse[Data]](method=HTTPMethod.GET)
    # declare method with return type and path (default method is GET)
    get_short_data = Method[JsonResponse[ShortData]](path="short")


# declare client service for group of real methods in client class
class FooClientService(ClientService):
    # declare method with certain signature pk is path part,
    # q is param (param type is default for GET method)
    [@FooService](https://github.com/FooService).get_data
    def get_data(self, pk: APath[int], q: bool | None = None) -> JsonResponse[Data]: ...

    get_short_data = FooService.get_short_data.from_method(get_data)


# declare client class
class FooClient(Client):
    # set client services as annotation
    service: FooClientService

All you have to do next is init the client and call methods you need.

# docs/examples/home/minimal/usage.py

# init client settings
settings = Settings(protocol="http", host="127.0.0.1", api_path="api/v1", port=8000)

# init client instance
client = FooClient(settings=settings)

# make get_data request
data_response = client.service.get_data(pk=1, q=True)
# extract data from response
data = get_res(data_response)
# Data(pk=1, q=True, nested=ShortData(pk=1, q=True))

# make get_short_data request
short_data_response = client.service.get_short_data(pk=1, q=True)
# extract data from response
short_data = get_res(short_data_response)
# ShortData(pk=1, q=True)

Features

  • Annotation like request parts mapper
  • All requests methods support
  • Pydantic validation output and input data
  • File sending
  • Custom extra and context data during request
  • Errors logging
  • Custom client settings
  • Custom and token auth
  • Test mode settings support
  • Requests REST support
    • Service declaration base request configuring
    • Method declaration base request configuring
    • Method call base request configuring
    • Request call base request configuring
    • Path part mapping without field annotation
  • Requests SOAP support
    • Custom transport for file sending
    • Service declaration base request configuring
    • Method declaration base request configuring
    • Method call base request configuring
    • Request call base request configuring
  • HTTPX REST support
  • HTTPX SOAP support
  • MkDocs documentation

Supported backends

Sync backends

Requests REST

Client and utils for declare the sync SDK based on requests.

Declare custom or use default settings

# docs/examples/home/sync/rest/settings.py

from pydantic_settings import SettingsConfigDict

from web_sdk.sdks.rest import Settings


class FooSettings(Settings):
    protocol: str = "https"
    """API protocol"""
    host: str = "example.com"
    """API host"""
    port: int | None = 8000
    """API port"""
    api_path: str = "/api/v1"
    """API path"""

    model_config = SettingsConfigDict(
        env_prefix="FOO_CLIENT_",
    )

Create responses schemas

# docs/examples/home/sync/rest/schemas.py

from datetime import datetime
from decimal import Decimal

from pydantic import BaseModel

from web_sdk.sdks.rest import JsonResponse


class PaymentShortInfoDTO(BaseModel):
    id: str
    is_success: bool


class PaymentInfoDTO(PaymentShortInfoDTO):
    order_id: str
    payment_date: datetime
    payment_amount: Decimal


class OrderShortInfoDTO(BaseModel):
    id: str
    reference: str


class OrderInfoDTO(OrderShortInfoDTO):
    class _TargetDTO(BaseModel):
        id: str
        type: str
        price: Decimal

    # it is working with nested model
    target: _TargetDTO


GetPaymentResponse = JsonResponse[PaymentInfoDTO]
# it is working with list data
GetPaymentsResponse = JsonResponse[list[PaymentInfoDTO]]
MakePaymentResponse = JsonResponse[PaymentShortInfoDTO]
GetOrderResponse = JsonResponse[OrderInfoDTO]

Declare services with methods for using in client

# docs/examples/home/sync/rest/methods.py

from web_sdk.enums import HTTPMethod
from web_sdk.sdks.rest import Method, Service

from . import schemas


class PaymentsService(
    Service,
    path="payments",
    description="Payments service",
):
    get = Method[schemas.GetPaymentResponse](
        path="{payment_id}",
        description="Get payment by id",
    )  # full path is "{settings.url}/payments/{payment_id}"

    make = Method[schemas.MakePaymentResponse](
        method=HTTPMethod.POST,
        path="make/{order_id}",
        description="Make payment for order",
    )  # full path is "{settings.url}/payments/make/{order_id}"


class OrdersService(
    Service,
    path="orders/{order_id}",
    description="Get order information by id",
):
    get = Method[schemas.GetOrderResponse](
        description="Get order information",
    )  # full path is "{settings.url}/orders/{order_id}"

    payments = Method[schemas.GetPaymentsResponse](
        path="payments",
        description="Get order payments",
    )  # full path is "{settings.url}/orders/{order_id}/payments"

Declare client and client services

# docs/examples/home/sync/rest/client.py

from decimal import Decimal
from typing import Annotated

from typing_extensions import Unpack

from web_sdk.core.backends.requests.rest.kwargs import RestRequestsKwargsWithSettings
from web_sdk.core.fields import APath, ASetting, Body, Param, Path
from web_sdk.sdks.rest import Client, ClientService

from . import schemas
from .methods import OrdersService, PaymentsService
from .settings import FooSettings


class BaseFooClient(Client, base=True):
    """Here you can customize the client logic to suit your needs."""

    __default_settings_class__ = FooSettings


class FooClientService(ClientService[BaseFooClient], client=BaseFooClient):
    """This class need for isolate subclasses registering in user class."""


class PaymentsClientService(FooClientService):
    [@PaymentsService](https://github.com/PaymentsService).get
    def get(
        self,
        # ALike aliases (shortcut for Annotated[T, Field])
        payment_id: APath[int],
        # Unpack with TypedDict
        **kwargs: Unpack[RestRequestsKwargsWithSettings],
    ) -> schemas.GetPaymentResponse: ...

    [@PaymentsService](https://github.com/PaymentsService).make(timeout=5)
    def make(
        self,
        # Annotated[T, Field] like annotations
        order_id: Annotated[str, Path],
        # Other variant with Field call
        amount: Annotated[Decimal, Body(ge=Decimal("0"))],
        # Field as default value. You can also use a field without
        # specifying a default value, then the field will be
        # required (arg: bool = Param) or (arg: bool = Param()).
        immediately: bool | None = Param(None),  # type: ignore
        # Using settings to change Client.make_request behavior
        raise_exception: ASetting[bool | None] = None,
    ) -> schemas.MakePaymentResponse: ...


class OrdersClientService(FooClientService):
    [@OrdersService](https://github.com/OrdersService).get
    def get(self, order_id: APath[int]) -> schemas.GetOrderResponse: ...

    [@OrdersService](https://github.com/OrdersService).get
    def payments(
        self,
        order_id: APath[int],
        # For GET, DELETE, OPTION, HEAD methods default field is Param,
        # for POST, PATCH, PUT methods default field id Body
        success_only: bool = False,
    ) -> schemas.GetPaymentsResponse: ...


class FooClient(BaseFooClient):
    payments: PaymentsClientService
    orders: PaymentsClientService

Usage example

# docs/examples/home/sync/rest/usage.py

from web_sdk.core.utils import make_client_factory
from web_sdk.sdks.rest import get_res

from .client import FooClient
from .settings import FooSettings

# You can just create client instance
# client = FooClient()

# But I recommend creating a client factory to cache instances
# based on the settings and logger hashes to avoid creating duplicate instances
client_factory = make_client_factory(FooClient, FooSettings)


# Create client
client = client_factory()

# Method response or error response if you use raise_exceptions=False
# or None if you use skip_for_tests
response = client.payments.get(
    payment_id=1,
    timeout=1,
    raise_exceptions=False,
)

result_or_none = get_res(response, required=False)
result = get_res(response)

Requests SOAP

Client and utils for declare the sync SDK based on requests, and zeep.

Declare custom or use default settings

# docs/examples/home/sync/soap/settings.py

from pydantic_settings import SettingsConfigDict

from web_sdk.sdks.soap import Settings


class FooSettings(Settings):
    protocol: str = "https"
    """API protocol"""
    host: str = "example.com"
    """API host"""
    port: int | None = 8000
    """API port"""
    api_path: str = "/api/v1"
    """API path"""
    service_name: str | None = "service"
    """The name of wsdl service."""
    port_name: str | None = "port"
    """The name of wsdl port."""

    model_config = SettingsConfigDict(
        env_prefix="FOO_CLIENT_",
    )

Create responses schemas

# docs/examples/home/sync/soap/schemas.py

from datetime import datetime
from decimal import Decimal
from typing import Generic

from pydantic import BaseModel

from web_sdk.contrib.pydantic.models import PydanticModel
from web_sdk.core.bases.soap import SoapFile
from web_sdk.sdks.soap import SoapResponse
from web_sdk.types import TData


class PaymentShortInfoDTO(PydanticModel):
    id: str
    is_success: bool


class PaymentInfoDTO(PydanticModel):
    order_id: str
    payment_date: datetime
    payment_amount: Decimal
    document: SoapFile


class PaymentsInfosDTO(PydanticModel):
    payments: list[PaymentInfoDTO]


class OrderShortInfoDTO(PydanticModel):
    id: str
    reference: str


class OrderInfoDTO(PydanticModel):
    class _TargetDTO(BaseModel):
        id: str
        type: str
        price: Decimal

    # it is working with nested model
    target: _TargetDTO


class FooResponse(SoapResponse, Generic[TData]):
    success: bool
    data: TData


GetPaymentResponse = FooResponse[PaymentInfoDTO]
GetPaymentsResponse = FooResponse[list[PaymentInfoDTO]]
MakePaymentResponse = FooResponse[PaymentShortInfoDTO]
GetOrderResponse = FooResponse[OrderInfoDTO]

Declare services with methods for using in client

# docs/examples/home/sync/soap/methods.py

from web_sdk.sdks.soap import Method, Service

from . import schemas


class PaymentsService(
    Service,
    path="Payments",
    description="Payments service",
):
    get = Method[schemas.GetPaymentResponse](
        path="getPayment",
        description="Get payment by id",
    )  # method path is "Payments.getPayment"

    make = Method[schemas.MakePaymentResponse](
        path="makePayment",
        description="Make payment for order",
    )  # method path is "Payments.makePayment"


class OrdersService(
    Service,
    description="Get order information by id",
):
    get = Method[schemas.GetOrderResponse](
        description="Get order information",
    )  # method path is "get"

    payments = Method[schemas.GetPaymentsResponse](
        path="paymentsWithPath",
        description="Get order payments",
    )  # method path is "paymentsWithPath"

Declare client and client services

# docs/examples/home/sync/soap/client.py

from decimal import Decimal
from typing import Annotated

from typing_extensions import Unpack

from web_sdk.core.backends.requests.soap.kwargs import SoapRequestsKwargsWithSettings
from web_sdk.core.fields import AFile, ASetting, Body
from web_sdk.sdks.soap import Client, ClientService, SoapFile

from . import schemas
from .methods import OrdersService, PaymentsService
from .settings import FooSettings


class BaseFooClient(Client, base=True):
    """Here you can customize the client logic to suit your needs."""

    __default_settings_class__ = FooSettings


class FooClientService(ClientService[BaseFooClient], client=BaseFooClient):
    """This class need for isolate subclasses registering in user class."""


# For soap client Body is base field type
class PaymentsClientService(FooClientService):
    [@PaymentsService](https://github.com/PaymentsService).get
    def get(
        self,
        # ALike aliases (shortcut for Annotated[T, Field])
        payment_id: int,
        # Unpack with TypedDict
        **kwargs: Unpack[SoapRequestsKwargsWithSettings],
    ) -> schemas.GetPaymentResponse: ...

    [@PaymentsService](https://github.com/PaymentsService).make
    def make(
        self,
        # Annotated[T, Field] like annotations
        order_id: Annotated[str, Body],
        # Other variant with Field call
        amount: Annotated[Decimal, Body(ge=Decimal("0"))],
        # Field as default value. You can also use a field without
        # specifying a default value, then the field will be
        # required (arg: bool = Body) or (arg: bool = Body()).
        immediately: bool | None = Body(None),  # type: ignore
        # Send single file with request
        payment_file: AFile[SoapFile | None] = None,
        # Send multiple files with request
        other_files: AFile[list[SoapFile] | None] = None,
        # Using settings to change Client.make_request behavior
        raise_exception: ASetting[bool | None] = None,
    ) -> schemas.MakePaymentResponse: ...


class OrdersClientService(FooClientService):
    [@OrdersService](https://github.com/OrdersService).get
    def get(self, order_id: int) -> schemas.GetOrderResponse: ...

    [@OrdersService](https://github.com/OrdersService).get
    def payments(
        self,
        order_id: int,
        success_only: bool = False,
    ) -> schemas.GetPaymentsResponse: ...


class FooClient(BaseFooClient):
    payments: PaymentsClientService
    orders: PaymentsClientService

Usage example

# docs/examples/home/sync/soap/usage.py

from decimal import Decimal

from web_sdk.core.bases.soap import SoapFile
from web_sdk.core.utils import make_client_factory

from .client import FooClient
from .settings import FooSettings

# You can just create client instance
# client = FooClient()

# But I recommend creating a client factory to cache instances
# based on the settings and logger hashes to avoid creating duplicate instances
client_factory = make_client_factory(FooClient, FooSettings)


# Create client
client = client_factory()

# Method response or error response if you use raise_exceptions=False
# or None if you use skip_for_tests
response = client.payments.make(
    order_id="123",
    amount=Decimal("100"),
    payment_file=SoapFile(
        filename="payment.txt",
        content_type="text/plain",
        content=b"content",
    ),
    raise_exception=False,
)

Async backends

Planned...

Httpx REST

Planned...

Httpx SOAP

Planned...

Changelog

v0.1.3

  • Add docs

v0.1.2

  • Add minimal docs

v0.1.1

  • Add project structure
  • Add core logic for requests soap and rest

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

web_sdk-0.1.3.tar.gz (66.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

web_sdk-0.1.3-py3-none-any.whl (66.5 kB view details)

Uploaded Python 3

File details

Details for the file web_sdk-0.1.3.tar.gz.

File metadata

  • Download URL: web_sdk-0.1.3.tar.gz
  • Upload date:
  • Size: 66.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for web_sdk-0.1.3.tar.gz
Algorithm Hash digest
SHA256 17c8c3e23f7a276a2e13399fb83c6e28932ccb5d2f652773b2a44da3b87ac77f
MD5 0f2c37ebbf23503e4abcdff0d168265f
BLAKE2b-256 dcc2b4dbf461188feb89210367dd75e7ca76c0e20a89c7ab02e38b0dc729ed95

See more details on using hashes here.

Provenance

The following attestation bundles were made for web_sdk-0.1.3.tar.gz:

Publisher: ci.yml on extralait-web/web-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file web_sdk-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: web_sdk-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 66.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for web_sdk-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 bc1a3334cd432c1e962c38aa0f068def4cc3e1cfe200e69c07e556c1e748bd62
MD5 db12912bed98f4fe13630703aa51d193
BLAKE2b-256 383e5eca806bdba1cbe321858be244baa43839f259ec2684d4a40d4369124691

See more details on using hashes here.

Provenance

The following attestation bundles were made for web_sdk-0.1.3-py3-none-any.whl:

Publisher: ci.yml on extralait-web/web-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page