Skip to content

Commit ec19d3b

Browse files
authored
Merge pull request #216 from python-ellar/authorized_refactor
fix: Policy Refactor
2 parents a19541f + aab73fd commit ec19d3b

File tree

34 files changed

+361
-313
lines changed

34 files changed

+361
-313
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ lint:fmt ## Run code linters
2323
ruff check ellar tests samples
2424
mypy ellar
2525

26+
ruff-fix: ## Run Ruff fixer
27+
ruff check ellar tests samples --fix --unsafe-fixes
28+
2629
fmt format:clean ## Run code formatters
2730
ruff format ellar tests samples
2831
ruff check --fix ellar tests samples

ellar/app/factory.py

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
from ellar.core.modules import ModuleRefBase
1111
from ellar.di import EllarInjector, ProviderConfig
1212
from ellar.reflect import reflect
13-
from ellar.utils import get_unique_type
13+
from ellar.threading.sync_worker import execute_async_context_manager
14+
from ellar.utils import get_name, get_unique_type
1415
from starlette.routing import BaseRoute, Host, Mount
1516

17+
from .context import ApplicationContext
1618
from .main import App
1719
from .services import EllarAppService
1820

@@ -118,13 +120,15 @@ def _build_modules(
118120
return routes
119121

120122
@classmethod
123+
@t.no_type_check
121124
def _create_app(
122125
cls,
123126
module: t.Type[t.Union[ModuleBase, t.Any]],
124127
global_guards: t.Optional[
125128
t.List[t.Union[t.Type["GuardCanActivate"], "GuardCanActivate"]]
126129
] = None,
127130
config_module: t.Union[str, t.Dict, None] = None,
131+
injector: t.Optional[EllarInjector] = None,
128132
) -> App:
129133
def _get_config_kwargs() -> t.Dict:
130134
if config_module is None:
@@ -141,45 +145,54 @@ def _get_config_kwargs() -> t.Dict:
141145

142146
config = Config(app_configured=True, **_get_config_kwargs())
143147

144-
injector = EllarInjector(auto_bind=config.INJECTOR_AUTO_BIND)
148+
injector = EllarInjector(auto_bind=config.INJECTOR_AUTO_BIND, parent=injector)
145149
injector.container.register_instance(config, concrete_type=Config)
146150

147151
service = EllarAppService(injector, config)
148152
service.register_core_services()
149153

150-
routes = cls._build_modules(app_module=module, injector=injector, config=config)
151-
152-
app = App(
153-
routes=routes,
154-
config=config,
155-
injector=injector,
156-
lifespan=config.DEFAULT_LIFESPAN_HANDLER,
157-
global_guards=global_guards,
158-
)
159-
160-
for module_config in reversed(list(injector.get_app_dependent_modules())):
161-
if injector.get_module(module_config.module): # pragma: no cover
162-
continue
154+
with execute_async_context_manager(ApplicationContext(injector)) as context:
155+
routes = cls._build_modules(
156+
app_module=module, injector=injector, config=config
157+
)
163158

164-
module_ref = module_config.configure_with_factory(
165-
config, injector.container
159+
app = App(
160+
routes=routes,
161+
config=config,
162+
injector=injector,
163+
lifespan=config.DEFAULT_LIFESPAN_HANDLER,
164+
global_guards=global_guards,
166165
)
166+
# tag application instance by ApplicationModule name
167+
context.injector.container.register_instance(app, App, tag=get_name(module))
168+
169+
for module_config in reversed(
170+
list(context.injector.get_app_dependent_modules())
171+
):
172+
if context.injector.get_module(
173+
module_config.module
174+
): # pragma: no cover
175+
continue
176+
177+
module_ref = module_config.configure_with_factory(
178+
config, context.injector.container
179+
)
167180

168-
assert isinstance(
169-
module_ref, ModuleRefBase
170-
), f"{module_config.module} is not properly configured."
181+
assert isinstance(
182+
module_ref, ModuleRefBase
183+
), f"{module_config.module} is not properly configured."
171184

172-
injector.add_module(module_ref)
173-
app.router.extend(module_ref.routes)
185+
context.injector.add_module(module_ref)
186+
app.router.extend(module_ref.routes)
174187

175-
# app.setup_jinja_environment
176-
app.setup_jinja_environment()
188+
# app.setup_jinja_environment
189+
app.setup_jinja_environment()
177190

178-
for module, module_ref in app.injector.get_modules().items():
179-
module_ref.run_module_register_services()
191+
for module, module_ref in context.injector.get_modules().items():
192+
module_ref.run_module_register_services()
180193

181-
if issubclass(module, IApplicationReady):
182-
app.injector.get(module).on_ready(app)
194+
if issubclass(module, IApplicationReady):
195+
context.injector.get(module).on_ready(app)
183196

184197
return app
185198

@@ -198,6 +211,7 @@ def create_app(
198211
] = None,
199212
commands: t.Sequence[t.Union[click.Command, click.Group, t.Any]] = (),
200213
config_module: t.Union[str, t.Dict, None] = None,
214+
injector: t.Optional[EllarInjector] = None,
201215
) -> App:
202216
module = Module(
203217
controllers=controllers,
@@ -211,11 +225,13 @@ def create_app(
211225
)
212226
app_factory_module = get_unique_type()
213227
module(app_factory_module)
214-
return cls._create_app(
228+
app = cls._create_app(
215229
module=app_factory_module,
216230
config_module=config_module,
217231
global_guards=global_guards,
232+
injector=injector,
218233
)
234+
return t.cast(App, app)
219235

220236
@classmethod
221237
def create_from_app_module(
@@ -224,8 +240,14 @@ def create_from_app_module(
224240
global_guards: t.Optional[
225241
t.List[t.Union[t.Type["GuardCanActivate"], "GuardCanActivate"]]
226242
] = None,
243+
injector: t.Optional[EllarInjector] = None,
227244
config_module: t.Union[str, t.Dict, None] = None,
228245
) -> App:
229-
return cls._create_app(
230-
module, config_module=config_module, global_guards=global_guards
246+
app = cls._create_app(
247+
module,
248+
config_module=config_module,
249+
global_guards=global_guards,
250+
injector=injector,
231251
)
252+
253+
return t.cast(App, app)

ellar/app/lifespan.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import typing as t
22
from contextlib import asynccontextmanager
33

4-
from anyio import create_task_group
54
from ellar.common import IApplicationShutdown, IApplicationStartup
65
from ellar.common.logging import logger
76

@@ -57,17 +56,15 @@ async def run_all_shutdown_actions(self, app: "App") -> None:
5756

5857
@asynccontextmanager
5958
async def lifespan(self, app: "App") -> t.AsyncIterator[t.Any]:
60-
logger.debug("Executing Modules Startup Handlers")
61-
6259
try:
63-
async with create_task_group() as tg:
64-
tg.start_soon(self.run_all_startup_actions, app)
60+
logger.debug("Executing Modules Startup Handlers")
61+
await self.run_all_startup_actions(app)
6562

6663
async with self._lifespan_context(app) as ctx: # type:ignore[attr-defined]
6764
logger.info("Application is ready.")
6865
yield ctx
6966
finally:
7067
logger.debug("Executing Modules Shutdown Handlers")
71-
async with create_task_group() as tg:
72-
tg.start_soon(self.run_all_shutdown_actions, app)
68+
await self.run_all_shutdown_actions(app)
69+
7370
logger.info("Application shutdown successfully.")

ellar/app/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,8 @@ def build_middleware_stack(self) -> ASGIApp:
201201
)
202202

203203
app = self.router
204-
for item in reversed(middleware):
205-
app = item(app=app, injector=self.injector)
204+
for cls, args, kwargs in reversed(middleware):
205+
app = cls(app, *args, **kwargs, ellar_injector=self.injector)
206206
return app
207207

208208
def application_context(self) -> ApplicationContext:

ellar/auth/__init__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
from .identity import UserIdentity
44
from .interceptor import AuthorizationInterceptor
55
from .policy import (
6-
BasePolicyHandler,
7-
BasePolicyHandlerWithRequirement,
8-
RequiredClaimsPolicy,
9-
RequiredRolePolicy,
6+
ClaimsPolicy,
7+
Policy,
8+
PolicyWithRequirement,
9+
RolePolicy,
1010
)
1111
from .services import AppIdentitySchemes, IdentityAuthenticationService
1212

@@ -16,11 +16,11 @@
1616
"Authorize",
1717
"BaseAuthenticationHandler",
1818
"CheckPolicies",
19-
"BasePolicyHandler",
20-
"BasePolicyHandlerWithRequirement",
19+
"Policy",
20+
"PolicyWithRequirement",
2121
"UserIdentity",
22-
"RequiredClaimsPolicy",
23-
"RequiredRolePolicy",
22+
"ClaimsPolicy",
23+
"RolePolicy",
2424
"AppIdentitySchemes",
2525
"IdentityAuthenticationService",
2626
"AuthenticationRequired",

ellar/auth/interceptor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from starlette import status
77

88
from .constants import POLICY_KEYS
9-
from .policy import BasePolicyHandler, PolicyType
9+
from .policy import Policy, PolicyType
1010

1111

1212
@injectable
@@ -26,7 +26,7 @@ def get_route_handler_policy(
2626

2727
def _get_policy_instance(
2828
self, context: IExecutionContext, policy: t.Union[t.Type, t.Any, str]
29-
) -> t.Union[BasePolicyHandler, t.Any]:
29+
) -> t.Union[Policy, t.Any]:
3030
if isinstance(policy, type):
3131
return context.get_service_provider().get(policy)
3232
return policy

ellar/auth/policy/__init__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
from .base import (
2-
BasePolicyHandler,
3-
BasePolicyHandlerWithRequirement,
42
DefaultRequirementType,
3+
Policy,
54
PolicyType,
5+
PolicyWithRequirement,
66
)
7-
from .common import RequiredClaimsPolicy, RequiredRolePolicy
7+
from .common import ClaimsPolicy, RolePolicy
88

99
__all__ = [
10-
"BasePolicyHandler",
11-
"BasePolicyHandlerWithRequirement",
10+
"Policy",
11+
"PolicyWithRequirement",
1212
"DefaultRequirementType",
1313
"PolicyType",
14-
"RequiredClaimsPolicy",
15-
"RequiredRolePolicy",
14+
"ClaimsPolicy",
15+
"RolePolicy",
1616
]

ellar/auth/policy/base.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class DefaultRequirementType(AttributeDict):
99
"""
1010
Stores Policy Requirement Arguments in `arg_[n]` value
1111
example:
12-
class MyPolicyHandler(BasePolicyHandlerWithRequirement):
12+
class MyPolicyHandler(PolicyWithRequirement):
1313
...
1414
1515
policy = MyPolicyHandler['req1', 'req2', 'req2']
@@ -26,40 +26,40 @@ def __init__(self, *args: t.Any) -> None:
2626
class _PolicyOperandMixin:
2727
@t.no_type_check
2828
def __and__(
29-
cls: t.Union["BasePolicyHandler", t.Type["BasePolicyHandler"]],
30-
other: t.Union["BasePolicyHandler", t.Type["BasePolicyHandler"]],
31-
) -> "BasePolicyHandler":
29+
cls: t.Union["Policy", t.Type["Policy"]],
30+
other: t.Union["Policy", t.Type["Policy"]],
31+
) -> "Policy":
3232
return _ANDPolicy(cls, other)
3333

3434
@t.no_type_check
3535
def __or__(
36-
cls: t.Union["BasePolicyHandler", t.Type["BasePolicyHandler"]],
37-
other: t.Union["BasePolicyHandler", t.Type["BasePolicyHandler"]],
38-
) -> "BasePolicyHandler":
36+
cls: t.Union["Policy", t.Type["Policy"]],
37+
other: t.Union["Policy", t.Type["Policy"]],
38+
) -> "Policy":
3939
return _ORPolicy(cls, other)
4040

4141
@t.no_type_check
4242
def __invert__(
43-
cls: t.Union["BasePolicyHandler", t.Type["BasePolicyHandler"]],
44-
) -> "BasePolicyHandler":
43+
cls: t.Union["Policy", t.Type["Policy"]],
44+
) -> "Policy":
4545
return _NOTPolicy(cls)
4646

4747

48-
class BasePolicyHandlerMetaclass(_PolicyOperandMixin, ABCMeta):
48+
class PolicyMetaclass(_PolicyOperandMixin, ABCMeta):
4949
pass
5050

5151

52-
class BasePolicyHandler(ABC, _PolicyOperandMixin, metaclass=BasePolicyHandlerMetaclass):
52+
class Policy(ABC, _PolicyOperandMixin, metaclass=PolicyMetaclass):
5353
@abstractmethod
5454
async def handle(self, context: IExecutionContext) -> bool:
5555
"""Run Policy Actions and return true or false"""
5656

5757

58-
class BasePolicyHandlerWithRequirement(
59-
BasePolicyHandler,
58+
class PolicyWithRequirement(
59+
Policy,
6060
ABC,
6161
):
62-
__requirements__: t.Dict[int, "BasePolicyHandler"] = {}
62+
__requirements__: t.Dict[int, "Policy"] = {}
6363

6464
requirement_type: t.Type = DefaultRequirementType
6565

@@ -68,7 +68,7 @@ class BasePolicyHandlerWithRequirement(
6868
async def handle(self, context: IExecutionContext, requirement: t.Any) -> bool:
6969
"""Handle Policy Action"""
7070

71-
def __class_getitem__(cls, parameters: t.Any) -> "BasePolicyHandler":
71+
def __class_getitem__(cls, parameters: t.Any) -> "Policy":
7272
_parameters = parameters if isinstance(parameters, tuple) else (parameters,)
7373
hash_id = hash(_parameters)
7474
if hash_id not in cls.__requirements__:
@@ -79,24 +79,24 @@ def __class_getitem__(cls, parameters: t.Any) -> "BasePolicyHandler":
7979

8080

8181
PolicyType = t.Union[
82-
BasePolicyHandler,
83-
t.Type[BasePolicyHandler],
84-
BasePolicyHandlerWithRequirement,
85-
t.Type[BasePolicyHandlerWithRequirement],
82+
Policy,
83+
t.Type[Policy],
84+
PolicyWithRequirement,
85+
t.Type[PolicyWithRequirement],
8686
]
8787

8888

8989
class _OperandResolversMixin:
9090
def _get_policy_object(
9191
self, context: IExecutionContext, policy: PolicyType
92-
) -> t.Union[BasePolicyHandler, BasePolicyHandlerWithRequirement]:
92+
) -> t.Union[Policy, PolicyWithRequirement]:
9393
if isinstance(policy, type):
9494
# resolve instance from EllarInjector, we assume the policy hand been decorated with @injector decorator.
9595
return context.get_service_provider().get(policy) # type: ignore[no-any-return]
9696
return policy
9797

9898

99-
class _ORPolicy(BasePolicyHandler, _OperandResolversMixin):
99+
class _ORPolicy(Policy, _OperandResolversMixin):
100100
def __init__(self, policy_1: PolicyType, policy_2: PolicyType) -> None:
101101
self._policy_1 = policy_1
102102
self._policy_2 = policy_2
@@ -111,7 +111,7 @@ async def handle(self, context: IExecutionContext) -> bool:
111111
return _policy_1_result or _policy_2_result
112112

113113

114-
class _NOTPolicy(BasePolicyHandler, _OperandResolversMixin):
114+
class _NOTPolicy(Policy, _OperandResolversMixin):
115115
def __init__(self, policy_1: PolicyType) -> None:
116116
self._policy_1 = policy_1
117117

@@ -134,7 +134,7 @@ async def handle(self, context: IExecutionContext) -> bool:
134134
return _policy_1_result and _policy_2_result
135135

136136

137-
class _PolicyHandlerWithRequirement(BasePolicyHandler, _OperandResolversMixin):
137+
class _PolicyHandlerWithRequirement(Policy, _OperandResolversMixin):
138138
def __init__(self, policy_1: PolicyType, requirement: t.Any) -> None:
139139
self._policy_1 = policy_1
140140
self.requirement = requirement

0 commit comments

Comments
 (0)