22
33from __future__ import annotations
44
5- import asyncio
6- import json
75from dataclasses import dataclass , field
86from typing import Any
97
108from aiokafka .admin import AIOKafkaAdminClient , NewTopic
119from aiokafka .errors import KafkaError
1210
11+ from ._admin_http import _AdminHttpTransport
1312from .exceptions import TopicError
1413from .validation import validate_topic_name
1514
16- try :
17- import aiohttp
18- HAS_AIOHTTP = True
19- except ImportError :
20- HAS_AIOHTTP = False
21-
2215
2316@dataclass
2417class TopicConfig :
@@ -243,6 +236,7 @@ def __init__(self, client_config: Any):
243236 self ._client_config = client_config
244237 self ._admin : AIOKafkaAdminClient | None = None
245238 self ._started = False
239+ self ._http = _AdminHttpTransport (client_config .http_url )
246240
247241 async def start (self ) -> None :
248242 """Start the admin client."""
@@ -373,7 +367,7 @@ async def list_topics(self) -> list[str]:
373367 raise TopicError ("Admin client not started" )
374368
375369 try :
376- data = await self ._http_get ("/v1/topics" )
370+ data = await self ._http . get ("/v1/topics" )
377371 return [t ["name" ] for t in data if not t .get ("name" , "" ).startswith ("__" )]
378372 except TopicError :
379373 raise
@@ -393,7 +387,7 @@ async def describe_topic(self, name: str) -> TopicInfo:
393387 raise TopicError ("Admin client not started" )
394388
395389 try :
396- data = await self ._http_get (f"/v1/topics/{ name } " )
390+ data = await self ._http . get (f"/v1/topics/{ name } " )
397391 return TopicInfo (
398392 name = data .get ("name" , name ),
399393 partitions = data .get ("partitions" , 0 ),
@@ -405,79 +399,6 @@ async def describe_topic(self, name: str) -> TopicInfo:
405399 except Exception as e :
406400 raise TopicError (f"Failed to describe topic '{ name } ': { e } " ) from e
407401
408- async def _http_get (self , path : str ) -> Any :
409- """Make an HTTP GET request to the Streamline REST API."""
410- http_url = self ._client_config .http_url
411- url = f"{ http_url } { path } "
412-
413- if HAS_AIOHTTP :
414- async with aiohttp .ClientSession () as session :
415- async with session .get (
416- url , timeout = aiohttp .ClientTimeout (total = 10 )
417- ) as resp :
418- if resp .status == 404 :
419- raise TopicError (f"Not found: { path } " )
420- if resp .status != 200 :
421- text = await resp .text ()
422- raise TopicError (f"HTTP { resp .status } : { text } " )
423- return await resp .json ()
424- else :
425- import urllib .request
426- req = urllib .request .Request (url )
427- def _sync_get ():
428- with urllib .request .urlopen (req , timeout = 10 ) as resp :
429- return json .loads (resp .read ())
430- return await asyncio .to_thread (_sync_get )
431-
432- async def _http_post (self , path : str , body : Any ) -> Any :
433- """Make an HTTP POST request to the Streamline REST API."""
434- http_url = self ._client_config .http_url
435- url = f"{ http_url } { path } "
436-
437- if HAS_AIOHTTP :
438- async with aiohttp .ClientSession () as session :
439- async with session .post (
440- url , json = body , timeout = aiohttp .ClientTimeout (total = 10 )
441- ) as resp :
442- if resp .status == 404 :
443- raise TopicError (f"Not found: { path } " )
444- if resp .status not in (200 , 201 ):
445- text = await resp .text ()
446- raise TopicError (f"HTTP { resp .status } : { text } " )
447- return await resp .json ()
448- else :
449- import urllib .request
450- payload = json .dumps (body ).encode ("utf-8" )
451- req = urllib .request .Request (url , data = payload , method = "POST" )
452- req .add_header ("Content-Type" , "application/json" )
453- def _sync_post ():
454- with urllib .request .urlopen (req , timeout = 10 ) as resp :
455- return json .loads (resp .read ())
456- return await asyncio .to_thread (_sync_post )
457-
458- async def _http_delete (self , path : str ) -> None :
459- """Make an HTTP DELETE request to the Streamline REST API."""
460- http_url = self ._client_config .http_url
461- url = f"{ http_url } { path } "
462-
463- if HAS_AIOHTTP :
464- async with aiohttp .ClientSession () as session :
465- async with session .delete (
466- url , timeout = aiohttp .ClientTimeout (total = 10 )
467- ) as resp :
468- if resp .status == 404 :
469- raise TopicError (f"Not found: { path } " )
470- if resp .status >= 300 :
471- text = await resp .text ()
472- raise TopicError (f"HTTP { resp .status } : { text } " )
473- else :
474- import urllib .request
475- req = urllib .request .Request (url , method = "DELETE" )
476- def _sync_delete ():
477- with urllib .request .urlopen (req , timeout = 10 ):
478- pass
479- await asyncio .to_thread (_sync_delete )
480-
481402 async def list_consumer_groups (self ) -> list [str ]:
482403 """List all consumer groups.
483404
@@ -540,7 +461,7 @@ async def cluster_info(self) -> ClusterInfo:
540461 Returns:
541462 ClusterInfo with broker details.
542463 """
543- data = await self ._http_get ("/v1/cluster" )
464+ data = await self ._http . get ("/v1/cluster" )
544465 brokers = [
545466 BrokerInfo (
546467 id = b .get ("id" , 0 ),
@@ -566,7 +487,7 @@ async def consumer_group_lag(self, group_id: str) -> ConsumerGroupLag:
566487 Returns:
567488 ConsumerGroupLag with per-partition lag.
568489 """
569- data = await self ._http_get (f"/v1/consumer-groups/{ group_id } /lag" )
490+ data = await self ._http . get (f"/v1/consumer-groups/{ group_id } /lag" )
570491 partitions = [
571492 ConsumerLag (
572493 topic = p .get ("topic" , "" ),
@@ -595,7 +516,7 @@ async def consumer_group_topic_lag(
595516 Returns:
596517 ConsumerGroupLag scoped to the given topic.
597518 """
598- data = await self ._http_get (f"/v1/consumer-groups/{ group_id } /lag/{ topic } " )
519+ data = await self ._http . get (f"/v1/consumer-groups/{ group_id } /lag/{ topic } " )
599520 partitions = [
600521 ConsumerLag (
601522 topic = p .get ("topic" , topic ),
@@ -633,7 +554,7 @@ async def inspect_messages(
633554 path = f"/v1/inspect/{ topic } ?partition={ partition } &limit={ limit } "
634555 if offset is not None :
635556 path += f"&offset={ offset } "
636- data = await self ._http_get (path )
557+ data = await self ._http . get (path )
637558 return [
638559 InspectedMessage (
639560 offset = m .get ("offset" , 0 ),
@@ -658,7 +579,7 @@ async def latest_messages(
658579 Returns:
659580 List of latest messages.
660581 """
661- data = await self ._http_get (f"/v1/inspect/{ topic } /latest?count={ count } " )
582+ data = await self ._http . get (f"/v1/inspect/{ topic } /latest?count={ count } " )
662583 return [
663584 InspectedMessage (
664585 offset = m .get ("offset" , 0 ),
@@ -677,7 +598,7 @@ async def metrics_history(self) -> list[MetricPoint]:
677598 Returns:
678599 List of metric data points.
679600 """
680- data = await self ._http_get ("/v1/metrics/history" )
601+ data = await self ._http . get ("/v1/metrics/history" )
681602 return [
682603 MetricPoint (
683604 name = m .get ("name" , "" ),
@@ -704,7 +625,7 @@ async def create_branch(
704625 body : dict [str , Any ] = {"name" : name , "base_topic" : base_topic }
705626 if base_offsets :
706627 body ["base_offsets" ] = base_offsets
707- data = await self ._http_post ("/v1/branches" , body )
628+ data = await self ._http . post ("/v1/branches" , body )
708629 return BranchInfo (
709630 name = data .get ("name" , name ),
710631 base_topic = data .get ("base_topic" , base_topic ),
@@ -724,7 +645,7 @@ async def list_branches(self, topic: str | None = None) -> list[BranchInfo]:
724645 path = "/v1/branches"
725646 if topic :
726647 path += f"?topic={ topic } "
727- data = await self ._http_get (path )
648+ data = await self ._http . get (path )
728649 items = data if isinstance (data , list ) else data .get ("items" , [])
729650 return [
730651 BranchInfo (
@@ -742,7 +663,7 @@ async def discard_branch(self, branch_id: str) -> None:
742663 Args:
743664 branch_id: Branch identifier.
744665 """
745- await self ._http_delete (f"/v1/branches/{ branch_id } " )
666+ await self ._http . delete (f"/v1/branches/{ branch_id } " )
746667
747668 async def __aenter__ (self ) -> Admin :
748669 """Enter async context manager."""
0 commit comments