Metadata-Version: 2.5
Name: souslesens
Version: 0.3.0
Summary: Python client for the SousLesSensVocables API
Requires-Python: >=3.13
Requires-Dist: requests
Description-Content-Type: text/markdown

# souslesens

Python client for the [SousLesSensVocables](https://github.com/totalenergies/souslesensVocables) API.

## Installation

```bash
pip install souslesens
```

## Quick start

```python
from souslesens import SlsClient

client = SlsClient(token="xxx", base_url="http://localhost:3010")
client.health()
client.ask.knowledge_model_graph("ISO-14224-IOF")
```

## API

Each method mirrors the API path it calls. The `/api/v1/ask/...` endpoints live under `client.ask`.

| Method | Description |
| --- | --- |
| `SlsClient(token, base_url="http://localhost:3010")` | Client for the SousLesSensVocables API. `base_url` defaults to the local backend port. |
| `health() -> dict` | Calls `GET /api/v1/health`. Returns the parsed body (`{"health": {...}}`) whether the status is `200` or `500`. |
| `ask.knowledge_model_graph(source_label) -> dict` | `GET /api/v1/ask/knowledgeModelGraph`. Returns a `{"nodes": [...], "edges": [...]}` graph of the source's main concepts and their relations. |
| `ask.linked_classes(source_label, uri1, uri2=None) -> dict` | `GET /api/v1/ask/linkedClasses`. Returns a map `class URI -> relations` for the given class URI (optionally restricted to the relations reaching `uri2`). |
| `ask.term_classes_infos(source_label, term) -> dict` | `GET /api/v1/ask/termClassesInfos`. Returns a map `class URI -> {id, label, ancestors, predicates, relations}` for every class whose label matches `term`. |
| `ask.terms_linked_classes(source_label, term1, term2=None) -> dict` | `GET /api/v1/ask/termsLinkedClasses`. Same as `linked_classes` but resolving the classes from plain words instead of URIs. |
| `ask.two_terms_shortest_path(source_label, term1, term2) -> list` | `GET /api/v1/ask/twoTermsShortestPath`. Returns the chains of properties joining `term1` and `term2`, each step being a `[subject, property, object]` list of `{id, label}` objects. |
| `ask.ontology_summary() -> list` | `GET /api/v1/ask/ontologySummary`. Returns one `{source, graphUri, imports, description}` entry per source the caller may read that describes itself. |
| `ask.uri_classes_infos(source_label, uri) -> dict` | `GET /api/v1/ask/uriClassesInfos`. Same description as `term_classes_infos`, starting from a class URI instead of a term. |

### Adding a new endpoint

A route inside an existing group (for example the `/api/v1/ask/...` endpoints under `client.ask`):

1. Add a method to the group class in `souslesens/client.py` (e.g. `Ask`) that calls `get_json(self._client.base_url, path, self._client.token, params=...)`.
2. Mirror it in `souslesens/testing.py`: override the method in the mock group class (e.g. `MockAsk`) to delegate to `self._client._get("<method_name>")`, and add the default response to `MockSlsClient.DEFAULT_RESPONSES`.
3. Add a test in `tests/test_client.py` asserting the URL and the `params`, then a row in the API table above.

A route in a new group (anything not under an existing one) needs a new group class, on the model of `Ask`:

1. In `client.py`, define the class (its `__init__` stores the client, one method per route) and instantiate it in `SlsClient.__init__`, next to `self.ask = Ask(self)`.
2. In `testing.py`, define a mock subclass overriding each method to delegate to `self._client._get(...)`, add its default responses to `DEFAULT_RESPONSES`, and instantiate it in `MockSlsClient.__init__`.
3. Add the same test and API table row.

Run `uv run pytest` then `uv run ruff check`.

### Health response

The `health` value is a mapping of probed services to booleans (`virtuoso`, `elasticsearch`, `spacyserver` by default).
The keys depend on the server-side `health_enabled_services` config.

```python
data = client.health()
if data["health"] == "error":
    # server could not probe itself — see HealthCheckError below
    ...
if not all(data["health"].values()):
    # at least one upstream service is down
    ...
```

## Exceptions

- `SlsClientError` — base class for all client failures (network error, invalid JSON response).
- `HealthCheckError(SlsClientError)` — the server returned `{"health": "error"}` (it could not probe itself).

## Testing

A drop-in mock replaces `SlsClient` without any network calls. Inject the health payload and inspect recorded calls:

```python
from souslesens.testing import MockSlsClient

client = MockSlsClient(health={"health": {"virtuoso": True, "elasticsearch": False, "spacyserver": True}})
data = client.health()  # returns the injected payload
assert client.calls == ["health"]
```

## Development

```bash
uv run pytest
uv run pytest --cov
uv run ruff check
uv build
```
