Metadata-Version: 2.4
Name: ewoxservicefastapi
Version: 1.7.29
Summary: 
License-File: LICENSE
Author: Martin Leo Prüss
Author-email: mleopruss@gmail.com
Requires-Python: >=3.13,<4.0
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Requires-Dist: authlib (>=1.6.9,<2.0.0)
Requires-Dist: ewoxcore (>=1.6.42,<10.0.0)
Requires-Dist: fastapi (>=0.135.2,<0.136.0)
Requires-Dist: httpx (>=0.28.1,<0.29.0)
Requires-Dist: itsdangerous (>=2.2.0,<3.0.0)
Requires-Dist: protobuf (>=4.21.6,<7.0.0)
Requires-Dist: python-dotenv (>=1.2.2,<2.0.0)
Requires-Dist: requests (>=2.33.0,<3.0.0)
Requires-Dist: uvicorn (>=0.42.0,<0.43.0)
Requires-Dist: uvloop (>=0.22.1,<0.23.0)
Requires-Dist: websockets (>=15.0,<17.0)
Description-Content-Type: text/markdown

# ewox-service-fastapi

## WebSockets

`ServiceApp` can expose a typed WebSocket route backed by a pluggable session
manager. The default manager stores live sessions in the current process.

```py
from ewoxcore.constants.server_env import ServerEnv
from ewoxservicefastapi.service_app import ServiceApp
from ewoxservicefastapi.websocket.websocket_settings import WebSocketSettings


class APIServer(ServiceApp):
    def __init__(self) -> None:
        super().__init__(service_name="api")

        settings = WebSocketSettings(
            environment=ServerEnv.LOCALHOST,
            public_host="localhost",
            public_port=8804,
            allowed_origins=["http://localhost:3000"],
        )
        self.addWebSocket(settings)
```

The public URL is `ws://localhost:8804/ws` for the `localhost` environment.
All other environments return a client-facing `wss://` URL. Cloudflare
terminates TLS and proxies the WebSocket upgrade to Uvicorn over the private
origin connection, so the service does not load or configure SSL certificate
files. WebSocket transport settings are code-owned and are not read from
`.env`.

An external package can replace the in-memory manager by implementing
`IWebSocketSessionManager` and passing a factory:

```py
self.addWebSocket(
    settings,
    session_manager_factory=lambda websocket_settings: ExternalSessionManager(
        websocket_settings
    ),
)
```

The package has no Redis dependency. A Redis-backed implementation can live in
another package and use this factory boundary for distributed registry and
message coordination while keeping live socket objects in each API process.

### Container scaling

`ServiceApp` starts one Uvicorn process per container and does not configure
Uvicorn's multi-worker mode. Production capacity is increased by running more
containers behind Cloudflare and the platform load balancer.

The default in-memory manager is intended for local development or a
single-container deployment. A multi-container deployment must provide an
external `IWebSocketSessionManager` implementation. The external manager must
keep each live WebSocket object inside its owning container and use its
distributed transport to route direct messages and broadcasts to the correct
container. A shared registry alone is not sufficient.

Transport models are serialized with `JsonUtil`. Application event models
should use `@Serializable` and be registered once through `ClassRegistry`.

### Durable delivery protocol

Distributed adapters can use `WebSocketDeliveryModel[TData]` to mark a server
message as requiring client acknowledgement:

```py
delivery = WebSocketDeliveryModel[WebSocketEventModel](
    delivery_id=delivery_id,
    data=event,
)
await session.send_model(delivery)
```

The serialized message has `type="delivery"`, a stable `deliveryId`, and typed
`data`. After processing it, the client returns a
`WebSocketAcknowledgementModel` with `type="ack"` and the same `deliveryId`.
The external session manager decides when that acknowledgement removes its
durable entry. The framework package does not assume Redis or acknowledge a
delivery merely because `send_text(...)` completed.

