Skip to content

Commit 3061439

Browse files
committed
bug fixes and test-cov: Fixed some bugs and improved on test-cov
1 parent 203bb5d commit 3061439

File tree

7 files changed

+73
-39
lines changed

7 files changed

+73
-39
lines changed

ellar/app/context.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@
99
from ellar.di import EllarInjector
1010

1111
if t.TYPE_CHECKING:
12-
from .main import App
12+
from ellar.app.main import App
1313

1414
_application_context: ContextVar[
1515
t.Optional[t.Union["ApplicationContext", t.Any]]
1616
] = ContextVar("ellar.app.context")
17-
_application_context.set(empty)
1817

1918

2019
class ApplicationContext:
20+
"""
21+
Provides Necessary Application Properties when running Ellar CLI commands and when serving request.
22+
23+
"""
24+
2125
__slots__ = ("_injector", "_config", "_app")
2226

2327
def __init__(self, config: Config, injector: EllarInjector, app: "App") -> None:
@@ -43,14 +47,13 @@ def config(self) -> Config:
4347
return self._config
4448

4549
def __enter__(self) -> "ApplicationContext":
46-
app_context = _application_context.get()
50+
app_context = _application_context.get(empty)
4751
if app_context is empty:
4852
# If app_context exist
4953
_application_context.set(self)
5054
if current_config._wrapped is not empty: # pragma: no cover
5155
# ensure current_config is in sync with running application context.
5256
current_config._wrapped = self.config
53-
5457
app_context = self
5558
return app_context # type:ignore[return-value]
5659

@@ -72,32 +75,33 @@ def create(cls, app: "App") -> "ApplicationContext":
7275

7376

7477
def _get_current_app() -> "App":
75-
if _application_context.get() is empty:
76-
raise RuntimeError("App is not available at this scope.")
78+
app_context = _application_context.get(empty)
79+
if app_context is empty:
80+
raise RuntimeError("ApplicationContext is not available at this scope.")
7781

78-
app_context = _application_context.get()
7982
return app_context.app # type:ignore[union-attr]
8083

8184

8285
def _get_injector() -> EllarInjector:
83-
if _application_context.get() is empty:
84-
raise RuntimeError("App is not available at this scope.")
86+
app_context = _application_context.get(empty)
87+
if app_context is empty:
88+
raise RuntimeError("ApplicationContext is not available at this scope.")
8589

86-
app_context = _application_context.get()
8790
return app_context.injector # type:ignore[union-attr]
8891

8992

9093
def _get_application_config() -> Config:
91-
if _application_context.get() is empty:
94+
app_context = _application_context.get(empty)
95+
if app_context is empty:
9296
config_module = os.environ.get(ELLAR_CONFIG_MODULE)
9397
if not config_module:
9498
raise RuntimeError(
9599
"You are trying to access app config outside app context "
96100
"and %s is not specified. This may cause differences in config "
97101
"values when the app." % (ELLAR_CONFIG_MODULE,)
98102
)
103+
return Config(config_module=config_module)
99104

100-
app_context = _application_context.get()
101105
return app_context.config # type:ignore[union-attr]
102106

103107

ellar/app/lifespan.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import typing as t
22

33
from anyio import create_task_group
4-
from ellar.common import IApplicationShutdown, IApplicationStartup, logger
4+
from ellar.common import IApplicationShutdown, IApplicationStartup
5+
from ellar.common.logger import logger
56
from ellar.reflect import asynccontextmanager
67

7-
from .context import ApplicationContext
8-
9-
if t.TYPE_CHECKING: # pragma: no cover
8+
if t.TYPE_CHECKING:
109
from ellar.app import App
1110

1211
_T = t.TypeVar("_T")
@@ -53,18 +52,17 @@ async def run_all_shutdown_actions(self, app: "App") -> None:
5352

5453
@asynccontextmanager
5554
async def lifespan(self, app: "App") -> t.AsyncIterator[t.Any]:
56-
logger.logger.debug("Executing Modules Startup Handlers")
55+
logger.debug("Executing Modules Startup Handlers")
56+
logger.debug("ELLAR LOGGER CONFIGURED")
5757

58-
with ApplicationContext.create(app):
58+
async with create_task_group() as tg:
59+
tg.start_soon(self.run_all_startup_actions, app)
60+
61+
try:
62+
async with self._lifespan_context(app) as ctx: # type:ignore[attr-defined]
63+
logger.info("Application is ready.")
64+
yield ctx
65+
finally:
66+
logger.debug("Executing Modules Shutdown Handlers")
5967
async with create_task_group() as tg:
60-
tg.start_soon(self.run_all_startup_actions, app)
61-
62-
try:
63-
async with self._lifespan_context(
64-
app
65-
) as ctx: # type:ignore[attr-defined]
66-
yield ctx
67-
finally:
68-
logger.logger.debug("Executing Modules Shutdown Handlers")
69-
async with create_task_group() as tg:
70-
tg.start_soon(self.run_all_shutdown_actions, app)
68+
tg.start_soon(self.run_all_shutdown_actions, app)

ellar/app/main.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
import logging.config
33
import typing as t
44

5+
from ellar.app.context import ApplicationContext
56
from ellar.auth.handlers import AuthenticationHandlerType
67
from ellar.common import GlobalGuard, IIdentitySchemes
78
from ellar.common.compatible import cached_property
89
from ellar.common.constants import ELLAR_LOG_FMT_STRING, LOG_LEVELS
910
from ellar.common.datastructures import State, URLPath
1011
from ellar.common.interfaces import IExceptionHandler, IExceptionMiddlewareService
11-
from ellar.common.logger import logger
1212
from ellar.common.models import EllarInterceptor, GuardCanActivate
1313
from ellar.common.templating import Environment
1414
from ellar.common.types import ASGIApp, T, TReceive, TScope, TSend
@@ -106,9 +106,6 @@ def _config_logging(self) -> None:
106106
logging.getLogger("ellar").setLevel(log_level)
107107
logging.getLogger("ellar.request").setLevel(log_level)
108108

109-
logger.info(f"APP SETTINGS MODULE: {self.config.config_module}")
110-
logger.debug("ELLAR LOGGER CONFIGURED")
111-
112109
def _statics_wrapper(self) -> t.Callable:
113110
async def _statics_func_wrapper(
114111
scope: TScope, receive: TReceive, send: TSend
@@ -259,9 +256,23 @@ def build_middleware_stack(self) -> ASGIApp:
259256
app = item(app=app, injector=self.injector)
260257
return app
261258

259+
def application_context(self) -> ApplicationContext:
260+
"""
261+
Create an ApplicationContext.
262+
Use as a contextmanager block to make `current_app`, `current_injector` and `current_config` point at this application.
263+
264+
It can be used manually outside ellar cli commands or request,
265+
e.g.,
266+
with app.application_context():
267+
assert current_app is app
268+
run_some_actions()
269+
"""
270+
return ApplicationContext.create(app=self)
271+
262272
async def __call__(self, scope: TScope, receive: TReceive, send: TSend) -> None:
263-
scope["app"] = self
264-
await self.middleware_stack(scope, receive, send)
273+
with self.application_context() as ctx:
274+
scope["app"] = ctx.app
275+
await self.middleware_stack(scope, receive, send)
265276

266277
@property
267278
def routes(self) -> t.List[BaseRoute]:

ellar/common/datastructures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,4 @@ def __get_validators__(
8989
def validate(cls: t.Type["UploadFile"], v: t.Any) -> t.Any:
9090
if not isinstance(v, StarletteUploadFile):
9191
raise ValueError(f"Expected UploadFile, received: {type(v)}")
92-
return v
92+
return cls(v.file, size=v.size, filename=v.filename, headers=v.headers)

ellar/socket_io/context.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ def get_args(self) -> t.Tuple[TScope, TReceive, TSend]: # pragma: no cover
5656
return self._context.get_args()
5757

5858
@property
59-
def user(self) -> Identity:
59+
def user(self) -> Identity: # pragma: no cover
6060
return self._context.user
6161

6262
@user.setter
63-
def user(self, value: Identity) -> None:
63+
def user(self, value: Identity) -> None: # pragma: no cover
6464
self._context.user = value

examples/04-auth-with-handlers/auth_project_with_handler/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Default Ellar Configurations are exposed here through `ConfigDefaultTypesMixin`
44
Make changes and define your own configurations specific to your application
55
6-
export ELLAR_CONFIG_MODULE=auth_project.config:DevelopmentConfig
6+
export ELLAR_CONFIG_MODULE=auth_project_with_handler.config:DevelopmentConfig
77
"""
88

99
import typing as t
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import pytest
2+
from ellar.app import AppFactory
3+
from ellar.common import IExecutionContext, get
4+
from starlette.exceptions import HTTPException
5+
from starlette.responses import Response
6+
7+
8+
def test_exception_after_response_sent(test_client_factory):
9+
@get()
10+
async def home(ctx: IExecutionContext):
11+
response = Response(b"", status_code=204)
12+
await response(*ctx.get_args())
13+
raise HTTPException(status_code=406)
14+
# raise RuntimeError("Something went wrong")
15+
16+
app = AppFactory.create_app()
17+
app.router.append(home)
18+
19+
client = test_client_factory(app)
20+
with pytest.raises(RuntimeError):
21+
client.get("/")

0 commit comments

Comments
 (0)