Repository: ibis-project/ibis

Issue:
bug(duckdb): read_only connection parameter doesn't work with booleans
### What happened?

looks like true and false as strings work, but bools don't due to the `capitalize()` call. PR incoming 

```python
[ins] In [1]: import ibis

[ins] In [2]: con = ibis.connect("duckdb://", read_only=False)

[ins] In [3]: exit
(venv) cody@dkdcvoda ibis % ipy
Python 3.11.4 (main, Jun 30 2023, 16:37:57) [Clang 14.0.3 (clang-1403.0.22.14.1)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.14.0 -- An enhanced Interactive Python. Type '?' for help.

[ins] In [1]: import ibis

[ins] In [2]: con = ibis.connect("duckdb://", read_only=True)

[ins] In [3]: con
Out[3]: <ibis.backends.duckdb.Backend at 0x10baee610>

[ins] In [4]: exit
(venv) cody@dkdcvoda ibis % clear

(venv) cody@dkdcvoda ibis % ipy
Python 3.11.4 (main, Jun 30 2023, 16:37:57) [Clang 14.0.3 (clang-1403.0.22.14.1)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.14.0 -- An enhanced Interactive Python. Type '?' for help.

[ins] In [1]: import ibis

[ins] In [2]: con = ibis.connect("duckdb://", read_only=True)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[2], line 1
----> 1 con = ibis.connect("duckdb://", read_only=True)

File ~/repos/ibis/ibis/backends/base/__init__.py:1153, in connect(resource, **kwargs)
   1150 except AttributeError:
   1151     raise ValueError(f"Don't know how to connect to {resource!r}") from None
-> 1153 return backend._from_url(url, **orig_kwargs)

File ~/repos/ibis/ibis/backends/base/sql/__init__.py:68, in BaseSQLBackend._from_url(self, url, **kwargs)
     66 kwargs.update(url.query)
     67 new_kwargs = toolz.merge(kwargs, new_kwargs)
---> 68 self._convert_kwargs(new_kwargs)
     69 return self.connect(**new_kwargs)

File ~/repos/ibis/ibis/backends/duckdb/__init__.py:84, in Backend._convert_kwargs(kwargs)
     82 @staticmethod
     83 def _convert_kwargs(kwargs: MutableMapping) -> None:
---> 84     read_only = kwargs.pop("read_only", "False").capitalize()
     85     try:
     86         kwargs["read_only"] = ast.literal_eval(read_only)

AttributeError: 'bool' object has no attribute 'capitalize'

[ins] In [3]: con = ibis.connect("duckdb://", read_only=False)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[3], line 1
----> 1 con = ibis.connect("duckdb://", read_only=False)

File ~/repos/ibis/ibis/backends/base/__init__.py:1153, in connect(resource, **kwargs)
   1150 except AttributeError:
   1151     raise ValueError(f"Don't know how to connect to {resource!r}") from None
-> 1153 return backend._from_url(url, **orig_kwargs)

File ~/repos/ibis/ibis/backends/base/sql/__init__.py:68, in BaseSQLBackend._from_url(self, url, **kwargs)
     66 kwargs.update(url.query)
     67 new_kwargs = toolz.merge(kwargs, new_kwargs)
---> 68 self._convert_kwargs(new_kwargs)
     69 return self.connect(**new_kwargs)

File ~/repos/ibis/ibis/backends/duckdb/__init__.py:84, in Backend._convert_kwargs(kwargs)
     82 @staticmethod
     83 def _convert_kwargs(kwargs: MutableMapping) -> None:
---> 84     read_only = kwargs.pop("read_only", "False").capitalize()
     85     try:
     86         kwargs["read_only"] = ast.literal_eval(read_only)

AttributeError: 'bool' object has no attribute 'capitalize'

[ins] In [4]: con = ibis.connect("duckdb://", read_only="False")

[ins] In [5]: con = ibis.connect("duckdb://", read_only="True")

```

### What version of ibis are you using?

master

### What backend(s) are you using, if any?

duckdb

### Relevant log output

```sh
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[3], line 1
----> 1 con = ibis.connect("duckdb://", read_only=False)

File ~/repos/ibis/ibis/backends/base/__init__.py:1153, in connect(resource, **kwargs)
   1150 except AttributeError:
   1151     raise ValueError(f"Don't know how to connect to {resource!r}") from None
-> 1153 return backend._from_url(url, **orig_kwargs)

File ~/repos/ibis/ibis/backends/base/sql/__init__.py:68, in BaseSQLBackend._from_url(self, url, **kwargs)
     66 kwargs.update(url.query)
     67 new_kwargs = toolz.merge(kwargs, new_kwargs)
---> 68 self._convert_kwargs(new_kwargs)
     69 return self.connect(**new_kwargs)

File ~/repos/ibis/ibis/backends/duckdb/__init__.py:84, in Backend._convert_kwargs(kwargs)
     82 @staticmethod
     83 def _convert_kwargs(kwargs: MutableMapping) -> None:
---> 84     read_only = kwargs.pop("read_only", "False").capitalize()
     85     try:
     86         kwargs["read_only"] = ast.literal_eval(read_only)

AttributeError: 'bool' object has no attribute 'capitalize'
```


### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:

Resolving patch:
diff --git a/ibis/backends/duckdb/__init__.py b/ibis/backends/duckdb/__init__.py
--- a/ibis/backends/duckdb/__init__.py
+++ b/ibis/backends/duckdb/__init__.py
@@ -81,7 +81,7 @@ def current_database(self) -> str:
 
     @staticmethod
     def _convert_kwargs(kwargs: MutableMapping) -> None:
-        read_only = kwargs.pop("read_only", "False").capitalize()
+        read_only = str(kwargs.pop("read_only", "False")).capitalize()
         try:
             kwargs["read_only"] = ast.literal_eval(read_only)
         except ValueError as e:


Test patch: