
## Python WebSocket Client

You can test this template:
1. Clone the project and run `npm install`
1. Navigate to `packages/templates/clients/websocket/test/integration-test` and run the tests with `npm run test` to generate WebSocket clients
1. Navigate to `packages/templates/clients/websocket/python`
1. Install with `npm install` and run test with `npm run test`
1. Install dependencies of the generated client: `pip install -r test/temp/snapshotTestResult/custom_client_hoppscotch/requirements.txt`
1. Start example script that uses a client library generated by the test: `python example.py`

> By default this is testing against Hoppscotch echo service. You can modify `packages/templates/clients/websocket/python/example.py` and change line 7 to `from temp.snapshotTestResult.client_postman.client import PostmanEchoWebSocketClientClient` and line 21 `client = PostmanEchoWebSocketClientClient()` and run `python example.py` again. You will see example still works but agains different API. This is possible as both AsyncAPI documents have the same name of operation for sending messages: `sendEchoMessage` so each client generated has the same API.

## Client for Slack

To run the Slack Client example, follow the steps above but with 2 exceptions:
- Use `example-slack.py` instead of `example.py`.
- You need to pass environment variables with proper authorization details. Your command must look like this: `TICKET={provide secret info} APP_ID={provide id of the slack app} python example-slack.py`. For example: `TICKET=1d967f38-ccff-44f6-adec-9616eec9c4b7 APP_ID=00dfdcccb53a2645dd3f1773fcb10fa7b0a598cf333a990a9db12375ef1865dd python example-slack.py`.

> Instructions on how to create the Slack app and also obtain authorization is described in details in the [Slack AsyncAPI document](../test/__fixtures__/asyncapi-slack-client.yml).

You can use our AsyncAPI's credentials to access different set of events produced in AsyncAPI Slack workspace, in the `#generator` channel.

1. Make sure you are in the `packages/templates/clients/websocket/python` directory.
1. Install Slack client dependencies by running: `pip install -r test/temp/snapshotTestResult/client_slack/requirements.txt`. If this fails, you probably  did not run tests that generate the client. Fix this by running `npm run test`.
1. Generate an access ticket with an application ID that will enable you to establish a websocket connection. Such a ticket can be used only once. You need to generate a new one every time you connect to Slack server. Replace the following  bearer token with real token that you can find in `slack-example.md` document added to bookmarks of `#generator` channel in [AsyncAPI Slack workspace](https://www.asyncapi.com/slack-invite):

    Linux/MacOs
    ```bash
    curl --location --request POST 'https://slack.com/api/apps.connections.open' \
    --header 'Authorization: Bearer TAKE_XAPP_TOKEN_FROM_BOOKMARKS_DOC_IN_SLACK'
    ```

    Windows
    ```powershell
    curl.exe --location --request POST 'https://slack.com/api/apps.connections.open' `
    --header 'Authorization: Bearer TAKE_XAPP_TOKEN_FROM_BOOKMARKS_DOC_IN_SLACK'
    ```
>**Note**:  Ensure that you do not expose the real token on GitHub or any other public platform because it will be disabled by Slack.

    Example response with `ticket` and `app_id`:
    ```json
    {"ok":true,"url":"wss:\/\/wss-primary.slack.com\/link\/?ticket=089a0c38-cdec-409f-99fa-0ca24e216ea4&app_id=00dfdcccb53a2645dd3f1773fcb10fa7b0a598cf333a990a9db12375ef1865dd"}
    ```

    You can take generated `url` and use with CLI websocket client like `websocat` (first remove excape backslashes):
    ```bash
    websocat "wss://wss-primary.slack.com/link/?ticket=5ad694c1-2a81-4cfc-a503-057b8e798120&app_id=00dfdcccb53a2645dd3f1773fcb10fa7b0a598cf333a990a9db12375ef1865dd"
    ```

    But that is just for testing. The point is to use the generated Python client.

1. Start the example that uses generated client. Examine events, and modify example as you want:
    
    Linux/MacOs
    ```bash
    TICKET=6b150bb1-82b4-457f-a09d-6ff0af1fd2d1 APP_ID=00dfdcccb53a2645dd3f1773fcb10fa7b0a598cf333a990a9db12375ef1865dd python example-slack.py
    ```
    Windows
    ```powershell
    $env:TICKET="dcaa9dc7-b728-40dd-ac40-16dd5f2f8710"; $env:APP_ID="00dfdcccb53a2645dd3f1773fcb10fa7b0a598cf333a990a9db12375ef1865dd"; python example-slack.py
    ```
    
    Successfully established connection will welcome you with below event:
    ```json
    {"type":"hello","num_connections":1,"debug_info":{"host":"applink-3","build_number":118,"approximate_connection_time":18060},"connection_info":{"app_id":"A08NKKBFGBD"}}
    ```
    If you did not receive it, you probably connect with wrong credentials. Remember that generated `ticket` can be used only once to establish a websocket connection.

## Client for Slack with Auto-Routing

To run the Slack Client example with auto-routing, first follow the "Client for Slack" setup above (generate credentials and install dependencies), then:
- Use `example-slack-with-routing.py` instead of `example-slack.py`.
- This example demonstrates auto-routing of messages to registered handlers for different event types (hello, event, disconnect, and unrecognized messages). The client automatically dispatches incoming messages based on their type without manual parsing.

1. Start the example that uses generated client with auto-routing. Examine events, and modify example as you want:
    
    Linux/MacOs
    ```bash
    TICKET=6b150bb1-82b4-457f-a09d-6ff0af1fd2d1 APP_ID=00dfdcccb53a2645dd3f1773fcb10fa7b0a598cf333a990a9db12375ef1865dd python example-slack-with-routing.py
    ```
    Windows
    ```powershell
    $env:TICKET="dcaa9dc7-b728-40dd-ac40-16dd5f2f8710"; $env:APP_ID="00dfdcccb53a2645dd3f1773fcb10fa7b0a598cf333a990a9db12375ef1865dd"; python example-slack-with-routing.py
    ```

1. By default, `discriminator_key` is derived from the discriminator field, and `discriminator_value` from the corresponding `const` in the AsyncAPI document. For non-default cases, users must provide both `discriminator_key` and `discriminator_value` explicitly in register_handlers. Partial inputs are not supported to avoid ambiguity in message routing.

### How Routing Works

- Generated WebSocket clients now automatically route incoming messages to operation-specific handlers based on message discriminators. Users can register handlers for specific message types without manually parsing or filtering messages.
- When a message arrives, the client checks it against registered discriminators (e.g., `type: "hello"`, `type: "events_api"`)
- If a match is found, the message is routed to the specific operation handler (e.g., `onHelloMessage`, `onEvent`)
- If no match is found, the message falls back to generic message handlers
- This enables clean separation of message handling logic based on message types

> `discriminator` is a `string` field that you can add to any AsyncAPI Schema. This also means that it is limited to AsyncAPI Schema only, and it won't work with other schema formats, like for example, Avro. 

The implementation automatically derives discriminator information from your AsyncAPI document:
- Discriminator `key` is extracted from the `discriminator` field in your AsyncAPI spec
- Discriminator `value` is extracted from the `const` property defined in message schemas

Example AsyncAPI Schema with `discriminator` and `const`:
```yaml
schemas:
    hello:
      type: object
      discriminator: type # you specify name of property
      properties:
        type:
          type: string
          const: hello # you specify the value of the discriminator property that is used for routing
          description: A hello string confirming WebSocket connection
```

### Fallback

When defaults aren't available in the AsyncAPI document, users must provide **both** `discriminator_key` and `discriminator_value` when registering handlers. Providing only one parameter is not supported - you must provide either both or neither.

> **Why this limitation exists**: When a receive operation has multiple messages sharing the same discriminator key (e.g., all use `"type"` field), we need the specific value (e.g., `"hello"`, `"disconnect"`) to distinguish between them. Without both pieces of information, the routing becomes ambiguous.

Example:

```python
# Default case - discriminator info auto-derived from AsyncAPI doc
client.register_on_hello_message_handler(my_handler)

# Custom case - must provide both key AND value
client.register_on_hello_message_handler(
    my_handler,
    discriminator_key="message_type",
    discriminator_value="custom_hello"
)
```
