Phase 1: public market + universe lookups via ESI
CI / lint (pull_request) Successful in 52s
CI / test (pull_request) Successful in 56s
CI / publish (pull_request) Has been skipped
CI / lint (push) Successful in 52s
CI / test (push) Successful in 56s
CI / publish (push) Failing after 34s

Add a thin async ESI client (httpx, descriptive User-Agent, uniform errors,
mockable transport) plus name<->id resolution. New read-only tools:

- universe: resolve_names, get_item, get_system, get_station, get_region, get_route
- market:   list_hubs, get_market_price, get_market_orders, compare_hubs

Trade-hub IDs (Jita/Amarr/Dodixie/Rens/Hek) are hardcoded constants. Market
aggregation (best buy/sell, spread, order book) lives in pure helpers. 30 unit
tests with mocked ESI; coverage 99%. SDE bulk-load deferred to a later phase.

Closes #2

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
LatteandClaude Opus 4.8 committed 2026-06-27 17:49:40 +02:00
1 parent 585acc766c
commit 4223141cf7
11 files changed
+839 -1

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
"""Name <-> id resolution."""
import asyncio
import httpx
import pytest
from familiar import lookup
def test_resolve_id_passes_through_numeric():
assert asyncio.run(lookup.resolve_id(34)) == 34
assert asyncio.run(lookup.resolve_id("34")) == 34
def test_resolve_name_picks_requested_category(mock_esi):
body = {
"inventory_types": [{"id": 34, "name": "Tritanium"}],
"systems": [{"id": 30000142, "name": "Jita"}],
}
mock_esi(lambda req: httpx.Response(200, json=body))
assert asyncio.run(lookup.resolve_name("Tritanium", "inventory_types")) == (
34,
"inventory_types",
)
def test_resolve_id_resolves_name(mock_esi):
body = {"inventory_types": [{"id": 34, "name": "Tritanium"}]}
mock_esi(lambda req: httpx.Response(200, json=body))
assert asyncio.run(lookup.resolve_id("Tritanium", "inventory_types")) == 34
def test_resolve_name_raises_when_absent(mock_esi):
mock_esi(lambda req: httpx.Response(200, json={}))
with pytest.raises(LookupError):
asyncio.run(lookup.resolve_name("Nope", "inventory_types"))