docstr: Receive query as AST (abstract syntax tree) and act upon it.
  
  Args:
      query (ASTNode): SQL query represented as AST. Usually it should be ast.Select
  
  Returns:
      pd.DataFrame
docstr_tok: ["Receive","query","as","AST","(","abstract","syntax","tree",")","and","act","upon","it",".","Args",":","query","(","ASTNode",")",":","SQL","query","represented","as","AST",".","Usually","it","should","be","ast.Select","Returns",":","pd.DataFrame"]
code: def select(self, query: ast.Select) -> pd.DataFrame:
      """Receive query as AST (abstract syntax tree) and act upon it.
  
      Args:
          query (ASTNode): SQL query represented as AST. Usually it should be ast.Select
  
      Returns:
          pd.DataFrame
      """
      conditions = extract_comparison_conditions(query.where)
  
      if len(conditions) == 0:
          raise Exception("WHERE clause is required")
  
      # Get page id and component id from query
      pageId = None
      componentId = None
      for condition in conditions:
          if condition[1] == "page_id" and condition[0] == "=":
              pageId = condition[2]
  
          if condition[1] == "component_id" and condition[0] == "=":
              componentId = condition[2]
  
      # Get column names from query
      selected_columns = []
      for target in query.targets:
          if isinstance(target, ast.Star):
              selected_columns = self.get_columns()
              break
          elif isinstance(target, ast.Identifier):
              selected_columns.append(target.parts[-1])
          else:
              raise ValueError(f"Unknown query target {type(target)}")
  
      limit = query.limit.value if query.limit else None
      if componentId:
          # Call instatus API and get the response as pd.DataFrame
          df = self.handler.call_instatus_api(
              endpoint=f"/v1/{pageId}/components/{componentId}"
          )
          for langCode in langCodes:
              try:
                  df[f"translations_name_in_{langCode}"] = (
                      df["translations"]
                      .apply(lambda x: x.get("name", None))
                      .apply(lambda x: x.get(langCode, None))
                  )
                  df[f"translations_desc_in_{langCode}"] = (
                      df["translations"]
                      .apply(lambda x: x.get("description", None))
                      .apply(lambda x: x.get(langCode, None))
                  )
              except AttributeError:
                  df[f"translations_name_in_{langCode}"] = None
                  df[f"translations_desc_in_{langCode}"] = None
          df = df.drop(columns=["translations"])
  
          result_df = df[selected_columns]
      else:
          # Call instatus API and get the response as pd.DataFrame
          page_size = 100
          # Calculate the number of pages required
          page_count = (
              (limit + page_size - 1) // page_size if limit else 1
          )
          result_df = pd.DataFrame(columns=selected_columns)
  
          # Call instatus API and get the response as pd.DataFrame for each page
          for page in range(1, page_count + 1):
              current_page_size = (
                  min(page_size, limit) if limit else page_size
              )
  
              df = self.handler.call_instatus_api(
                  endpoint=f"/v1/{pageId}/components",
                  params={"page": page, "per_page": current_page_size},
              )
              # Break if no more data is available or limit is reached
              if len(df) == 0 or (limit and limit <= 0) or limit == 0:
                  break
              """ Add translations_name_in_{langCode} and translations_desc_in_{langCode} columns to the dataframe"""
              for i in range(len(df)):
                  for langCode in langCodes:
                      try:
                          df.at[
                              i, f"translations_name_in_{langCode}"
                          ] = (
                              df.at[i, "translations"]
                              .get("name", {})
                              .get(langCode, None)
                          )
                          df.at[
                              i, f"translations_desc_in_{langCode}"
                          ] = (
                              df.at[i, "translations"]
                              .get("description", {})
                              .get(langCode, None)
                          )
                      except AttributeError:
                          df.at[
                              i, f"translations_name_in_{langCode}"
                          ] = None
                          df.at[
                              i, f"translations_desc_in_{langCode}"
                          ] = None
  
              # Drop the 'translations' column
              df = df.drop(columns=["translations"])
              # Concatenate the dataframes
              result_df = pd.concat(
                  [result_df, df[selected_columns]], ignore_index=True
              )
  
              if limit:
                  limit -= len(df)
  
      return result_df
  
file_path: mindsdb/mindsdb/integrations/handlers/instatus_handler/instatus_tables.py
imports: from typing import List
  import pandas
  from mindsdb.integrations.libs.api_handler import APITable
  from mindsdb_sql.parser import ast
  from mindsdb.integrations.utilities.sql_utils import extract_comparison_conditions
  from mindsdb_sql.parser.ast.select.constant import Constant
  import json
  import re
label_desc: Use image node_id 1 for calling the Components obj's underlying member method code with example usage: obj.select(query) and returns: result_df