> Source: https://wexa.ai/docs/surfaces/python/install

# Install the Python SDK

`wexa` is a single-module HTTP client for the Wexa gateway. No dependencies at all — it is built
on `urllib` from the standard library — and it supports Python 3.9 and later.

## Install

```bash
pip install wexa
```

The **current published version on PyPI is 0.1.2**. Confirm what you actually got:

```python
import wexa
print(wexa.__version__)   # 0.1.2
```

### Reaching a tool with no method on 0.1.2

An unrecognised tool name is sent to the wire verbatim rather than rejected, and the name-to-path
transformation is the same mechanical one. So every tool the gateway registers is reachable from
0.1.2 through `call()`:

```python
fabric.call("delete-context", label="Customer", key="id", value="c-1")
```

With 0.1.2 installed from PyPI, that reached
`POST /v1/delete-context` — the gateway's own access log recorded
`POST /v1/delete-context -> 404`, so the 404 was the gateway saying the tool is not registered in
that deployment, not the client failing to route it.

## Configure

```bash
export WEXA_WORKSPACE=https://fabric.wexa.ai    # your workspace URL
export WEXA_API_KEY=fab_sk_…                    # any bearer credential the gateway accepts
```

`WEXA_WORKSPACE` is the workspace URL, not the API base. The client discovers the API base from
`GET /v1/connection-info` itself. Trailing slashes are stripped for you.

## Construct the client

```python
from wexa import Fabric

fabric = Fabric()                                   # reads the two environment variables
# or
fabric = Fabric(workspace="https://fabric.wexa.ai", api_key=my_key)
```

### Constructor options

| Option | Default | What it does |
|---|---|---|
| `workspace` | `WEXA_WORKSPACE` | Workspace URL. |
| `api_key` | `WEXA_API_KEY` | Bearer credential. |
| `timeout` | `70` | Seconds. Governed tool routes cap near 60s upstream, so 70 lets the server's own deadline fire first. |
| `retries` | `3` | Attempts when a call passes `retry=True`. **Total**, not extra. |

### Discovery happens in the constructor

Unlike the TypeScript client, the Python client performs the `/v1/connection-info` round trip inside
`__init__`, so `fabric.base` is populated the moment the object exists:

```python
fabric = Fabric()
print(fabric.base)      # http://localhost:7123/v1
print(fabric.issuer)    # http://localhost:7123
```

That means constructing a client is a network operation. Construct it once and reuse it; do not
build one per request.

## Verify the install

```python
from wexa import Fabric

fabric = Fabric()
print(fabric.whoami())
```

With 0.1.2 installed from PyPI:

```python
{'dept_id': 'dept_c10', 'org_id': 'org_c10', 'project_id': 'proj_c10',
 'role': 'OWNER', 'user_id': 'u_c10',
 'grants': ['fabric:query.read', 'fabric:docs.read', 'fabric:ontology.write',
            'fabric:orchestrate.read', 'fabric:orchestrate.write', 'fabric:agent.run',
            'fabric:skill.write', 'fabric:model.write']}
```

There is nothing to release. `urlopen` closes per request, so the client has no `close()` and needs
none.

## One SDK ported to the other

`wexa` and `@wexa-fabric/sdk` are deliberate ports of one another, not two independent libraries and
not a stack. Neither wraps the other; both are thin HTTP clients for the same gateway. Python is the
source of truth and TypeScript is the port, and they share one tool table, one set of server-bound
arguments, one error taxonomy, one retry policy and one response envelope.

They are not byte-identical, and a handler moved between them needs four adjustments:

| | Python | TypeScript |
|---|---|---|
| Nothing reached the gateway | urllib's `URLError`, an `OSError` — **not** a `WexaError` | `TransportError`, which *is* a `WexaError` |
| The 504 class | `TimeoutError_`, underscored to dodge the builtin | `TimeoutError` |
| Retry predicates | `isinstance(e, RETRYABLE)` / `RESUME_RETRYABLE` | `isRetryable(e)` / `isResumeRetryable(e)` |
| Discovery | In `__init__` | Deferred to the first call |

Catch `WexaError` **and** `OSError` in Python. A lone `except WexaError` will not see a refused
connection, and that is the failure most likely to happen in production.

## Related

  <Card title="Usage" href="/docs/surfaces/python/usage">
    Calling tools, the non-tool methods, retry and approvals.
  </Card>
  <Card title="Error handling" href="/docs/surfaces/typescript/error-handling">
    The hierarchy both SDKs share, and the resume boundary.
  </Card>
  <Card title="Python quickstart" href="/docs/get-started/quickstart/python">
    First successful call in five minutes.
  </Card>
  <Card title="TypeScript SDK" href="/docs/surfaces/typescript/install">
    The sibling package.
  </Card>
