prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  bug: polars backend chokes on Categorical
  ### What happened?
  
  Expected `ibis.polars.read_parquet` to succeed with a categorical dtype in the parquet file. Instead, we see a `KeyError: Categorical` exception.
  
  It looks like the reverse direction is fine, but I believe `ibis.backends.polars.datatypes.py` needs the `dt.Category: pl.Categorical,` pair added into `_to_polars_types`
  
  ### What version of ibis are you using?
  
  4.0.0 (still exists on main)
  
  ### What backend(s) are you using, if any?
  
  polars
  
  ### Relevant log output
  
  ```sh
  KeyError                                  Traceback (most recent call last)
  Cell In[3], line 1
  ----> 1 ibis.polars.read_parquet('data.parquet')
  
  File ~/.conda/envs/modin/lib/python3.10/site-packages/ibis/backends/polars/__init__.py:211, in Backend.read_parquet(self, path, table_name, **kwargs)
      209 table_name = table_name or f"ibis_read_parquet_{next(pa_n)}"
      210 self._tables[table_name] = pl.scan_parquet(path, **kwargs)
  --> 211 return self.table(table_name)
  
  File ~/.conda/envs/modin/lib/python3.10/site-packages/ibis/backends/polars/__init__.py:61, in Backend.table(self, name, _schema)
       60 def table(self, name: str, _schema: sch.Schema = None) -> ir.Table:
  ---> 61     schema = sch.infer(self._tables[name])
       62     return self.table_class(name, schema, self).to_expr()
  
  File ~/.conda/envs/modin/lib/python3.10/site-packages/multipledispatch/dispatcher.py:278, in Dispatcher.__call__(self, *args, **kwargs)
      276     self._cache[types] = func
      277 try:
  --> 278     return func(*args, **kwargs)
      280 except MDNotImplementedError:
      281     funcs = self.dispatch_iter(*types)
  
  File ~/.conda/envs/modin/lib/python3.10/site-packages/ibis/backends/polars/datatypes.py:104, in from_polars_schema(df)
      102 @sch.infer.register(pl.LazyFrame)
      103 def from_polars_schema(df: pl.LazyFrame) -> sch.Schema:
  ...
       75     """Convert polars dtype to the ibis counterpart."""
  ---> 76     klass = _to_ibis_dtypes[typ]
       77     return klass()
  
  KeyError: Categorical
  ```
  
  
  ### Code of Conduct
  
  - [X] I agree to follow this project's Code of Conduct
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `ibis/backends/polars/datatypes.py`
  Content:
  ```
  1 from __future__ import annotations
  2 
  3 import functools
  4 
  5 import polars as pl
  6 
  7 import ibis.expr.datatypes as dt
  8 import ibis.expr.schema as sch
  9 
  10 _to_polars_types = {
  11     dt.Boolean: pl.Boolean,
  12     dt.Null: pl.Null,
  13     dt.Array: pl.List,
  14     dt.String: pl.Utf8,
  15     dt.Binary: pl.Binary,
  16     dt.Date: pl.Date,
  17     dt.Time: pl.Time,
  18     dt.Int8: pl.Int8,
  19     dt.Int16: pl.Int16,
  20     dt.Int32: pl.Int32,
  21     dt.Int64: pl.Int64,
  22     dt.UInt8: pl.UInt8,
  23     dt.UInt16: pl.UInt16,
  24     dt.UInt32: pl.UInt32,
  25     dt.UInt64: pl.UInt64,
  26     dt.Float32: pl.Float32,
  27     dt.Float64: pl.Float64,
  28 }
  29 
  30 _to_ibis_dtypes = {v: k for k, v in _to_polars_types.items()}
  31 
  32 
  33 @functools.singledispatch
  34 def to_polars_type(dtype):
  35     """Convert ibis dtype to the polars counterpart."""
  36     try:
  37         return _to_polars_types[dtype.__class__]  # else return  pl.Object?
  38     except KeyError:
  39         raise NotImplementedError(
  40             f"Translation to polars dtype not implemented for {dtype}"
  41         )
  42 
  43 
  44 @to_polars_type.register(dt.Timestamp)
  45 def from_ibis_timestamp(dtype):
  46     return pl.Datetime("ns", dtype.timezone)
  47 
  48 
  49 @to_polars_type.register(dt.Interval)
  50 def from_ibis_interval(dtype):
  51     if dtype.unit in {'us', 'ns', 'ms'}:
  52         return pl.Duration(dtype.unit)
  53     else:
  54         raise ValueError(f"Unsupported polars duration unit: {dtype.unit}")
  55 
  56 
  57 @to_polars_type.register(dt.Struct)
  58 def from_ibis_struct(dtype):
  59     fields = [
  60         pl.Field(name=name, dtype=to_polars_type(dtype))
  61         for name, dtype in dtype.fields.items()
  62     ]
  63     return pl.Struct(fields)
  64 
  65 
  66 @to_polars_type.register(dt.Category)
  67 def from_ibis_category(dtype):
  68     return pl.Categorical
  69 
  70 
  71 @to_polars_type.register(dt.Array)
  72 def from_ibis_array(dtype):
  73     return pl.List(to_polars_type(dtype.value_type))
  74 
  75 
  76 @functools.singledispatch
  77 def to_ibis_dtype(typ):
  78     """Convert polars dtype to the ibis counterpart."""
  79     klass = _to_ibis_dtypes[typ]
  80     return klass()
  81 
  82 
  83 @to_ibis_dtype.register(pl.Datetime)
  84 def from_polars_datetime(typ):
  85     # TODO(kszucs): handle timezone?
  86     return dt.Timestamp()
  87 
  88 
  89 @to_ibis_dtype.register(pl.List)
  90 def from_polars_list(typ):
  91     return dt.Array(to_ibis_dtype(typ.inner))
  92 
  93 
  94 @to_ibis_dtype.register(pl.Struct)
  95 def from_polars_struct(typ):
  96     return dt.Struct.from_tuples(
  97         [(field.name, to_ibis_dtype(field.dtype)) for field in typ.fields]
  98     )
  99 
  100 
  101 # Can't register here since polars datatypes are classes
  102 # @dt.dtype.register(pl.DataType)
  103 
  104 
  105 @sch.infer.register(pl.LazyFrame)
  106 def from_polars_schema(df: pl.LazyFrame) -> sch.Schema:
  107     fields = [(name, to_ibis_dtype(typ)) for name, typ in df.schema.items()]
  108     return sch.Schema.from_tuples(fields)
  109
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/ibis/backends/polars/datatypes.py b/ibis/backends/polars/datatypes.py
  --- a/ibis/backends/polars/datatypes.py
  +++ b/ibis/backends/polars/datatypes.py
  @@ -10,7 +10,6 @@
   _to_polars_types = {
       dt.Boolean: pl.Boolean,
       dt.Null: pl.Null,
  -    dt.Array: pl.List,
       dt.String: pl.Utf8,
       dt.Binary: pl.Binary,
       dt.Date: pl.Date,
  @@ -28,6 +27,7 @@
   }
   
   _to_ibis_dtypes = {v: k for k, v in _to_polars_types.items()}
  +_to_ibis_dtypes[pl.Categorical] = dt.String
   
   
   @functools.singledispatch
  @@ -64,7 +64,7 @@
   
   
   @to_polars_type.register(dt.Category)
  -def from_ibis_category(dtype):
  +def from_ibis_category(_):
       return pl.Categorical
   
   
  @@ -82,8 +82,12 @@
   
   @to_ibis_dtype.register(pl.Datetime)
   def from_polars_datetime(typ):
  -    # TODO(kszucs): handle timezone?
  -    return dt.Timestamp()
  +    return dt.Timestamp(timezone=typ.tz)
  +
  +
  +@to_ibis_dtype.register(pl.Duration)
  +def from_polars_duration(typ):
  +    return dt.Interval(unit=typ.tu)
   
   
   @to_ibis_dtype.register(pl.List)
  @@ -98,10 +102,6 @@
       )
   
   
  -# Can't register here since polars datatypes are classes
  -# @dt.dtype.register(pl.DataType)
  -
  -
   @sch.infer.register(pl.LazyFrame)
   def from_polars_schema(df: pl.LazyFrame) -> sch.Schema:
       fields = [(name, to_ibis_dtype(typ)) for name, typ in df.schema.items()]
  
verification_info: {"golden_diff": "diff --git a/ibis/backends/polars/datatypes.py b/ibis/backends/polars/datatypes.py\n--- a/ibis/backends/polars/datatypes.py\n+++ b/ibis/backends/polars/datatypes.py\n@@ -10,7 +10,6 @@\n _to_polars_types = {\n     dt.Boolean: pl.Boolean,\n     dt.Null: pl.Null,\n-    dt.Array: pl.List,\n     dt.String: pl.Utf8,\n     dt.Binary: pl.Binary,\n     dt.Date: pl.Date,\n@@ -28,6 +27,7 @@\n }\n \n _to_ibis_dtypes = {v: k for k, v in _to_polars_types.items()}\n+_to_ibis_dtypes[pl.Categorical] = dt.String\n \n \n @functools.singledispatch\n@@ -64,7 +64,7 @@\n \n \n @to_polars_type.register(dt.Category)\n-def from_ibis_category(dtype):\n+def from_ibis_category(_):\n     return pl.Categorical\n \n \n@@ -82,8 +82,12 @@\n \n @to_ibis_dtype.register(pl.Datetime)\n def from_polars_datetime(typ):\n-    # TODO(kszucs): handle timezone?\n-    return dt.Timestamp()\n+    return dt.Timestamp(timezone=typ.tz)\n+\n+\n+@to_ibis_dtype.register(pl.Duration)\n+def from_polars_duration(typ):\n+    return dt.Interval(unit=typ.tu)\n \n \n @to_ibis_dtype.register(pl.List)\n@@ -98,10 +102,6 @@\n     )\n \n \n-# Can't register here since polars datatypes are classes\n-# @dt.dtype.register(pl.DataType)\n-\n-\n @sch.infer.register(pl.LazyFrame)\n def from_polars_schema(df: pl.LazyFrame) -> sch.Schema:\n     fields = [(name, to_ibis_dtype(typ)) for name, typ in df.schema.items()]\n", "issue": "bug: polars backend chokes on Categorical\n### What happened?\n\nExpected `ibis.polars.read_parquet` to succeed with a categorical dtype in the parquet file. Instead, we see a `KeyError: Categorical` exception.\r\n\r\nIt looks like the reverse direction is fine, but I believe `ibis.backends.polars.datatypes.py` needs the `dt.Category: pl.Categorical,` pair added into `_to_polars_types`\n\n### What version of ibis are you using?\n\n4.0.0 (still exists on main)\n\n### What backend(s) are you using, if any?\n\npolars\n\n### Relevant log output\n\n```sh\nKeyError                                  Traceback (most recent call last)\r\nCell In[3], line 1\r\n----> 1 ibis.polars.read_parquet('data.parquet')\r\n\r\nFile ~/.conda/envs/modin/lib/python3.10/site-packages/ibis/backends/polars/__init__.py:211, in Backend.read_parquet(self, path, table_name, **kwargs)\r\n    209 table_name = table_name or f\"ibis_read_parquet_{next(pa_n)}\"\r\n    210 self._tables[table_name] = pl.scan_parquet(path, **kwargs)\r\n--> 211 return self.table(table_name)\r\n\r\nFile ~/.conda/envs/modin/lib/python3.10/site-packages/ibis/backends/polars/__init__.py:61, in Backend.table(self, name, _schema)\r\n     60 def table(self, name: str, _schema: sch.Schema = None) -> ir.Table:\r\n---> 61     schema = sch.infer(self._tables[name])\r\n     62     return self.table_class(name, schema, self).to_expr()\r\n\r\nFile ~/.conda/envs/modin/lib/python3.10/site-packages/multipledispatch/dispatcher.py:278, in Dispatcher.__call__(self, *args, **kwargs)\r\n    276     self._cache[types] = func\r\n    277 try:\r\n--> 278     return func(*args, **kwargs)\r\n    280 except MDNotImplementedError:\r\n    281     funcs = self.dispatch_iter(*types)\r\n\r\nFile ~/.conda/envs/modin/lib/python3.10/site-packages/ibis/backends/polars/datatypes.py:104, in from_polars_schema(df)\r\n    102 @sch.infer.register(pl.LazyFrame)\r\n    103 def from_polars_schema(df: pl.LazyFrame) -> sch.Schema:\r\n...\r\n     75     \"\"\"Convert polars dtype to the ibis counterpart.\"\"\"\r\n---> 76     klass = _to_ibis_dtypes[typ]\r\n     77     return klass()\r\n\r\nKeyError: Categorical\n```\n\n\n### Code of Conduct\n\n- [X] I agree to follow this project's Code of Conduct\n", "before_files": [{"content": "from __future__ import annotations\n\nimport functools\n\nimport polars as pl\n\nimport ibis.expr.datatypes as dt\nimport ibis.expr.schema as sch\n\n_to_polars_types = {\n    dt.Boolean: pl.Boolean,\n    dt.Null: pl.Null,\n    dt.Array: pl.List,\n    dt.String: pl.Utf8,\n    dt.Binary: pl.Binary,\n    dt.Date: pl.Date,\n    dt.Time: pl.Time,\n    dt.Int8: pl.Int8,\n    dt.Int16: pl.Int16,\n    dt.Int32: pl.Int32,\n    dt.Int64: pl.Int64,\n    dt.UInt8: pl.UInt8,\n    dt.UInt16: pl.UInt16,\n    dt.UInt32: pl.UInt32,\n    dt.UInt64: pl.UInt64,\n    dt.Float32: pl.Float32,\n    dt.Float64: pl.Float64,\n}\n\n_to_ibis_dtypes = {v: k for k, v in _to_polars_types.items()}\n\n\n@functools.singledispatch\ndef to_polars_type(dtype):\n    \"\"\"Convert ibis dtype to the polars counterpart.\"\"\"\n    try:\n        return _to_polars_types[dtype.__class__]  # else return  pl.Object?\n    except KeyError:\n        raise NotImplementedError(\n            f\"Translation to polars dtype not implemented for {dtype}\"\n        )\n\n\n@to_polars_type.register(dt.Timestamp)\ndef from_ibis_timestamp(dtype):\n    return pl.Datetime(\"ns\", dtype.timezone)\n\n\n@to_polars_type.register(dt.Interval)\ndef from_ibis_interval(dtype):\n    if dtype.unit in {'us', 'ns', 'ms'}:\n        return pl.Duration(dtype.unit)\n    else:\n        raise ValueError(f\"Unsupported polars duration unit: {dtype.unit}\")\n\n\n@to_polars_type.register(dt.Struct)\ndef from_ibis_struct(dtype):\n    fields = [\n        pl.Field(name=name, dtype=to_polars_type(dtype))\n        for name, dtype in dtype.fields.items()\n    ]\n    return pl.Struct(fields)\n\n\n@to_polars_type.register(dt.Category)\ndef from_ibis_category(dtype):\n    return pl.Categorical\n\n\n@to_polars_type.register(dt.Array)\ndef from_ibis_array(dtype):\n    return pl.List(to_polars_type(dtype.value_type))\n\n\n@functools.singledispatch\ndef to_ibis_dtype(typ):\n    \"\"\"Convert polars dtype to the ibis counterpart.\"\"\"\n    klass = _to_ibis_dtypes[typ]\n    return klass()\n\n\n@to_ibis_dtype.register(pl.Datetime)\ndef from_polars_datetime(typ):\n    # TODO(kszucs): handle timezone?\n    return dt.Timestamp()\n\n\n@to_ibis_dtype.register(pl.List)\ndef from_polars_list(typ):\n    return dt.Array(to_ibis_dtype(typ.inner))\n\n\n@to_ibis_dtype.register(pl.Struct)\ndef from_polars_struct(typ):\n    return dt.Struct.from_tuples(\n        [(field.name, to_ibis_dtype(field.dtype)) for field in typ.fields]\n    )\n\n\n# Can't register here since polars datatypes are classes\n# @dt.dtype.register(pl.DataType)\n\n\n@sch.infer.register(pl.LazyFrame)\ndef from_polars_schema(df: pl.LazyFrame) -> sch.Schema:\n    fields = [(name, to_ibis_dtype(typ)) for name, typ in df.schema.items()]\n    return sch.Schema.from_tuples(fields)\n", "path": "ibis/backends/polars/datatypes.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport functools\n\nimport polars as pl\n\nimport ibis.expr.datatypes as dt\nimport ibis.expr.schema as sch\n\n_to_polars_types = {\n    dt.Boolean: pl.Boolean,\n    dt.Null: pl.Null,\n    dt.String: pl.Utf8,\n    dt.Binary: pl.Binary,\n    dt.Date: pl.Date,\n    dt.Time: pl.Time,\n    dt.Int8: pl.Int8,\n    dt.Int16: pl.Int16,\n    dt.Int32: pl.Int32,\n    dt.Int64: pl.Int64,\n    dt.UInt8: pl.UInt8,\n    dt.UInt16: pl.UInt16,\n    dt.UInt32: pl.UInt32,\n    dt.UInt64: pl.UInt64,\n    dt.Float32: pl.Float32,\n    dt.Float64: pl.Float64,\n}\n\n_to_ibis_dtypes = {v: k for k, v in _to_polars_types.items()}\n_to_ibis_dtypes[pl.Categorical] = dt.String\n\n\n@functools.singledispatch\ndef to_polars_type(dtype):\n    \"\"\"Convert ibis dtype to the polars counterpart.\"\"\"\n    try:\n        return _to_polars_types[dtype.__class__]  # else return  pl.Object?\n    except KeyError:\n        raise NotImplementedError(\n            f\"Translation to polars dtype not implemented for {dtype}\"\n        )\n\n\n@to_polars_type.register(dt.Timestamp)\ndef from_ibis_timestamp(dtype):\n    return pl.Datetime(\"ns\", dtype.timezone)\n\n\n@to_polars_type.register(dt.Interval)\ndef from_ibis_interval(dtype):\n    if dtype.unit in {'us', 'ns', 'ms'}:\n        return pl.Duration(dtype.unit)\n    else:\n        raise ValueError(f\"Unsupported polars duration unit: {dtype.unit}\")\n\n\n@to_polars_type.register(dt.Struct)\ndef from_ibis_struct(dtype):\n    fields = [\n        pl.Field(name=name, dtype=to_polars_type(dtype))\n        for name, dtype in dtype.fields.items()\n    ]\n    return pl.Struct(fields)\n\n\n@to_polars_type.register(dt.Category)\ndef from_ibis_category(_):\n    return pl.Categorical\n\n\n@to_polars_type.register(dt.Array)\ndef from_ibis_array(dtype):\n    return pl.List(to_polars_type(dtype.value_type))\n\n\n@functools.singledispatch\ndef to_ibis_dtype(typ):\n    \"\"\"Convert polars dtype to the ibis counterpart.\"\"\"\n    klass = _to_ibis_dtypes[typ]\n    return klass()\n\n\n@to_ibis_dtype.register(pl.Datetime)\ndef from_polars_datetime(typ):\n    return dt.Timestamp(timezone=typ.tz)\n\n\n@to_ibis_dtype.register(pl.Duration)\ndef from_polars_duration(typ):\n    return dt.Interval(unit=typ.tu)\n\n\n@to_ibis_dtype.register(pl.List)\ndef from_polars_list(typ):\n    return dt.Array(to_ibis_dtype(typ.inner))\n\n\n@to_ibis_dtype.register(pl.Struct)\ndef from_polars_struct(typ):\n    return dt.Struct.from_tuples(\n        [(field.name, to_ibis_dtype(field.dtype)) for field in typ.fields]\n    )\n\n\n@sch.infer.register(pl.LazyFrame)\ndef from_polars_schema(df: pl.LazyFrame) -> sch.Schema:\n    fields = [(name, to_ibis_dtype(typ)) for name, typ in df.schema.items()]\n    return sch.Schema.from_tuples(fields)\n", "path": "ibis/backends/polars/datatypes.py"}]}