docs: def reader(file_obj, columns=None):
      """
      Reader for a parquet file object.
  
      This function is a generator returning a list of values for each row
      of data in the parquet file.
  
      :param file_obj: the file containing parquet data
      :param columns: the columns to include. If None (default), all columns
                      are included. Nested values are referenced with "." notation
      """
      if hasattr(file_obj, 'mode') and 'b' not in file_obj.mode:
          logger.error("parquet.reader requires the fileobj to be opened in binary mode!")
      footer = _read_footer(file_obj)
      schema_helper = schema.SchemaHelper(footer.schema)
      keys = columns if columns else [s.name for s in
                                      footer.schema if s.type]
      debug_logging = logger.isEnabledFor(logging.DEBUG)
      for row_group in footer.row_groups:
          res = defaultdict(list)
          row_group_rows = row_group.num_rows
          for col_group in row_group.columns:
              dict_items = []
              cmd = col_group.meta_data
              # skip if the list of columns is specified and this isn't in it
              if columns and not ".".join(cmd.path_in_schema) in columns:
                  continue
  
              offset = _get_offset(cmd)
              file_obj.seek(offset, 0)
              values_seen = 0
              if debug_logging:
                  logger.debug("reading column chunk of type: %s",
                               _get_name(parquet_thrift.Type, cmd.type))
              while values_seen < row_group_rows:
                  page_header = _read_page_header(file_obj)
                  if debug_logging:
                      logger.debug("Reading page (type=%s, "
                                   "uncompressed=%s bytes, "
                                   "compressed=%s bytes)",
                                   _get_name(parquet_thrift.PageType, page_header.type),
                                   page_header.uncompressed_page_size,
                                   page_header.compressed_page_size)
  
                  if page_header.type == parquet_thrift.PageType.DATA_PAGE:
                      values = read_data_page(file_obj, schema_helper, page_header, cmd,
                                              dict_items)
                      res[".".join(cmd.path_in_schema)] += values
                      values_seen += page_header.data_page_header.num_values
                  elif page_header.type == parquet_thrift.PageType.DICTIONARY_PAGE:
                      if debug_logging:
                          logger.debug(page_header)
                      assert dict_items == []
                      dict_items = _read_dictionary_page(file_obj, schema_helper, page_header, cmd)
                      if debug_logging:
                          logger.debug("Dictionary: %s", str(dict_items))
                  else:
                      logger.info("Skipping unknown page type=%s",
                                  _get_name(parquet_thrift.PageType, page_header.type))
  
          for i in range(row_group.num_rows):
              yield [res[k][i] for k in keys if res[k]]