Repository: litestar-org/litestar

Issue:
Bug: Starlite CLI cannot import project
**Describe the bug**
I have a very minimal hello world application in Starlite, using Poetry.
I have installed python-dotenv, and created a base env file that points to the poetry package.
However, whenever I run `starlite info` I get an import error in the cli.
I've attempted to debug this through the use of print statements, augmenting python's `sys.path`, as well as importing other installed packages, like `pydantic`. 

I am able to import my files using `importlib.import_module` from the python REPL, but am unable to in the cli.

```python
from starlite import Starlite, get

@get("/")
def hello() -> str:
    return "world"

app = Starlite(route_handlers=[hello])
```

**To Reproduce**
1. Generate a Poetry project
2. Install `starlite[full]`
3. Install `python-dotenv`
4. Install `uvicorn`
5. Create a `.env` file pointing to the poetry package
6. Create a basic hello world file in the `__init__.py` (see attachment)
7. Run `starlite info`

**Additional context**
I am on Starlite Version 1.48.1
Here is the full error message. For context the Poetry package is named `starlite_playground`
```
Using Starlite app from env: 'starlite_playground:app'
Traceback (most recent call last):
  File "/home/ikollipara/Documents/GitHub/starlite-playground/.venv/bin/starlite", line 8, in <module>
    sys.exit(cli())
             ^^^^^
  File "/home/ikollipara/Documents/GitHub/starlite-playground/.venv/lib/python3.11/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ikollipara/Documents/GitHub/starlite-playground/.venv/lib/python3.11/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/home/ikollipara/Documents/GitHub/starlite-playground/.venv/lib/python3.11/site-packages/click/core.py", line 1654, in invoke
    super().invoke(ctx)
  File "/home/ikollipara/Documents/GitHub/starlite-playground/.venv/lib/python3.11/site-packages/click/core.py", line 1404, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ikollipara/Documents/GitHub/starlite-playground/.venv/lib/python3.11/site-packages/click/core.py", line 760, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ikollipara/Documents/GitHub/starlite-playground/.venv/lib/python3.11/site-packages/click/decorators.py", line 26, in new_func
    return f(get_current_context(), *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ikollipara/Documents/GitHub/starlite-playground/.venv/lib/python3.11/site-packages/starlite/cli.py", line 347, in cli
    ctx.obj = StarliteEnv.from_env(app_path)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ikollipara/Documents/GitHub/starlite-playground/.venv/lib/python3.11/site-packages/starlite/cli.py", line 102, in from_env
    loaded_app = _autodiscover_app(getenv("STARLITE_APP"))
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ikollipara/Documents/GitHub/starlite-playground/.venv/lib/python3.11/site-packages/starlite/cli.py", line 210, in _autodiscover_app
    return _load_app_from_path(app_path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ikollipara/Documents/GitHub/starlite-playground/.venv/lib/python3.11/site-packages/starlite/cli.py", line 192, in _load_app_from_path
    module = importlib.import_module(module_path)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1206, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1142, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'starlite_playground'
```


Discussion:
I can't reproduce this on my end. Can you share your `pyproject.toml` and directory layout?

Although I have a hunch what's going on. 
> Create a .env file pointing to the poetry package

Does running a separate `poetry install` solve your problem? If you set `STARLITE_APP` to the name of the package as defined in poetry, it could require that this package is actually installed in the current environment, depending on the directory structure / naming.

Resolving patch:
diff --git a/starlite/cli.py b/starlite/cli.py
--- a/starlite/cli.py
+++ b/starlite/cli.py
@@ -1,4 +1,5 @@
 import importlib
+import importlib.util
 import inspect
 import sys
 from dataclasses import dataclass
@@ -80,6 +81,7 @@ class StarliteEnv:
     app_path: str
     debug: bool
     app: Starlite
+    cwd: Path
     host: Optional[str] = None
     port: Optional[int] = None
     reload: Optional[bool] = None
@@ -91,6 +93,11 @@ def from_env(cls, app_path: Optional[str]) -> "StarliteEnv":
 
         If `python-dotenv` is installed, use it to populate environment first
         """
+        cwd = Path().cwd()
+        cwd_str_path = str(cwd)
+        if cwd_str_path not in sys.path:
+            sys.path.append(cwd_str_path)
+
         try:
             import dotenv
 
@@ -99,7 +106,7 @@ def from_env(cls, app_path: Optional[str]) -> "StarliteEnv":
             pass
 
         if not app_path:
-            loaded_app = _autodiscover_app(getenv("STARLITE_APP"))
+            loaded_app = _autodiscover_app(getenv("STARLITE_APP"), cwd)
         else:
             loaded_app = _load_app_from_path(app_path)
 
@@ -113,6 +120,7 @@ def from_env(cls, app_path: Optional[str]) -> "StarliteEnv":
             port=int(port) if port else None,
             reload=_bool_from_env("STARLITE_RELOAD"),
             is_app_factory=loaded_app.is_factory,
+            cwd=cwd,
         )
 
 
@@ -204,12 +212,11 @@ def _path_to_dotted_path(path: Path) -> str:
     return ".".join(path.with_suffix("").parts)
 
 
-def _autodiscover_app(app_path: Optional[str]) -> LoadedApp:
+def _autodiscover_app(app_path: Optional[str], cwd: Path) -> LoadedApp:
     if app_path:
         console.print(f"Using Starlite app from env: [bright_blue]{app_path!r}")
         return _load_app_from_path(app_path)
 
-    cwd = Path().cwd()
     for name in AUTODISCOVER_PATHS:
         path = cwd / name
         if not path.exists():


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,9 +1,8 @@
 import importlib
 import shutil
 import sys
-from contextlib import contextmanager
 from pathlib import Path
-from typing import Callable, Generator, List, Optional, Union
+from typing import Callable, Generator, List, Optional, Protocol, Union
 from unittest.mock import MagicMock
 
 import pytest
@@ -28,21 +27,6 @@
 from starlite.middleware.rate_limit import RateLimitConfig
 from starlite.middleware.session.memory_backend import MemoryBackendConfig
 
-
-@pytest.fixture
-def patch_autodiscovery_paths(request: FixtureRequest) -> Callable[[List[str]], None]:
-    def patcher(paths: List[str]) -> None:
-        old_paths = starlite.cli.AUTODISCOVER_PATHS[::]
-        starlite.cli.AUTODISCOVER_PATHS[:] = paths
-
-        def finalizer() -> None:
-            starlite.cli.AUTODISCOVER_PATHS[:] = old_paths
-
-        request.addfinalizer(finalizer)
-
-    return patcher
-
-
 APP_FILE_CONTENT = """
 from starlite import Starlite
 app = Starlite([])
@@ -72,31 +56,68 @@ def any_name() -> "Starlite":
 """
 
 
-@contextmanager
+@pytest.fixture
+def patch_autodiscovery_paths(request: FixtureRequest) -> Callable[[List[str]], None]:
+    def patcher(paths: List[str]) -> None:
+        old_paths = starlite.cli.AUTODISCOVER_PATHS[::]
+        starlite.cli.AUTODISCOVER_PATHS[:] = paths
+
+        def finalizer() -> None:
+            starlite.cli.AUTODISCOVER_PATHS[:] = old_paths
+
+        request.addfinalizer(finalizer)
+
+    return patcher
+
+
+@fixture
+def tmp_project_dir(monkeypatch: MonkeyPatch, tmp_path: Path) -> Path:
+    path = tmp_path / "project_dir"
+    path.mkdir(exist_ok=True)
+    monkeypatch.chdir(path)
+    return path
+
+
+class CreateAppFileFixture(Protocol):
+    def __call__(
+        self,
+        file: Union[str, Path],
+        directory: Optional[Union[str, Path]] = None,
+        content: Optional[str] = None,
+    ) -> Path:
+        ...
+
+
+@fixture
 def create_app_file(
-    file: Union[str, Path], directory: Optional[Union[str, Path]] = None, content: Optional[str] = None
-) -> Generator[Path, None, None]:
-    base = Path.cwd()
-    if directory:
-        base = base / directory
-        base.mkdir()
-
-    tmp_app_file = base / file
-    tmp_app_file.write_text(
-        content
-        or """
-from starlite import Starlite
-app = Starlite([])
-"""
-    )
+    tmp_project_dir: Path,
+    request: FixtureRequest,
+) -> CreateAppFileFixture:
+    def _create_app_file(
+        file: Union[str, Path],
+        directory: Optional[Union[str, Path]] = None,
+        content: Optional[str] = None,
+    ) -> Path:
+        base = tmp_project_dir
+        if directory:
+            base = base / directory
+            base.mkdir()
+
+        tmp_app_file = base / file
+        tmp_app_file.write_text(content or APP_FILE_CONTENT)
 
-    try:
-        yield tmp_app_file
-    finally:
         if directory:
-            shutil.rmtree(directory)
+            request.addfinalizer(lambda: shutil.rmtree(directory))  # type: ignore[arg-type]
         else:
-            tmp_app_file.unlink()
+            request.addfinalizer(tmp_app_file.unlink)
+        return tmp_app_file
+
+    return _create_app_file
+
+
+@fixture
+def app_file(create_app_file: CreateAppFileFixture) -> Path:
+    return create_app_file("asgi.py")
 
 
 @fixture
@@ -109,12 +130,6 @@ def mock_uvicorn_run(mocker: MockerFixture) -> MagicMock:
     return mocker.patch("uvicorn.run")
 
 
-@fixture
-def app_file() -> Generator[Path, None, None]:
-    with create_app_file("asgi.py") as path:
-        yield path
-
-
 @fixture
 def mock_confirm_ask(mocker: MockerFixture) -> Generator[MagicMock, None, None]:
     yield mocker.patch("starlite.cli.Confirm.ask", return_value=True)
@@ -192,21 +207,23 @@ def test_starlite_env_from_env_host(monkeypatch: MonkeyPatch, app_file: Path) ->
     ],
 )
 @pytest.mark.parametrize("path", AUTODISCOVER_PATHS)
-def test_env_from_env_autodiscover_from_files(path: str, file_content: str) -> None:
+def test_env_from_env_autodiscover_from_files(
+    path: str, file_content: str, create_app_file: CreateAppFileFixture
+) -> None:
     directory = None
     if "/" in path:
         directory, path = path.split("/", 1)
 
-    with create_app_file(path, directory, content=file_content) as tmp_file_path:
-        env = StarliteEnv.from_env(None)
+    tmp_file_path = create_app_file(path, directory, content=file_content)
+    env = StarliteEnv.from_env(None)
 
     assert isinstance(env.app, Starlite)
     assert env.app_path == f"{_path_to_dotted_path(tmp_file_path.relative_to(Path.cwd()))}:app"
 
 
-def test_autodiscover_not_found() -> None:
+def test_autodiscover_not_found(tmp_project_dir: Path) -> None:
     with pytest.raises(StarliteCLIException):
-        _autodiscover_app(None)
+        _autodiscover_app(None, tmp_project_dir)
 
 
 def test_info_command(mocker: MockerFixture, runner: CliRunner, app_file: Path) -> None:
@@ -231,6 +248,7 @@ def test_run_command(
     port: Optional[int],
     host: Optional[str],
     custom_app_file: Optional[Path],
+    create_app_file: CreateAppFileFixture,
     set_in_env: bool,
 ) -> None:
     mock_show_app_info = mocker.patch("starlite.cli._show_app_info")
@@ -264,9 +282,9 @@ def test_run_command(
     else:
         host = "127.0.0.1"
 
-    with create_app_file(custom_app_file or "asgi.py") as path:
+    path = create_app_file(custom_app_file or "asgi.py")
 
-        result = runner.invoke(cli_command, args)
+    result = runner.invoke(cli_command, args)
 
     assert result.exception is None
     assert result.exit_code == 0
@@ -297,11 +315,12 @@ def test_run_command_with_autodiscover_app_factory(
     file_content: str,
     factory_name: str,
     patch_autodiscovery_paths: Callable[[List[str]], None],
+    create_app_file: CreateAppFileFixture,
 ) -> None:
 
     patch_autodiscovery_paths([file_name])
-    with create_app_file(file_name, content=file_content) as path:
-        result = runner.invoke(cli_command, "run")
+    path = create_app_file(file_name, content=file_content)
+    result = runner.invoke(cli_command, "run")
 
     assert result.exception is None
     assert result.exit_code == 0
@@ -318,11 +337,12 @@ def test_run_command_with_autodiscover_app_factory(
 def test_run_command_with_app_factory(
     runner: CliRunner,
     mock_uvicorn_run: MagicMock,
+    create_app_file: CreateAppFileFixture,
 ) -> None:
 
-    with create_app_file("_create_app_with_path.py", content=CREATE_APP_FILE_CONTENT) as path:
-        app_path = f"{path.stem}:create_app"
-        result = runner.invoke(cli_command, ["--app", app_path, "run"])
+    path = create_app_file("_create_app_with_path.py", content=CREATE_APP_FILE_CONTENT)
+    app_path = f"{path.stem}:create_app"
+    result = runner.invoke(cli_command, ["--app", app_path, "run"])
 
     assert result.exception is None
     assert result.exit_code == 0