Repository: litestar-org/litestar

Issue:
Enhancement: Allow configuration of an application via an `AppConfig` instance
We already have `AppConfig` in place, so it would be nice to be able to do something like 

```python
from starlite import Starlite, AppConfig

config = AppConfig(...)

app = Starlite.from_config(config)
```




Discussion:
Cool, why not
How would you recommend this be implemented ? 
Should the from_config just call __init__ under the hood
```
@classmethod
def from_config(cls, config):
    return cls(**config) // this wont work since config is not a dict
``` 
Something like this ?
>  Should the from_config just call init under the hood

Something like calling `Starlite(**config.dict(exclude_unset=True))` would certainly work, but then we just turn around and construct another `AppConfig` object inside `__init__` (not that performance is an issue, as it only happens once).

We could add `app_config: Optional[AppConfig] = None` to the `Starlite.__init__()` signature, and if it is provided, we use that as the initial config object that is passed to the `on_app_init` hooks, in lieu of constructing our own. If that was the case, the constructor method wouldn't be necessary as we could simply call `Starlite(config=AppConfig(...))`, but we'd have to work out precedence rules, e.g., if both `route_handlers` and `config` were given to the constructor, then should they be merged, would one take precedence over the other etc etc.

Starlite has _a lot_ of `__init__` params, maybe an evolution into 2.0 could be adding the `config` object to the signature, and then in 2.0 making config of the app _only_ via config object.
> maybe an evolution into 2.0 could be adding the config object to the signature, and then in 2.0 making config of the app only via config object.

I'm not sure about that. Feels a bit cumbersome *having* to construct a config object and pass it immediately to `Starlite`. 

I think for now, the best approach would be to do what you described, but `@overload` `__init__`, to only allow either kwargs or a config object, and then just discard all kwargs if a config object is passed. 
> I think for now, the best approach would be to do what you described, but @overload __init__, to only allow either kwargs or a config object, and then just discard all kwargs if a config object is passed.

SGTM. We'd need to make `route_handlers` optional for that, but I can't see why that should be a problem, and also put default values on the `AppConfig` object
Can we initialize AppConfig from .env's ? 
No. It's mostly Python objects, so adding `.env`-file support doesn't make a lot of sense.
There's not a lot of settings there that would lend to being directly set by environment.

I go pretty heavy on the config-via-environment approach in [starlite-saqlalchemy](https://github.com/topsport-com-au/starlite-saqlalchemy/blob/main/src/starlite_saqlalchemy/settings.py), but mostly that is using environment to configure things like cache config, openapi config, sqlalchemy plugin etc, which are then passed to the app via `AppConfig` as a `on_app_init` hook.
@provinzkraut @peterschutt  Thank you for informing, One more question generally we could set defaults in dunder init for AppConfig or create a settings.py like how django does it (Although I think that would over complicate things for this issue)
>  One more question generally we could set defaults in dunder init for AppConfig

If you take a look at `AppConfig`, you'll see that it's a Pydantic model. So the correct way to set defaults would be on the class attributes themselves. 

I don't think `settings.py` fits the Starlite way of doing things, and I don't see any benefit of this. Even for Django this makes things more complicated. It's opaque "automagic", which almost never is a good idea.
ok thanks for the help
> I'm not sure about that. Feels a bit cumbersome _having_ to construct a config object and pass it immediately to `Starlite`.

Yeh OK, so on retrospect replacing all config would be a bit extreme, but could we still benefit from consolidating some of the configs into a config object, just like we do for `openapi_config`, `static_files_config` etc etc. The one that stands out to me most is the lifecycle hooks., having a `LifecycleHookConfig` makes sense as a grouping IMO, and would significantly reduce the number of parameters to the application.
> The one that stands out to me most is the lifecycle hooks., having a `LifecycleHookConfig` makes sense as a grouping IMO, and would significantly reduce the number of parameters to the application.

Well it would certainly make sense, and it'd be 8 parameters we could remove. On the other hand

```python
app = Starlite(..., on_startup=on_startup)
```
is way more convenient than

```python
app = Starlite(..., lifecycle_hooks_config=LifecycleHooksConfig(on_startup=on_startup))
```
@wassafshahzad were you working on this?

@JacobCoffee - you can work on this 
> @wassafshahzad were you working on this?
> 

Yes will have a pr up by tomorrow 

Resolving patch:
diff --git a/starlite/app.py b/starlite/app.py
--- a/starlite/app.py
+++ b/starlite/app.py
@@ -15,7 +15,7 @@
 )
 
 from pydantic_openapi_schema import construct_open_api_with_schema_class
-from typing_extensions import TypedDict
+from typing_extensions import Self, TypedDict
 
 from starlite.asgi import ASGIRouter
 from starlite.asgi.utils import get_route_handlers, wrap_in_exception_handler
@@ -446,6 +446,18 @@ async def __call__(
         scope["state"] = {}
         await self.asgi_handler(scope, receive, self._wrap_send(send=send, scope=scope))  # type: ignore[arg-type]
 
+    @classmethod
+    def from_config(cls, config: AppConfig) -> Self:
+        """Initialize a ``Starlite`` application from a configuration instance.
+
+        Args:
+            config: An instance of :class:`AppConfig` <startlite.config.AppConfig>
+
+        Returns:
+            An instance of ``Starlite`` application.
+        """
+        return cls(**dict(config))
+
     def register(  # type: ignore[override]
         self, value: "ControllerRouterHandler", add_to_openapi_schema: bool = False
     ) -> None:
diff --git a/starlite/config/app.py b/starlite/config/app.py
--- a/starlite/config/app.py
+++ b/starlite/config/app.py
@@ -6,6 +6,7 @@
 from starlite.connection import Request, WebSocket
 from starlite.datastructures import CacheControlHeader, ETag
 from starlite.di import Provide
+from starlite.events.emitter import SimpleEventEmitter
 from starlite.events.listener import EventListener
 from starlite.plugins import PluginProtocol
 from starlite.types import (
@@ -50,7 +51,7 @@ class AppConfig(BaseModel):
     class Config(BaseConfig):
         arbitrary_types_allowed = True
 
-    after_exception: List[AfterExceptionHookHandler]
+    after_exception: List[AfterExceptionHookHandler] = []
     """An application level :class:`exception hook handler <starlite.types.AfterExceptionHookHandler>` or list thereof.
 
     This hook is called after an exception occurs. In difference to exception handlers, it is not meant to return a
@@ -67,12 +68,12 @@ class Config(BaseConfig):
 
     :class:`Request <starlite.connection.Request>` object and should not return any values.
     """
-    after_shutdown: List[LifeSpanHookHandler]
+    after_shutdown: List[LifeSpanHookHandler] = []
     """An application level :class:`life-span hook handler <starlite.types.LifeSpanHookHandler>` or list thereof.
 
     This hook is called during the ASGI shutdown, after all callables in the 'on_shutdown' list have been called.
     """
-    after_startup: List[LifeSpanHookHandler]
+    after_startup: List[LifeSpanHookHandler] = []
     """An application level :class:`life-span hook handler <starlite.types.LifeSpanHookHandler>` or list thereof.
 
     This hook is called during the ASGI startup, after all callables in the 'on_startup' list have been called.
@@ -85,22 +86,22 @@ class Config(BaseConfig):
     :class:`Request <starlite.connection.Request>` instance and any non-``None`` return value is used for the response, bypassing
     the route handler.
     """
-    before_send: List[BeforeMessageSendHookHandler]
+    before_send: List[BeforeMessageSendHookHandler] = []
     """An application level :class:`before send hook handler <starlite.types.BeforeMessageSendHookHandler>` or list thereof.
 
     This hook is called when the ASGI send function is called.
     """
-    before_shutdown: List[LifeSpanHookHandler]
+    before_shutdown: List[LifeSpanHookHandler] = []
     """An application level :class:`life-span hook handler <starlite.types.LifeSpanHookHandler>` or list thereof.
 
     This hook is called during the ASGI shutdown, before any callables in the 'on_shutdown' list have been called.
     """
-    before_startup: List[LifeSpanHookHandler]
+    before_startup: List[LifeSpanHookHandler] = []
     """An application level :class:`life-span hook handler <starlite.types.LifeSpanHookHandler>` or list thereof.
 
     This hook is called during the ASGI startup, before any callables in the 'on_startup' list have been called.
     """
-    cache_config: CacheConfig
+    cache_config: CacheConfig = CacheConfig()
     """Configures caching behavior of the application."""
     cache_control: Optional[CacheControlHeader]
     """A ``cache-control`` header of type :class:`CacheControlHeader <starlite.datastructures.CacheControlHeader>` to add to route
@@ -116,67 +117,67 @@ class Config(BaseConfig):
     """If set this enables the builtin CORS middleware."""
     csrf_config: Optional[CSRFConfig]
     """If set this enables the builtin CSRF middleware."""
-    debug: bool
+    debug: bool = False
     """If ``True``, app errors rendered as HTML with a stack trace."""
-    dependencies: Dict[str, Provide]
+    dependencies: Dict[str, Provide] = {}
     """A string keyed dictionary of dependency :class:`Provider <starlite.datastructures.Provide>` instances."""
     etag: Optional[ETag]
     """An ``etag`` header of type :class:`ETag <starlite.datastructures.ETag>` to add to route handlers of this app.
 
     Can be overridden by route handlers.
     """
-    event_emitter_backend: Type[BaseEventEmitterBackend]
+    event_emitter_backend: Type[BaseEventEmitterBackend] = SimpleEventEmitter
     """A subclass of :class:`BaseEventEmitterBackend <starlite.events.emitter.BaseEventEmitterBackend>`."""
-    exception_handlers: ExceptionHandlersMap
+    exception_handlers: ExceptionHandlersMap = {}
     """A dictionary that maps handler functions to status codes and/or exception types."""
-    guards: List[Guard]
+    guards: List[Guard] = []
     """A list of :class:`Guard <starlite.types.Guard>` callables."""
-    initial_state: InitialStateType
+    initial_state: InitialStateType = {}
     """An object from which to initialize the app state."""
-    listeners: List[EventListener]
+    listeners: List[EventListener] = []
     """A list of :class:`EventListener <starlite.events.listener.EventListener>`."""
     logging_config: Optional[BaseLoggingConfig]
     """An instance of :class:`BaseLoggingConfig <starlite.config.logging.BaseLoggingConfig>` subclass."""
     middleware: List[Middleware]
     """A list of :class:`Middleware <starlite.types.Middleware>`."""
-    on_shutdown: List[LifeSpanHandler]
+    on_shutdown: List[LifeSpanHandler] = []
     """A list of :class:`LifeSpanHandler <starlite.types.LifeSpanHandler>` called during application shutdown."""
-    on_startup: List[LifeSpanHandler]
+    on_startup: List[LifeSpanHandler] = []
     """A list of :class:`LifeSpanHandler <starlite.types.LifeSpanHandler>` called during application startup."""
     openapi_config: Optional[OpenAPIConfig]
     """Defaults to :data:`DEFAULT_OPENAPI_CONFIG <starlite.app.DEFAULT_OPENAPI_CONFIG>`"""
-    opt: Dict[str, Any]
+    opt: Dict[str, Any] = {}
     """A string keyed dictionary of arbitrary values that can be accessed in :class:`Guards <starlite.types.Guard>` or
     wherever you have access to :class:`Request <starlite.connection.request.Request>` or :class:`ASGI Scope <starlite.types.Scope>`.
 
     Can be overridden by routers and router handlers.
     """
-    parameters: ParametersMap
+    parameters: ParametersMap = {}
     """A mapping of :class:`Parameter <starlite.params.Parameter>` definitions available to all application paths."""
-    plugins: List[PluginProtocol]
+    plugins: List[PluginProtocol] = []
     """List of :class:`SerializationPluginProtocol <starlite.plugins.base.SerializationPluginProtocol>`."""
     request_class: Optional[Type[Request]]
     """An optional subclass of :class:`Request <starlite.connection.request.Request>` to use for http connections."""
     response_class: Optional[ResponseType]
     """A custom subclass of [starlite.response.Response] to be used as the app's default response."""
-    response_cookies: ResponseCookies
+    response_cookies: ResponseCookies = []
     """A list of [Cookie](starlite.datastructures.Cookie] instances."""
-    response_headers: ResponseHeadersMap
+    response_headers: ResponseHeadersMap = {}
     """A string keyed dictionary mapping :class:`ResponseHeader <starlite.datastructures.ResponseHeader>` instances."""
-    route_handlers: List[ControllerRouterHandler]
+    route_handlers: List[ControllerRouterHandler] = []
     """A required list of route handlers, which can include instances of :class:`Router <starlite.router.Router>`, subclasses
     of.
 
     :class:`Controller <starlite.controller.Controller>` or any function decorated by the route handler decorators.
     """
-    security: List[SecurityRequirement]
+    security: List[SecurityRequirement] = []
     """A list of dictionaries that will be added to the schema of all route handlers in the application. See.
 
     :class:`SecurityRequirement <pydantic_openapi_schema.v3_1_0.security_requirement.SecurityRequirement>` for details.
     """
-    static_files_config: List[StaticFilesConfig]
+    static_files_config: List[StaticFilesConfig] = []
     """An instance or list of :class:`StaticFilesConfig <starlite.config.StaticFilesConfig>`."""
-    tags: List[str]
+    tags: List[str] = []
     """A list of string tags that will be appended to the schema of all route handlers under the application."""
     template_config: Optional[TemplateConfig]
     """An instance of :class:`TemplateConfig <starlite.config.TemplateConfig>`."""


Test patch: