Metadata-Version: 2.5
Name: uex-lib
Version: 0.1.0
Summary: Python client for the UEX Corp API with TTL caching and pluggable HTTP transports
Project-URL: Homepage, https://github.com/Garulf/uex-lib
Project-URL: Issues, https://github.com/Garulf/uex-lib/issues
Author: Garulf
License-Expression: MIT
License-File: LICENSE
Keywords: api,cache,client,star-citizen,trading,uex,uexcorp
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Games/Entertainment
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: aiohttp
Requires-Dist: aiohttp>=3.9; extra == 'aiohttp'
Provides-Extra: httpx
Requires-Dist: httpx>=0.27; extra == 'httpx'
Description-Content-Type: text/markdown

<!-- generated by readwright from README.md.j2; edit the template, not this file -->
# uex-lib

Python client for the UEX Corp API with TTL caching and pluggable HTTP transports

[![PyPI](https://img.shields.io/pypi/v/uex-lib)](https://pypi.org/project/uex-lib/) [![Python](https://img.shields.io/pypi/pyversions/uex-lib)](https://pypi.org/project/uex-lib/) [![CI](https://img.shields.io/github/actions/workflow/status/Garulf/uex-lib/ci.yml)](https://github.com/Garulf/uex-lib/actions/workflows/ci.yml) [![License](https://img.shields.io/github/license/Garulf/uex-lib)](https://github.com/Garulf/uex-lib/blob/main/LICENSE) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

## Installation

```sh
uv add uex-lib          # or: pip install uex-lib
```

The base package has no third-party dependencies. The sync client uses the standard library, and the async client needs one of:

```sh
uv add "uex-lib[httpx]"
uv add "uex-lib[aiohttp]"
```

Requires Python 3.11 or newer.

## Usage

```python
import asyncio
from uex import AsyncClient


async def main():
    async with AsyncClient() as uex:
        prices = await uex.commodities.prices(id_terminal=1)
        for price in prices:
            print(price.commodity_name, price.price_sell)

        route = await uex.commodities.routes(id_commodity=1)
        vehicles = await uex.vehicles.list(id_company=195)


asyncio.run(main())
```

The sync client has the same surface:

```python
from uex import Client

with Client() as uex:
    versions = uex.game_versions.current()
    for system in uex.universe.star_systems():
        print(system.code, system.name)
```

Endpoints that need an app token or a user secret key raise `AuthRequired` locally if the credential is missing, rather than making a doomed request:

```python
from uex import Client

with Client(token="app-token", secret_key="user-secret-key") as uex:
    balance = uex.user.wallet_balance()
    trades = uex.user.trades()
```

Every list or detail response comes back as one of the typed models under `uex.models`, keeping the raw payload on `.raw` so a field the model hasn't caught up to is still reachable.

## Caching

The client caches every public GET response. Freshness comes from the API's own `Cache-Control: max-age`, falling back to a documented TTL per endpoint when the header is missing (30 minutes for most prices, 12 hours for terminals and vehicles, a day for static reference data). Responses behind a user secret key are never cached.

The default store is SQLite in `$XDG_CACHE_HOME/uex/` (or `%LOCALAPPDATA%\uex\` on Windows), so a bot and a script can share one file. An in-process LRU sits in front of it.

If the API is down or returns a 5xx after retries, an expired entry is served with `meta.stale = True`.

```python
from uex import Client, MemoryStore, SqliteStore, LruFront

uex = Client()  # SQLite + LRU, the default
uex = Client(cache=MemoryStore())  # per-process only
uex = Client(cache=LruFront(SqliteStore("/srv/uex.db")))
uex = Client(cache=None)  # no caching at all

uex.commodities.prices(id_terminal=1, fresh=True)  # bypass the cache for one call
uex.purge_cache(endpoint="commodities_prices")  # drop one endpoint's cache
```

## Bring your own HTTP library

The client never imports an HTTP library. It talks to a transport with one method, `send(Request) -> Response`, and ships adapters for urllib (default for the sync client), httpx and aiohttp. Pass your own if you already have a configured session:

```python
import aiohttp
from uex import AsyncClient
from uex.transports.aiohttp import AiohttpTransport

session = aiohttp.ClientSession()
uex = AsyncClient(transport=AiohttpTransport(session))
```

## Website links

`uex.trade_routes_url()` and `uex.trade_route_detail_url()` build links to the UEX Corp website's trade route finder rather than calling the API, so you can hand a pre-filled search or a specific route to a person:

```python
from uex import trade_routes_url, trade_route_detail_url

trade_routes_url(id_vehicle=147, investment=20_000)
# -> "https://uexcorp.space/trade/routes/?id_vehicle=147&investment=20%2C000"

route = await uex.commodities.routes(id_commodity=1)
trade_route_detail_url(route[0].code)
# -> "https://uexcorp.space/trade/route?code=9db9ca68"
```

## Development

```sh
uv sync --all-extras --dev
uv run pytest                                # offline, fixture-driven
UEX_NETWORK=1 uv run pytest tests/contract   # live transport checks
uv run tox                                   # full matrix, lint, types
uv run python scripts/refresh_fixtures.py    # re-record fixtures from the live API
```

## Attribution

Data comes from [UEX Corp](https://uexcorp.space). This is a fan project and is not affiliated with or endorsed by UEX Corp, Cloud Imperium Games, or Star Citizen.

## Contributing

Issues and pull requests are welcome at [Garulf/uex-lib](https://github.com/Garulf/uex-lib).

## License

MIT
