text: import asyncio
  import aiohttp
  import yarl
  
  __version__ = '0.0.0'
  
  __all__ = ('Client',)
  
  
  class Asset(dict):
  
      __slots__ = ()
  
      def _convert(self, value):
  
          cls = value.__class__
  
          if cls is dict:
  
              value = self.__class__(value)
  
          elif cls is list:
  
              value = tuple(map(self._convert, value))
  
          return value
  
      def _consistent(self, key, old):
  
          value = self._convert(old)
  
          if not value is old:
  
              self.__setitem__(key, value)
  
          return value
  
      def __getitem__(self, key):
  
          value = super().__getitem__(key)
  
          value = self._consistent(key, value)
  
          return value
  
      def __getattr__(self, key):
  
          try:
  
              value = self.__getitem__(key)
  
          except KeyError as error:
  
              raise AttributeError(*error.args) from None
  
          return value
  
  
  class Client:
  
      """
      Main means of reading the 4chan API.
  
      Parameters:
          http: bool [True]
              whether to use http or https scheme
          hold: int [1]
              waiting time between requests
              consult https://github.com/4chan/4chan-API#api-rules
          limit: int [8000]
              maximum amount of concurrent requests
              this is to prevent "too many files" exceptions
          session: aiohttp.ClientSession [None]
              used for requesting
              if None, created automatically
          loop: asyncio.AbstractEventLoop [None]
              used and passed whethever applicable
              if None, fetched from asyncio.get_event_loop
      """
  
      __slots__ = ('_url', '_session', '_hold', '_lock', '_limit', '_loop')
  
      _base = yarl.URL('http://a.4cdn.org')
  
      _urls = (_base.with_scheme('https'), _base)
  
      def __init__(self,
                   http = True,
                   hold = 1,
                   limit = 8000,
                   session = None,
                   loop = None):
  
          if not loop:
  
              loop = asyncio.get_event_loop()
  
          if not session:
  
              session = aiohttp.ClientSession(loop = loop)
  
          self._url = self._urls[http]
  
          self._hold = hold
  
          self._lock = asyncio.Lock(loop = loop)
  
          self._limit = asyncio.Semaphore(value = limit, loop = loop)
  
          self._session = session
  
          self._loop = loop
  
      @property
      def url(self):
  
          """
          Get the yarl.URL used for requests.
          """
  
          return self._url
  
      @property
      def session(self):
  
          """
          Get the aiohttp.ClientSession used for requests.
          """
  
          return self._session
  
      @property
      def loop(self):
  
          """
          Get the event loop used for async operations.
          """
  
          return self._loop
  
      async def _comply(self):
  
          await asyncio.sleep(self._hold, loop = self._loop)
  
          self._lock.release()
  
      async def interact(self, route):
  
          """
          Execute the request.
  
          Paramters:
              route: str
                  base/{route}.json part of the url
              **kwargs: dict
                  passed to the request call
          """
  
          if self._hold > 0:
  
              await self._lock.acquire()
  
              self._loop.create_task(self._comply())
  
          url = self._url.with_path(f'{route}.json')
  
          await self._limit.acquire()
  
          try:
  
              response = await self._session.request('GET', url)
  
          finally:
  
              self._limit.release()
  
          response.raise_for_status()
  
          payload = await response.json()
  
          return payload
  
      async def get_boards(self):
  
          """
          Get all boards.
          """
  
          data = await self.interact(f'boards')
  
          value = tuple(map(Asset, data['boards']))
  
          return value
  
      async def get_threads(self, board_id, page = None):
  
          """
          Get the board's active threads.
  
          Parameters:
              board_id: str
                  board name
              page: int [None]
                  board index
                  omitting this will return all pages
          """
  
          route = f'{board_id}/' + (str(page) if page else 'threads')
  
          data = await self.interact(route)
  
          value = tuple(map(Asset, data))
  
          return value
  
      async def get_archive(self, board_id):
  
          """
          Get the board's archived threads.
  
          Parameters:
              board_id: str
                  board name
          """
  
          route = f'{board_id}/archive'
  
          try:
  
              data = await self.interact(route)
  
          except aiohttp.ClientResponseError as error:
  
              if error.status == 404:
  
                  return ()
  
              raise
  
          value = tuple(data)
  
          return value
  
      async def get_thread(self, board_id, thread_id):
  
          """
          Get the board thread's posts.
  
          Parameters:
              board_id: str
                  board name
              thread_id: int
                  thread identifier
          """
  
          route = f'{board_id}/thread/{thread_id}'
  
          data = await self.interact(route)
  
          value = tuple(map(Asset, data['posts']))
  
          return value
  
      async def get_catalog(self, board_id):
  
          """
          Get the board's catalog.
  
          Parameters:
              board_id: str
                  board name
          """
  
          route = f'{board_id}/catalog'
  
          data = await self.interact(route)
  
          value = Asset(data)
  
          return value
  