diff --git a/faststream/_internal/application.py b/faststream/_internal/application.py index 85e0ba43d0..5f9c523797 100644 --- a/faststream/_internal/application.py +++ b/faststream/_internal/application.py @@ -16,7 +16,11 @@ from faststream._internal.constants import EMPTY from faststream._internal.context import ContextRepo from faststream._internal.log import logger -from faststream._internal.state import DIState +from faststream._internal.state import ( + BasicApplicationState, + DIState, + RunningApplicationState, +) from faststream._internal.state.broker import OuterBrokerState from faststream._internal.utils import apply_types from faststream._internal.utils.functions import ( @@ -99,13 +103,15 @@ def _init_setupable_( # noqa: PLW3201 serializer = PydanticSerializer() - self._state = DIState( - use_fastdepends=True, - get_dependent=None, - call_decorators=(), - serializer=serializer, - provider=self.provider, - context=self.context, + self._state = BasicApplicationState( + di_state=DIState( + use_fastdepends=True, + get_dependent=None, + call_decorators=(), + serializer=serializer, + provider=self.provider, + context=self.context, + ) ) self.broker = broker @@ -113,7 +119,7 @@ def _init_setupable_( # noqa: PLW3201 self._setup() def _setup(self) -> None: - self.broker._setup(OuterBrokerState(di_state=self._state)) + self.broker._setup(OuterBrokerState(di_state=self._state.di_state)) async def _start_broker(self) -> None: await self.broker.start() @@ -188,6 +194,8 @@ async def _startup( async with self._startup_logging(log_level=log_level): await self.start(**(run_extra_options or {})) + self._state = RunningApplicationState(di_state=self._state.di_state) + async def start( self, **run_extra_options: "SettingField", @@ -235,6 +243,8 @@ async def _shutdown(self, log_level: int = logging.INFO) -> None: async with self._shutdown_logging(log_level=log_level): await self.stop() + self._state = BasicApplicationState(di_state=self._state.di_state) + async def stop(self) -> None: """Executes shutdown hooks and stop broker.""" async with self._shutdown_hooks_context(): @@ -318,3 +328,7 @@ def after_shutdown( apply_types(to_async(func), context__=self.context) ) return func + + @property + def running(self) -> bool: + return self._state.running diff --git a/faststream/_internal/state/__init__.py b/faststream/_internal/state/__init__.py index f65fc1cb63..47975c762b 100644 --- a/faststream/_internal/state/__init__.py +++ b/faststream/_internal/state/__init__.py @@ -1,3 +1,4 @@ +from .application import BasicApplicationState, RunningApplicationState from .broker import BrokerState, EmptyBrokerState from .fast_depends import DIState from .logger import LoggerParamsStorage, LoggerState @@ -6,6 +7,7 @@ __all__ = ( # state + "BasicApplicationState", "BrokerState", # FastDepend "DIState", @@ -14,6 +16,7 @@ # logging "LoggerState", "Pointer", + "RunningApplicationState", # proto "SetupAble", ) diff --git a/faststream/_internal/state/application.py b/faststream/_internal/state/application.py new file mode 100644 index 0000000000..e63aa3be29 --- /dev/null +++ b/faststream/_internal/state/application.py @@ -0,0 +1,28 @@ +from abc import ABC, abstractmethod + +from faststream._internal.state.fast_depends import DIState + + +class ApplicationState(ABC): + def __init__(self, di_state: DIState) -> None: + self._di_state = di_state + + @property + @abstractmethod + def running(self) -> bool: ... + + @property + def di_state(self) -> DIState: + return self._di_state + + +class BasicApplicationState(ApplicationState): + @property + def running(self) -> bool: + return False + + +class RunningApplicationState(ApplicationState): + @property + def running(self) -> bool: + return True diff --git a/tests/cli/test_app_state.py b/tests/cli/test_app_state.py new file mode 100644 index 0000000000..9896b8839a --- /dev/null +++ b/tests/cli/test_app_state.py @@ -0,0 +1,31 @@ +from unittest.mock import AsyncMock, patch + +import pytest + +from faststream import FastStream + + +@pytest.mark.asyncio() +async def test_state_running(app: FastStream) -> None: + with patch( + "faststream._internal.application.Application.start", new_callable=AsyncMock + ): + await app._startup() + + assert app.running + + +@pytest.mark.asyncio() +async def test_state_stopped(app: FastStream) -> None: + with ( + patch( + "faststream._internal.application.Application.start", new_callable=AsyncMock + ), + patch( + "faststream._internal.application.Application.stop", new_callable=AsyncMock + ), + ): + await app._startup() + await app._shutdown() + + assert not app.running