Client
http
¶
BaseHttpClient
¶
Bases: Generic[_ClientT, _SessionT, _CacheManagerT]
Base class containing shared HTTP logic, validation, and media checks.
Source code in pokelance/http/_base.py
AsyncHttpClient
¶
AsyncHttpClient(
*,
cache_size: int,
client: PokeLanceAsyncClient,
session: AsyncSession | None = None,
)
Bases: BaseHttpClient['PokeLanceAsyncClient', AsyncSession, AsyncCacheManager]
The asynchronous HTTP client for PokeLance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
PokeLanceAsyncClient
|
The client that this HTTP client is for. |
required |
cache_size
|
int
|
The size of the cache. |
required |
session
|
AsyncSession | None
|
The session to use for the HTTP client. If not provided, one is created internally on the first request and owned by this client. |
None
|
Source code in pokelance/http/_async.py
def __init__(
self,
*,
cache_size: int,
client: PokeLanceAsyncClient,
session: niquests.AsyncSession | None = None,
) -> None:
super().__init__(client=client, session=session)
self._cache_manager = AsyncCacheManager(max_size=cache_size, client=self._client)
self._loader = AsyncEndpointLoader(client=self._client)
close
async
¶
Closes the HTTP client session and cancels pending endpoint loaders.
Source code in pokelance/http/_async.py
async def close(self) -> None:
"""Closes the HTTP client session and cancels pending endpoint loaders."""
if self._closing:
return
self._closing = True
try:
await self._loader.cancel_tasks()
session_to_close = self.session if self._session_owner else None
self.session = None
if session_to_close:
logger.debug("Closing internal async HTTP session...")
await session_to_close.close()
finally:
self._is_ready = False
self._closing = False
connect
async
¶
Connects the HTTP client and sets up the session.
Source code in pokelance/http/_async.py
async def connect(self) -> None:
"""Connects the HTTP client and sets up the session."""
if self._closing:
raise RuntimeError("Cannot connect while the HTTP client is closing.")
if self.session is None:
logger.debug("Initializing internal async HTTP session (niquests)...")
self.session = niquests.AsyncSession(resolver="system://")
self._session_owner = True
if not self._is_ready:
self._is_ready = True
if self._client.cache_endpoints:
await self._loader.schedule_tasks()
request
async
¶
Makes an asynchronous request to the PokeAPI.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
route
|
Route
|
The route to use for the request. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
The response from the PokeAPI parsed as JSON. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
An error occurred while making the request. |
Source code in pokelance/http/_async.py
async def request(self, route: Route) -> dict[str, t.Any]:
"""Makes an asynchronous request to the PokeAPI.
Parameters
----------
route: Route
The route to use for the request.
Returns
-------
dict[str, t.Any]
The response from the PokeAPI parsed as JSON.
Raises
------
HTTPException
An error occurred while making the request.
"""
if self._closing:
raise RuntimeError("Cannot make a request while the HTTP client is closing.")
await self.connect()
if self.session is not None:
logger.debug(f"Sending {route.method} request to {route.url}")
response = await self.session.request(route.method, route.url, params=route.payload)
return self._validate_response(response, route)
raise HTTPException("No session was provided.", route, -1).create()
load_image
async
¶
Loads an image from the url asynchronously.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The URL to load the image from. |
required |
Returns:
| Type | Description |
|---|---|
bytes
|
The raw image bytes. |
Source code in pokelance/http/_async.py
async def load_image(self, url: str) -> bytes:
"""Loads an image from the url asynchronously.
Parameters
----------
url: str
The URL to load the image from.
Returns
-------
bytes
The raw image bytes.
"""
await self.connect()
if self.session is not None:
logger.debug(f"Fetching image from {url}")
response = await self.session.get(url)
return self._validate_image(response, url)
return b""
load_audio
async
¶
Loads an audio from the url asynchronously.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The URL to load the audio from. |
required |
Returns:
| Type | Description |
|---|---|
bytes
|
The raw audio bytes. |
Source code in pokelance/http/_async.py
async def load_audio(self, url: str) -> bytes:
"""Loads an audio from the url asynchronously.
Parameters
----------
url: str
The URL to load the audio from.
Returns
-------
bytes
The raw audio bytes.
"""
await self.connect()
if self.session is not None:
logger.debug(f"Fetching audio from {url}")
response = await self.session.get(url)
return self._validate_audio(response, url)
return b""
AsyncEndpointLoader
¶
AsyncEndpointLoader(client: PokeLanceAsyncClient)
Composition helper for scheduling and tracking async endpoint pre-population tasks.
Source code in pokelance/http/_async.py
wait_until_ready
async
¶
schedule_tasks
async
¶
Schedules background endpoint-loading tasks using asyncio.create_task.
Source code in pokelance/http/_async.py
async def schedule_tasks(self) -> None:
"""Schedules background endpoint-loading tasks using asyncio.create_task."""
if self._scheduled:
return
self._scheduled = True
self._ready_event.clear()
if not self._client.cache_endpoints:
self._ready_event.set()
self._client.ext_tasks.clear()
return
total = len(self._client.ext_tasks)
self._remaining = total
logger.info(f"Scheduling {total} endpoint pre-population task(s)...")
for num, (coroutine, name) in enumerate(self._client.ext_tasks):
message = f"Extension {name} endpoints ({num + 1}/{total})"
task = asyncio.create_task(coro=self._load_ext(coroutine, message), name=name)
self._tasks.add(task)
task.add_done_callback(self._tasks.discard)
self._client.ext_tasks.clear()
if self._remaining == 0:
self._ready_event.set()
cancel_tasks
async
¶
Cancels and awaits all in-flight endpoint loading tasks.
Source code in pokelance/http/_async.py
async def cancel_tasks(self) -> None:
"""Cancels and awaits all in-flight endpoint loading tasks."""
tasks = [task for task in self._tasks if not task.done()]
if tasks:
logger.warning(f"Cancelling {len(tasks)} in-flight endpoint loading task(s)...")
for task in tasks:
task.cancel()
logger.warning(f"Cancelled task {task.get_name()}")
try:
await asyncio.wait_for(asyncio.gather(*tasks, return_exceptions=True), timeout=1.5)
except asyncio.TimeoutError:
logger.warning("Timed out waiting for endpoint loading tasks to cancel.")
self._tasks.clear()
self._scheduled = False
self._remaining = 0
self._ready_event.set()
SyncHttpClient
¶
SyncHttpClient(
*,
cache_size: int,
client: PokeLanceSyncClient,
session: Session | None = None,
)
Bases: BaseHttpClient['PokeLanceSyncClient', Session, SyncCacheManager]
The synchronous HTTP client for PokeLance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
PokeLanceSyncClient
|
The client that this HTTP client is for. |
required |
cache_size
|
int
|
The size of the cache. |
required |
session
|
Session | None
|
The session to use for the HTTP client. If not provided, one is created internally on the first request and owned by this client. |
None
|
Source code in pokelance/http/_sync.py
def __init__(
self,
*,
cache_size: int,
client: PokeLanceSyncClient,
session: niquests.Session | None = None,
) -> None:
super().__init__(client=client, session=session)
self._cache_manager = SyncCacheManager(max_size=cache_size, client=self._client)
self._loader = SyncEndpointLoader(client=self._client)
self._lock = threading.Lock()
close
¶
Closes the HTTP client and shuts down the endpoint loader thread pool.
Source code in pokelance/http/_sync.py
def close(self) -> None:
"""Closes the HTTP client and shuts down the endpoint loader thread pool."""
with self._lock:
if self._closing:
return
self._closing = True
try:
session_to_close = self.session if self._session_owner else None
self.session = None
if session_to_close:
logger.debug("Closing internal sync HTTP session...")
session_to_close.close()
self._loader.shutdown()
finally:
self._is_ready = False
self._closing = False
connect
¶
Connects the HTTP client and sets up the session.
Source code in pokelance/http/_sync.py
def connect(self) -> None:
"""Connects the HTTP client and sets up the session."""
with self._lock:
if self._closing:
raise RuntimeError("Cannot connect while the HTTP client is closing.")
if self.session is None:
logger.debug("Initializing internal sync HTTP session (niquests)...")
self.session = niquests.Session(resolver="system://")
self._session_owner = True
if not self._is_ready:
self._is_ready = True
if self._client.cache_endpoints:
self._loader.schedule_tasks()
request
¶
Makes a synchronous request to the PokeAPI.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
route
|
Route
|
The route to use for the request. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
The response from the PokeAPI parsed as JSON. |
Raises:
| Type | Description |
|---|---|
HTTPException
|
An error occurred while making the request. |
Source code in pokelance/http/_sync.py
def request(self, route: Route) -> dict[str, t.Any]:
"""Makes a synchronous request to the PokeAPI.
Parameters
----------
route: Route
The route to use for the request.
Returns
-------
dict[str, t.Any]
The response from the PokeAPI parsed as JSON.
Raises
------
HTTPException
An error occurred while making the request.
"""
if self._closing:
raise RuntimeError("Cannot make a request while the HTTP client is closing.")
self.connect()
if self.session is not None:
logger.debug(f"Sending {route.method} request to {route.url}")
response = self.session.request(route.method, route.url, params=route.payload)
return self._validate_response(response, route)
raise HTTPException("No session was provided.", route, -1).create()
load_image
¶
Loads an image from the url synchronously.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The URL to load the image from. |
required |
Returns:
| Type | Description |
|---|---|
bytes
|
The raw image bytes. |
Source code in pokelance/http/_sync.py
def load_image(self, url: str) -> bytes:
"""Loads an image from the url synchronously.
Parameters
----------
url: str
The URL to load the image from.
Returns
-------
bytes
The raw image bytes.
"""
self.connect()
if self.session is not None:
logger.debug(f"Fetching image from {url}")
response = self.session.get(url)
return self._validate_image(response, url)
return b""
load_audio
¶
Loads an audio from the url synchronously.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The URL to load the audio from. |
required |
Returns:
| Type | Description |
|---|---|
bytes
|
The raw audio bytes. |
Source code in pokelance/http/_sync.py
def load_audio(self, url: str) -> bytes:
"""Loads an audio from the url synchronously.
Parameters
----------
url: str
The URL to load the audio from.
Returns
-------
bytes
The raw audio bytes.
"""
self.connect()
if self.session is not None:
logger.debug(f"Fetching audio from {url}")
response = self.session.get(url)
return self._validate_audio(response, url)
return b""
SyncEndpointLoader
¶
SyncEndpointLoader(client: PokeLanceSyncClient)
Composition helper for scheduling and tracking sync endpoint pre-population tasks via thread pool.
Source code in pokelance/http/_sync.py
def __init__(self, client: PokeLanceSyncClient) -> None:
self._client = client
self._executor: concurrent.futures.ThreadPoolExecutor | None = None
self._futures: set[concurrent.futures.Future[None]] = set()
self._remaining: int = 0
self._lock = threading.Lock()
self._ready_event = threading.Event()
self._ready_event.set()
self._scheduled: bool = False
wait_until_ready
¶
Wait until all background endpoint tasks have completed.
Source code in pokelance/http/_sync.py
schedule_tasks
¶
Schedules the background endpoint-loading tasks on a thread pool.
Source code in pokelance/http/_sync.py
def schedule_tasks(self) -> None:
"""Schedules the background endpoint-loading tasks on a thread pool."""
with self._lock:
if self._scheduled:
return
self._scheduled = True
self._ready_event.clear()
if not self._client.cache_endpoints:
self._ready_event.set()
self._client.ext_tasks.clear()
return
total = len(self._client.ext_tasks)
self._remaining = total
logger.info(f"Scheduling {total} endpoint pre-population task(s)...")
if self._executor is None:
self._executor = concurrent.futures.ThreadPoolExecutor(
max_workers=min(32, max(4, total)),
thread_name_prefix="PokeLance-EndpointLoader",
)
for num, (fn, name) in enumerate(self._client.ext_tasks):
message = f"Extension {name} endpoints ({num + 1}/{total})"
future = self._executor.submit(self._load_ext, fn, message)
self._futures.add(future)
future.add_done_callback(self._futures.discard)
self._client.ext_tasks.clear()
if self._remaining == 0:
self._ready_event.set()
shutdown
¶
Cancels and shuts down the loader thread pool.
Source code in pokelance/http/_sync.py
def shutdown(self) -> None:
"""Cancels and shuts down the loader thread pool."""
with self._lock:
count = sum(1 for f in self._futures if not f.done())
if count > 0:
logger.warning(f"Cancelling {count} in-flight endpoint loading tasks...")
for future in list(self._futures):
future.cancel()
if self._executor is not None:
logger.debug("Shutting down endpoint loader thread pool...")
self._executor.shutdown(wait=False, cancel_futures=True)
self._executor = None