Repository: litestar-org/litestar

Issue:
Testing: DeprecationWarning: Setting per-request cookies
```
tests/test_testing.py::test_test_client[True-session_data0]
tests/test_testing.py::test_test_client[False-session_data1]
  /home/peter/.cache/pypoetry/virtualenvs/starlite-CEGnKvDD-py3.10/lib/python3.10/site-packages/httpx/_client.py:800: DeprecationWarning: Setting per-request cookies=<...> is being deprecated, because the expected behaviour on cookie persistence is ambiguous. Set cookies directly on the client instance instead.
    warnings.warn(message, DeprecationWarning)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
```

HTTPX are deprecating setting cookies per request.

```python
@pytest.mark.parametrize("enable_session, session_data", [(True, {"user": "test-user"}), (False, {})])
def test_test_client(enable_session: bool, session_data: Dict[str, str]) -> None:
    ...
    with TestClient(app=app, session_config=session_config if enable_session else None) as client:
        cookies = client.create_session_cookies(session_data=session_data)
        client.get("/test", cookies=cookies)
        assert app.state.value == 1
```

Easy fix to silence the warning:

```python
@pytest.mark.parametrize("enable_session, session_data", [(True, {"user": "test-user"}), (False, {})])
def test_test_client(enable_session: bool, session_data: Dict[str, str]) -> None:
    ...
    with TestClient(app=app, session_config=session_config if enable_session else None) as client:
        client.cookies = client.create_session_cookies(session_data=session_data)
        client.get("/test")
        assert app.state.value == 1
```

Perhaps we should consider adding `TestClient.set_session_cookies()` method as well.


Discussion:

Resolving patch:
diff --git a/starlite/plugins/sql_alchemy/config.py b/starlite/plugins/sql_alchemy/config.py
--- a/starlite/plugins/sql_alchemy/config.py
+++ b/starlite/plugins/sql_alchemy/config.py
@@ -12,7 +12,7 @@
 )
 
 from orjson import OPT_SERIALIZE_NUMPY, dumps, loads
-from pydantic import BaseConfig, BaseModel, validator
+from pydantic import BaseConfig, BaseModel, root_validator, validator
 from typing_extensions import Literal
 
 from starlite.config.logging import BaseLoggingConfig, LoggingConfig
@@ -147,7 +147,7 @@ class SQLAlchemyConfig(BaseModel):
     class Config(BaseConfig):
         arbitrary_types_allowed = True
 
-    connection_string: str
+    connection_string: Optional[str] = None
     """Database connection string in one of the formats supported by SQLAlchemy.
 
     Notes:
@@ -236,6 +236,30 @@ def validate_before_send_handler(  # pylint: disable=no-self-argument
         """
         return AsyncCallable(value)  # type: ignore[arg-type]
 
+    @root_validator
+    def check_connection_string_or_engine_instance(  # pylint: disable=no-self-argument
+        cls, values: Dict[str, Any]
+    ) -> Dict[str, Any]:
+        """Either `connection_string` or `engine_instance` must be specified,
+        and not both.
+
+        Args:
+            values: Field values, after validation.
+
+        Returns:
+            Field values.
+        """
+        connection_string = values.get("connection_string")
+        engine_instance = values.get("engine_instance")
+
+        if connection_string is None and engine_instance is None:
+            raise ValueError("One of 'connection_string' or 'engine_instance' must be provided.")
+
+        if connection_string is not None and engine_instance is not None:
+            raise ValueError("Only one of 'connection_string' or 'engine_instance' can be provided.")
+
+        return values
+
     @property
     def engine_config_dict(self) -> Dict[str, Any]:
         """
@@ -261,7 +285,9 @@ def engine(self) -> Union["Engine", "FutureEngine", "AsyncEngine"]:
             create_engine_callable = (
                 self.create_async_engine_callable if self.use_async_engine else self.create_engine_callable
             )
-            self.engine_instance = create_engine_callable(self.connection_string, **self.engine_config_dict)
+            self.engine_instance = create_engine_callable(
+                self.connection_string, **self.engine_config_dict  # type:ignore[arg-type]
+            )
         return cast("Union[Engine, FutureEngine, AsyncEngine]", self.engine_instance)
 
     @property


Test patch:
diff --git a/tests/plugins/sql_alchemy_plugin/test_sql_alchemy_config.py b/tests/plugins/sql_alchemy_plugin/test_sql_alchemy_config.py
--- a/tests/plugins/sql_alchemy_plugin/test_sql_alchemy_config.py
+++ b/tests/plugins/sql_alchemy_plugin/test_sql_alchemy_config.py
@@ -1,6 +1,8 @@
 from typing import Any, Optional
 
 import pytest
+from pydantic import ValidationError
+from sqlalchemy import create_engine
 from sqlalchemy.ext.asyncio import AsyncSession
 from sqlalchemy.orm import Session
 from starlette.status import HTTP_200_OK
@@ -99,3 +101,18 @@ def test_logging_config(engine_logger: Optional[str], pool_logger: Optional[str]
 
 def test_default_serializer_returns_string() -> None:
     assert serializer({"hello": "world"}) == '{"hello":"world"}'
+
+
+def test_config_connection_string_or_engine_instance_validation() -> None:
+    with pytest.raises(ValidationError):
+        SQLAlchemyConfig(connection_string=None, engine_instance=None)
+
+    connection_string = "sqlite:///"
+    engine = create_engine(connection_string)
+
+    with pytest.raises(ValidationError):
+        SQLAlchemyConfig(connection_string=connection_string, engine_instance=engine)
+
+    # these should be OK
+    SQLAlchemyConfig(engine_instance=engine)
+    SQLAlchemyConfig(connection_string=connection_string)
diff --git a/tests/plugins/sql_alchemy_plugin/test_sqlalchemy_plugin.py b/tests/plugins/sql_alchemy_plugin/test_sqlalchemy_plugin.py
new file mode 100644
--- /dev/null
+++ b/tests/plugins/sql_alchemy_plugin/test_sqlalchemy_plugin.py
@@ -0,0 +1,67 @@
+from typing import Any
+
+import pytest
+from sqlalchemy import Column, Integer
+from sqlalchemy.orm import Mapper, as_declarative, declarative_base
+
+from starlite.exceptions import ImproperlyConfiguredException
+from starlite.plugins.sql_alchemy import SQLAlchemyPlugin
+
+DeclBase = declarative_base()
+
+
+@as_declarative()
+class AsDeclBase:
+    ...
+
+
+class FromAsDeclarative(AsDeclBase):
+    __tablename__ = "whatever"
+    id = Column(Integer, primary_key=True)
+
+
+class FromDeclBase(DeclBase):  # type:ignore[misc,valid-type]
+    __tablename__ = "whatever"
+    id = Column(Integer, primary_key=True)
+
+
+class Plain:
+    ...
+
+
+@pytest.mark.parametrize(
+    "item,result",
+    [
+        (FromAsDeclarative, True),
+        (FromAsDeclarative(), True),
+        (FromDeclBase, True),
+        (FromDeclBase(), True),
+        (Plain, False),
+        (Plain(), False),
+        (None, False),
+        ("str", False),
+    ],
+)
+def test_is_plugin_supported_type(item: Any, result: bool) -> None:
+    assert SQLAlchemyPlugin.is_plugin_supported_type(item) is result
+
+
+@pytest.mark.parametrize(
+    "item,should_raise",
+    [
+        (FromAsDeclarative, False),
+        (FromAsDeclarative(), True),
+        (FromDeclBase, False),
+        (FromDeclBase(), True),
+        (Plain, True),
+        (Plain(), True),
+        (None, True),
+        ("str", True),
+    ],
+)
+def test_parse_model(item: Any, should_raise: bool) -> None:
+    if should_raise:
+        with pytest.raises(ImproperlyConfiguredException):
+            SQLAlchemyPlugin.parse_model(item)
+    else:
+        assert isinstance(SQLAlchemyPlugin.parse_model(item), Mapper)
diff --git a/tests/test_testing.py b/tests/test_testing.py
--- a/tests/test_testing.py
+++ b/tests/test_testing.py
@@ -202,6 +202,6 @@ def test_handler(state: State, request: Request) -> None:
     app = Starlite(route_handlers=[test_handler], on_startup=[start_up_handler], middleware=[session_config.middleware])
 
     with TestClient(app=app, session_config=session_config if enable_session else None) as client:
-        cookies = client.create_session_cookies(session_data=session_data)
-        client.get("/test", cookies=cookies)
+        client.cookies = client.create_session_cookies(session_data=session_data)
+        client.get("/test")
         assert app.state.value == 1