code: def schema_from_csv(
      spark: SparkSession, file_path: str
  ) -> T.StructType:  # noqa: C901
      """Return a StructType from a CSV file containing schema configuration.
  
      :param spark: The SparkSession object
      :type spark: pyspark.sql.session.SparkSession
  
      :param file_path: The path to the CSV file containing the schema configuration
      :type file_path: str
  
      :raises ValueError: If the CSV file does not contain the expected columns: name, type, nullable, description
  
      :return: A StructType object representing the schema configuration
      :rtype: pyspark.sql.types.StructType
      """
  
      def _validate_json(metadata: Optional[str]) -> dict:
          if metadata is None:
              return {}
  
          try:
              metadata_dict = json.loads(metadata)
  
          except json.JSONDecodeError as exc:
              msg = f"Invalid JSON: {metadata}"
              raise ValueError(msg) from exc
  
          return metadata_dict
  
      def _lookup_type(type_str: str) -> T.DataType:
          type_lookup = {
              "string": T.StringType(),
              "int": T.IntegerType(),
              "float": T.FloatType(),
              "double": T.DoubleType(),
              "boolean": T.BooleanType(),
              "bool": T.BooleanType(),
              "timestamp": T.TimestampType(),
              "date": T.DateType(),
              "binary": T.BinaryType(),
          }
  
          if type_str not in type_lookup:
              msg = f"Invalid type: {type_str}. Expecting one of: {type_lookup.keys()}"
              raise ValueError(msg)
  
          return type_lookup[type_str]
  
      def _convert_nullable(null_str: str) -> bool:
          if null_str is None:
              return True
  
          parsed_val = null_str.lower()
          if parsed_val not in ["true", "false"]:
              msg = f"Invalid nullable value: {null_str}. Expecting True or False."
              raise ValueError(msg)
  
          return parsed_val == "true"
  
      schema_df = spark.read.csv(file_path, header=True)
      possible_columns = ["name", "type", "nullable", "metadata"]
      num_cols = len(schema_df.columns)
      expected_columns = possible_columns[0:num_cols]
  
      # ensure that csv contains the expected columns: name, type, nullable, description
      if schema_df.columns != expected_columns:
          msg = f"CSV must contain columns in this order: {expected_columns}"
          raise ValueError(msg)
  
      # create a StructType per field
      fields = []
      for row in schema_df.collect():
          field = T.StructField(
              name=row["name"],
              dataType=_lookup_type(row["type"]),
              nullable=_convert_nullable(row["nullable"])
              if "nullable" in row
              else True,
              metadata=_validate_json(
                  row["metadata"] if "metadata" in row else None
              ),
          )
          fields.append(field)
  
      return T.StructType(fields=fields)
  
file_path: quinn/quinn/schema_helpers.py
imports: from __future__ import annotations
  import json
  from typing import Optional
  from pyspark.sql import SparkSession
  from pyspark.sql import types
label_desc: Use image node_id 3 for calling a global function with example usage: schema_from_csv(spark, file_path) and returns: T, metadata_dict, type_lookup, unknown, dict, True