Skip to content

Commit 94a7eb1

Browse files
committed
general code refactor and templating rendering fix
1 parent d0c0242 commit 94a7eb1

File tree

10 files changed

+102
-19
lines changed

10 files changed

+102
-19
lines changed

ellar/app/factory.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pathlib import Path
44

55
import click
6-
from ellar.common import Module
6+
from ellar.common import IApplicationReady, Module
77
from ellar.common.constants import MODULE_METADATA, MODULE_WATERMARK
88
from ellar.common.models import GuardCanActivate
99
from ellar.core import Config, DynamicModule, LazyModuleImport, ModuleBase, ModuleSetup
@@ -174,6 +174,13 @@ def _get_config_kwargs() -> t.Dict:
174174

175175
# app.setup_jinja_environment
176176
app.setup_jinja_environment()
177+
178+
for module, module_ref in app.injector.get_modules().items():
179+
module_ref.run_module_register_services()
180+
181+
if issubclass(module, IApplicationReady):
182+
app.injector.get(module).on_ready(app)
183+
177184
return app
178185

179186
@classmethod

ellar/app/lifespan.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ def __init__(
3030
)
3131

3232
def _get_startup_modules(self, app: "App") -> t.Iterator[IApplicationStartup]:
33-
for module, module_ref in app.injector.get_modules().items():
34-
module_ref.run_module_register_services()
33+
for module in app.injector.get_modules():
3534
if issubclass(module, IApplicationStartup):
3635
yield app.injector.get(module)
3736

ellar/common/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
WebSocketException,
3131
)
3232
from .interfaces import (
33+
IApplicationReady,
3334
IApplicationShutdown,
3435
IApplicationStartup,
3536
IExceptionHandler,
@@ -185,6 +186,7 @@
185186
"IApplicationStartup",
186187
"IApplicationShutdown",
187188
"IIdentitySchemes",
189+
"IApplicationReady",
188190
]
189191

190192

ellar/common/interfaces/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .application import IApplicationShutdown, IApplicationStartup
1+
from .application import IApplicationReady, IApplicationShutdown, IApplicationStartup
22
from .context import (
33
IExecutionContext,
44
IExecutionContextFactory,
@@ -41,4 +41,5 @@
4141
"IAPIVersioningResolver",
4242
"IEllarMiddleware",
4343
"IIdentitySchemes",
44+
"IApplicationReady",
4445
]

ellar/common/interfaces/application.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,30 @@
66

77

88
class IApplicationStartup:
9+
"""
10+
Module Interface called during application LifeSpan starts up
11+
"""
12+
913
@abstractmethod
1014
async def on_startup(self, app: "App") -> None:
1115
"""Application Startup Actions"""
1216

1317

1418
class IApplicationShutdown:
19+
"""
20+
Module Interface called during application LifeSpan shutdown
21+
"""
22+
1523
@abstractmethod
1624
async def on_shutdown(self) -> None:
1725
"""Application Shutdown Actions"""
26+
27+
28+
class IApplicationReady:
29+
"""
30+
Module Interface called when all modules have been registered just before lifespan execution starts
31+
"""
32+
33+
@abstractmethod
34+
async def on_ready(self, app: "App") -> None:
35+
"""Application Shutdown Actions"""

ellar/common/params/resolvers/bulk_parameter.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,14 @@ async def resolve_grouped_fields(
8282
request_logger.debug(
8383
f"Resolving Form Grouped Field - '{self.__class__.__name__}'"
8484
)
85-
value, resolver_errors = await self._get_resolver_data(ctx, body, by_alias=True)
85+
values, resolver_errors = await self._get_resolver_data(
86+
ctx, body, by_alias=True
87+
)
8688
if resolver_errors:
87-
return value, resolver_errors
89+
return values, resolver_errors
8890
# Combining resolved values into one pydantic model specified by the user in Route function parameter
8991
processed_value, processed_errors = self.model_field.validate(
90-
value,
92+
values,
9193
{},
9294
loc=(self.model_field.field_info.in_.value, self.model_field.alias),
9395
)
@@ -148,4 +150,4 @@ async def resolve_handle(
148150
return values, self.validate_error_sequence(errors)
149151

150152
_, body_value = values.popitem()
151-
return body_value.dict(), []
153+
return {k: getattr(body_value, k) for k in body_value.model_fields_set}, []

ellar/common/templating/renderer.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,32 @@ def render_template_string(
4141
:param template_string: Template String
4242
:param template_context: variables that should be available in the context of the template.
4343
"""
44-
jinja_environment = request.service_provider.get(Environment)
45-
jinja_template = jinja_environment.from_string(template_string)
44+
try:
45+
jinja_template, template_context_ = _get_jinja_and_template_context(
46+
template_name=template_string,
47+
request=request,
48+
**process_view_model(template_context),
49+
)
50+
return jinja_template.render(template_context_)
51+
except jinja2.TemplateNotFound:
52+
jinja_environment = request.service_provider.get(Environment)
53+
jinja_template = jinja_environment.from_string(template_string)
4654

47-
_template_context = dict(template_context)
48-
_template_context.update(request=request)
55+
_template_context = dict(template_context)
56+
_template_context.update(request=request)
4957

50-
return jinja_template.render(_template_context) # type:ignore[no-any-return]
58+
return jinja_template.render(_template_context)
5159

5260

5361
def render_template(
5462
template_name: str,
5563
request: "Request",
5664
background: t.Optional[BackgroundTask] = None,
65+
status_code: int = 200,
5766
**template_kwargs: t.Any,
5867
) -> TemplateResponse:
5968
"""Renders a template from the template folder with the given context.
69+
:param status_code: Template Response status code
6070
:param request: Request instance
6171
:param template_name: the name of the template to be rendered
6272
:param template_kwargs: variables that should be available in the context of the template.
@@ -69,5 +79,8 @@ def render_template(
6979
**process_view_model(template_kwargs),
7080
)
7181
return TemplateResponse(
72-
template=jinja_template, context=template_context, background=background
82+
template=jinja_template,
83+
context=template_context,
84+
background=background,
85+
status_code=status_code,
7386
)

ellar/testing/module.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,14 @@ def override_provider(
5050
def create_application(self) -> App:
5151
if self._app:
5252
return self._app
53+
5354
self._app = AppFactory.create_app(
5455
modules=[self._testing_module],
5556
global_guards=self._global_guards,
5657
config_module=self._config_module,
5758
providers=self._providers,
5859
)
5960

60-
for _, module_ref in self._app.injector.get_modules().items():
61-
# setup module just in case lifespan function will not be called during testing.
62-
module_ref.run_module_register_services()
63-
6461
return self._app
6562

6663
def get_test_client(

tests/test_modules/test_module_startup_shutdown.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
from contextlib import asynccontextmanager
22

3-
from ellar.common import IApplicationShutdown, IApplicationStartup, Module, ModuleRouter
3+
from ellar.common import (
4+
IApplicationReady,
5+
IApplicationShutdown,
6+
IApplicationStartup,
7+
Module,
8+
ModuleRouter,
9+
)
410
from ellar.testing import Test
511

612
router = ModuleRouter("")
@@ -29,6 +35,15 @@ async def on_shutdown(self) -> None:
2935
self.on_shutdown_called = True
3036

3137

38+
@Module()
39+
class OnApplicationReadyModule(IApplicationReady):
40+
def __init__(self):
41+
self.on_ready_called = False
42+
43+
def on_ready(self, app) -> None:
44+
self.on_ready_called = True
45+
46+
3247
@asynccontextmanager
3348
async def _testing_life_span(app):
3449
yield {"life_span_testing": "Ellar"}
@@ -40,6 +55,15 @@ async def _testing_life_span(app):
4055
)
4156

4257

58+
def test_on_ready_works():
59+
_tm = Test.create_test_module(
60+
modules=[OnApplicationReadyModule],
61+
config_module={"DEFAULT_LIFESPAN_HANDLER": _testing_life_span},
62+
)
63+
on_ready_module_instance = _tm.get(OnApplicationReadyModule)
64+
assert on_ready_module_instance.on_ready_called
65+
66+
4367
def test_on_startup_and_on_shutdown_works():
4468
client = tm.get_test_client()
4569
on_shutdown_module_instance = tm.get(OnShutdownModule)

tests/test_templating/test_renderer.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ def another_index(self, first_name: str, last_name: str):
4040
last_name=last_name,
4141
)
4242

43+
@get("/render-as-string")
44+
def render_template_as_string(self):
45+
"""Render template as string with template name"""
46+
return render_template_string(
47+
request=self.context.switch_to_http_connection().get_request(),
48+
template_string="index.html",
49+
)
50+
4351

4452
@Controller(prefix="/template-static")
4553
class TemplateWithStaticsController(ControllerBase):
@@ -74,6 +82,18 @@ def test_render_template():
7482
)
7583

7684

85+
def test_render_template_as_string_from_template_name():
86+
client = tm.get_test_client()
87+
88+
response = client.get("/ellar/render-as-string")
89+
assert response.status_code == 200
90+
91+
content = re.sub("\\s+", " ", response.text).strip()
92+
assert (
93+
"<title>Index Page</title>\\n</head>\\n<body>\\n\\n</body>\\n</html>" in content
94+
)
95+
96+
7797
def test_ellar_controller_index():
7898
client = tm.get_test_client()
7999
response = client.get("/ellar/index")

0 commit comments

Comments
 (0)