Skip to content

Commit ddc9134

Browse files
authored
Merge pull request #15 from eadwinCode/application_test_client_test
App Test
2 parents 45f2f35 + 17ba9e5 commit ddc9134

File tree

17 files changed

+680
-117
lines changed

17 files changed

+680
-117
lines changed

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,6 @@ You will see the automatic interactive API documentation (provided by <a href="h
105105

106106
## Status
107107
Project is still in development
108-
- Remaining testing modules:
109-
- configuration
110108
- Project CLI scaffolding
111109
- Documentation
112110
- Database Plugin with [Encode/ORM](https://github.com/encode/orm)

ellar/core/conf/app_settings_models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,12 @@ class Config:
6969
DEBUG: bool = False
7070
DEFAULT_JSON_CLASS: t.Type[JSONResponse] = JSONResponse
7171

72-
TEMPLATES_AUTO_RELOAD: t.Optional[bool] = None
72+
JINJA_TEMPLATES_OPTIONS: t.Dict[str, t.Any] = {}
7373
VERSIONING_SCHEME: TVersioning = Field(DefaultAPIVersioning())
7474

7575
REDIRECT_SLASHES: bool = False
7676
STATIC_FOLDER_PACKAGES: t.Optional[t.List[t.Union[str, t.Tuple[str]]]] = []
77+
STATIC_DIRECTORIES: t.Optional[t.List[t.Union[str, t.Any]]] = []
7778

7879
MIDDLEWARE: t.List[TMiddleware] = []
7980
APP_EXCEPTION_HANDLERS: t.Dict[t.Union[int, t.Type[Exception]], t.Callable] = {}

ellar/core/conf/default_settings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@
2828
# For more info, read: https://injector.readthedocs.io/en/latest/index.html
2929
INJECTOR_AUTO_BIND = False
3030

31-
TEMPLATES_AUTO_RELOAD: t.Optional[bool] = None
31+
JINJA_TEMPLATES_OPTIONS: t.Dict[str, t.Any] = {}
3232

3333
VERSIONING_SCHEME: BaseAPIVersioning = DefaultAPIVersioning()
3434
REDIRECT_SLASHES: bool = False
3535

3636
STATIC_FOLDER_PACKAGES: t.Optional[t.List[t.Union[str, t.Tuple[str]]]] = []
37+
STATIC_DIRECTORIES: t.Optional[t.List[t.Union[str, t.Any]]] = []
3738
STATIC_MOUNT_PATH: str = "/static"
3839

3940
MIDDLEWARE: t.Sequence[Middleware] = []

ellar/core/factory.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from ellar.reflect import reflect
1919

2020
if t.TYPE_CHECKING: # pragma: no cover
21+
from ellar.core import GuardCanActivate
2122
from ellar.core.routing import ModuleMount, ModuleRouter
2223

2324

@@ -65,7 +66,14 @@ def _run_module_application_ready(
6566
module_ref.run_application_ready(app)
6667

6768
@classmethod
68-
def _create_app(cls, module: t.Type[ModuleBase], config_module: str = None) -> App:
69+
def _create_app(
70+
cls,
71+
module: t.Type[ModuleBase],
72+
global_guards: t.List[
73+
t.Union[t.Type["GuardCanActivate"], "GuardCanActivate"]
74+
] = None,
75+
config_module: str = None,
76+
) -> App:
6977
assert reflect.get_metadata(MODULE_WATERMARK, module), "Only Module is allowed"
7078

7179
config = Config(app_configured=True, config_module=config_module)
@@ -84,6 +92,7 @@ def _create_app(cls, module: t.Type[ModuleBase], config_module: str = None) -> A
8492
t.Optional[t.Callable[[App], t.AsyncContextManager[t.Any]]],
8593
config.DEFAULT_LIFESPAN_HANDLER,
8694
),
95+
global_guards=global_guards,
8796
)
8897

8998
cls._run_module_application_ready(
@@ -103,6 +112,9 @@ def create_app(
103112
template_folder: t.Optional[str] = None,
104113
base_directory: t.Optional[str] = None,
105114
static_folder: str = "static",
115+
global_guards: t.List[
116+
t.Union[t.Type["GuardCanActivate"], "GuardCanActivate"]
117+
] = None,
106118
config_module: str = None,
107119
) -> App:
108120
from ellar.common import Module
@@ -119,11 +131,20 @@ def create_app(
119131
app_factory_module = type(f"Module{uuid4().hex[:6]}", (ModuleBase,), {})
120132
module(app_factory_module)
121133
return cls._create_app(
122-
t.cast(t.Type[ModuleBase], app_factory_module), config_module
134+
t.cast(t.Type[ModuleBase], app_factory_module),
135+
config_module=config_module,
136+
global_guards=global_guards,
123137
)
124138

125139
@classmethod
126140
def create_from_app_module(
127-
cls, module: t.Type[ModuleBase], config_module: str = None
141+
cls,
142+
module: t.Type[t.Union[ModuleBase, t.Any]],
143+
global_guards: t.List[
144+
t.Union[t.Type["GuardCanActivate"], "GuardCanActivate"]
145+
] = None,
146+
config_module: str = None,
128147
) -> App:
129-
return cls._create_app(module, config_module)
148+
return cls._create_app(
149+
module, config_module=config_module, global_guards=global_guards
150+
)

ellar/core/main.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from ellar.core.templating import AppTemplating, Environment
2020
from ellar.core.versioning import VERSIONING, BaseAPIVersioning
2121
from ellar.di.injector import EllarInjector
22+
from ellar.di.providers import ModuleProvider
2223
from ellar.logger import logger
2324
from ellar.services.reflector import Reflector
2425
from ellar.types import ASGIApp, T, TReceive, TScope, TSend
@@ -64,6 +65,8 @@ def __init__(
6465
else list(on_shutdown_event_handlers)
6566
)
6667

68+
self._static_app: t.Optional[ASGIApp] = None
69+
6770
self.state = State()
6871
self.config.DEFAULT_LIFESPAN_HANDLER = (
6972
lifespan or self.config.DEFAULT_LIFESPAN_HANDLER
@@ -77,14 +80,11 @@ def __init__(
7780
lifespan=self.config.DEFAULT_LIFESPAN_HANDLER, # type: ignore
7881
)
7982
self.middleware_stack = self.build_middleware_stack()
80-
81-
self._static_app: t.Optional[ASGIApp] = None
82-
8383
self._finalize_app_initialization()
8484

8585
logger.info(f"APP SETTINGS: {self._config.config_module}")
8686

87-
def statics_wrapper(self) -> t.Callable:
87+
def _statics_wrapper(self) -> t.Callable:
8888
async def _statics_func_wrapper(
8989
scope: TScope, receive: TReceive, send: TSend
9090
) -> t.Any:
@@ -100,7 +100,7 @@ def _get_module_routes(self) -> t.List[BaseRoute]:
100100
_routes.append(
101101
Mount(
102102
str(self.config.STATIC_MOUNT_PATH),
103-
app=self.statics_wrapper(),
103+
app=self._statics_wrapper(),
104104
name="static",
105105
)
106106
)
@@ -137,7 +137,9 @@ def install_module(
137137
def get_guards(self) -> t.List[t.Union[t.Type[GuardCanActivate], GuardCanActivate]]:
138138
return self._global_guards
139139

140-
def use_global_guards(self, *guards: "GuardCanActivate") -> None:
140+
def use_global_guards(
141+
self, *guards: t.Union["GuardCanActivate", t.Type["GuardCanActivate"]]
142+
) -> None:
141143
self._global_guards.extend(guards)
142144

143145
@property
@@ -204,12 +206,6 @@ def routes(self) -> t.List[BaseRoute]:
204206
def url_path_for(self, name: str, **path_params: t.Any) -> URLPath:
205207
return self.router.url_path_for(name, **path_params)
206208

207-
def mount(self, path: str, app: ASGIApp, name: str = None) -> None:
208-
self.router.mount(path, app=app, name=name)
209-
210-
def host(self, host: str, app: ASGIApp, name: str = None) -> None:
211-
self.router.host(host, app=app, name=name)
212-
213209
def enable_versioning(
214210
self,
215211
schema: VERSIONING,
@@ -228,7 +224,10 @@ def _finalize_app_initialization(self) -> None:
228224
self.injector.container.register_instance(Reflector())
229225
self.injector.container.register_instance(self.config, Config)
230226
self.injector.container.register_instance(self.jinja_environment, Environment)
231-
self.injector.container.register_scoped(IExecutionContext, ExecutionContext)
227+
self.injector.container.register_scoped(
228+
IExecutionContext,
229+
ModuleProvider(ExecutionContext, scope={}, receive=None, send=None),
230+
)
232231

233232
def add_exception_handler(
234233
self,

ellar/core/routing/router/route_collections.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def _add_operation(
3333
if hasattr(operation, "get_allowed_version")
3434
else {}
3535
)
36+
path = operation.host if isinstance(operation, Host) else operation.path # type: ignore
3637

3738
if isinstance(operation, (Mount, Host)):
3839
_methods = {
@@ -41,7 +42,7 @@ def _add_operation(
4142
}
4243

4344
_hash = generate_controller_operation_unique_id(
44-
path=operation.path, # type: ignore
45+
path=path,
4546
methods=list(_methods),
4647
versioning=_versioning or ["no_versioning"],
4748
)

ellar/core/templating/interface.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ class AppTemplating(JinjaTemplating):
135135
_static_app: t.Optional[ASGIApp]
136136
_injector: "EllarInjector"
137137
has_static_files: bool
138+
build_middleware_stack: t.Callable
138139

139140
def get_module_loaders(self) -> t.Generator[ModuleTemplating, None, None]:
140141
for loader in self._injector.get_templating_modules().values():
@@ -148,6 +149,7 @@ def debug(self) -> bool:
148149
def debug(self, value: bool) -> None:
149150
del self.__dict__["jinja_environment"]
150151
self.config.DEBUG = value
152+
self.build_middleware_stack()
151153

152154
@cached_property
153155
def jinja_environment(self) -> BaseEnvironment:
@@ -161,17 +163,15 @@ def select_jinja_auto_escape(filename: str) -> bool:
161163
return True
162164
return filename.endswith((".html", ".htm", ".xml", ".xhtml"))
163165

164-
# TODO: get extensions from configuration
165-
options: t.Dict = dict(extensions=[])
166-
167-
_auto_reload = self.config.TEMPLATES_AUTO_RELOAD
168-
_auto_reload = _auto_reload if _auto_reload is not None else self.debug
169-
170-
if "autoescape" not in options:
171-
options["autoescape"] = select_jinja_auto_escape
166+
options_defaults: t.Dict = dict(
167+
extensions=[], auto_reload=self.debug, autoescape=select_jinja_auto_escape
168+
)
169+
jinja_options: t.Dict = t.cast(
170+
t.Dict, self.config.JINJA_TEMPLATES_OPTIONS or {}
171+
)
172172

173-
if "auto_reload" not in options:
174-
options["auto_reload"] = _auto_reload
173+
for k, v in options_defaults.items():
174+
jinja_options.setdefault(k, v)
175175

176176
@pass_context
177177
def url_for(context: dict, name: str, **path_params: t.Any) -> str:
@@ -180,7 +180,7 @@ def url_for(context: dict, name: str, **path_params: t.Any) -> str:
180180

181181
app: App = t.cast("App", self)
182182

183-
jinja_env = Environment(app, **options)
183+
jinja_env = Environment(app, **jinja_options)
184184
jinja_env.globals.update(
185185
url_for=url_for,
186186
config=self.config,
@@ -208,7 +208,7 @@ def _update_jinja_env_filters(self, jinja_environment: Environment) -> None:
208208

209209
@cached_property
210210
def static_files(self) -> t.List[str]:
211-
static_directories = []
211+
static_directories = t.cast(t.List, self.config.STATIC_DIRECTORIES or [])
212212
for module in self.get_module_loaders():
213213
if module.static_directory:
214214
static_directories.append(module.static_directory)

ellar/core/testclient.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def create_test_module(
4343
template_folder: t.Optional[str] = None,
4444
base_directory: t.Optional[str] = None,
4545
static_folder: str = "static",
46+
config_module: str = None,
4647
) -> _TestingModule:
4748
app = AppFactory.create_app(
4849
controllers=controllers,
@@ -51,6 +52,7 @@ def create_test_module(
5152
template_folder=template_folder,
5253
base_directory=base_directory,
5354
static_folder=static_folder,
55+
config_module=config_module,
5456
)
5557
return _TestingModule(app=app)
5658

@@ -59,6 +61,7 @@ def create_test_module_from_module(
5961
cls,
6062
module: t.Union[t.Type, t.Type[ModuleBase]],
6163
mock_services: t.Sequence[ProviderConfig] = tuple(),
64+
config_module: str = None,
6265
) -> _TestingModule:
6366
app_module_watermark = reflect.get_metadata(MODULE_WATERMARK, module)
6467
if mock_services:
@@ -67,6 +70,12 @@ def create_test_module_from_module(
6770
)
6871

6972
if app_module_watermark:
70-
return _TestingModule(app=AppFactory.create_from_app_module(module))
71-
app = AppFactory.create_app(modules=(module,), providers=mock_services)
73+
return _TestingModule(
74+
app=AppFactory.create_from_app_module(
75+
module, config_module=config_module
76+
)
77+
)
78+
app = AppFactory.create_app(
79+
modules=(module,), providers=mock_services, config_module=config_module
80+
)
7281
return _TestingModule(app=app)

ellar/di/injector.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,29 +120,35 @@ def register(
120120
_scope = scope.scope
121121
self.register_binding(base_type, Binding(base_type, provider, _scope))
122122

123-
def register_instance(self, instance: T, concrete_type: t.Type[T] = None) -> None:
123+
def register_instance(
124+
self, instance: T, concrete_type: t.Union[t.Type[T], Provider] = None
125+
) -> None:
124126
assert not isinstance(instance, type)
125127
_concrete_type = instance.__class__ if not concrete_type else concrete_type
126128
self.register(_concrete_type, instance, scope=SingletonScope)
127129

128130
def register_singleton(
129131
self,
130132
base_type: t.Type[T],
131-
concrete_type: t.Optional[t.Union[t.Type[T], T]] = None,
133+
concrete_type: t.Union[t.Type[T], T, Provider] = None,
132134
) -> None:
133135
if not concrete_type:
134136
self.register_exact_singleton(base_type)
135137
self.register(base_type, concrete_type, scope=SingletonScope)
136138

137139
def register_transient(
138-
self, base_type: t.Type, concrete_type: t.Optional[t.Type] = None
140+
self,
141+
base_type: t.Type,
142+
concrete_type: t.Union[t.Type, Provider] = None,
139143
) -> None:
140144
if not concrete_type:
141145
self.register_exact_transient(base_type)
142146
self.register(base_type, concrete_type, scope=TransientScope)
143147

144148
def register_scoped(
145-
self, base_type: t.Type, concrete_type: t.Optional[t.Type] = None
149+
self,
150+
base_type: t.Type,
151+
concrete_type: t.Union[t.Type, Provider] = None,
146152
) -> None:
147153
if not concrete_type:
148154
self.register_exact_scoped(base_type)

tests/test_application/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)