-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
154 changed files
with
890 additions
and
1,415 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,104 +1,29 @@ | ||
## Dependency injection | ||
|
||
Dependency injection is a pattern when a function receives other functions that it requires, | ||
instead of creating them internally. | ||
In frontik implementation, dependencies are simple functions, | ||
which run after `RequestHandler.prepare` and before* handler code is executed. | ||
Dependencies are great for running common actions before actual request processing takes place. | ||
|
||
Here is what a dependencies may look like: | ||
|
||
```python | ||
from frontik.dependency_manager import dependency | ||
|
||
|
||
async def get_session_dependency(handler: PageHandler) -> Session: | ||
token = handler.get_cookie('token') | ||
return await session_client.get_session(token) | ||
|
||
|
||
class Page(PageHandler): | ||
# Can be used on class level | ||
dependencies = (another_dependency,) | ||
|
||
async def get_page(self, session=dependency(get_session_dependency)): | ||
self.json.put({'result': session}) | ||
``` | ||
|
||
If you have several dependencies without results, you can put them all to one dependency marker | ||
```python | ||
from frontik.dependency_manager import dependency | ||
|
||
|
||
async def check_host(handler: PageHandler) -> None: | ||
if handler.request.host != 'example': | ||
raise HttpError() | ||
|
||
|
||
async def check_session(session=dependency(get_session_dependency)) -> None: | ||
if session.role != 'admin': | ||
raise HttpError() | ||
|
||
|
||
class Page(PageHandler): | ||
async def get_page(self, _=dependency(check_host, check_session)): | ||
... | ||
``` | ||
|
||
Dependency can be sync or async functions. When page is executed all ready to run | ||
async dependencies run in parallel with asyncio.gather(). If something finishes the page | ||
(call self.finish() or raise Exception), then we stop executing the remaining dependencies | ||
|
||
Dependencies can depend on another dependencies, thus we have a dependency graph. | ||
Within one execution of a graph, the same dependencies will be executed once. | ||
Sameness is determined by {function.__module__}.{function.__name__} | ||
Dependencies can come from factories, then it turns out that there are several different dependencies | ||
with the same name. In this case the one that is specified explicitly in the method arg or | ||
in class level will be taken, the rest from the graph depths will be discarded | ||
|
||
|
||
There is an opportunity to specify priorities for dependencies: | ||
We are in the process of migrating to fastapi. But, we still use the server and routing from tornado. | ||
So, in order to specify dependencies for handler, you should pass dependencies to router decorator. | ||
There is special dependency `get_current_handler` for getting PageHandler object | ||
|
||
```python | ||
from frontik.dependency_manager import dependency | ||
|
||
from frontik.dependency_manager import APIRouter | ||
from fastapi import Depends | ||
from frontik.handler import PageHandler, get_current_handler | ||
|
||
async def get_session_dependency(handler: PageHandler) -> Session: | ||
token = handler.get_cookie('token') | ||
return await session_client.get_session(token) | ||
router = APIRouter(dependencies=[Depends(some_dependency_function)]) | ||
|
||
|
||
class Page(PageHandler): | ||
# Can be used on class level | ||
dependencies = (another_dependency,) | ||
_priority_dependency_names: list[str] = [ | ||
side_dependency, | ||
another_dependency, | ||
] | ||
|
||
async def get_page(self, session=dependency(get_session_dependency)): | ||
self.json.put({'result': session}) | ||
``` | ||
If any of the _priority_dependency_names are present in the current graph, | ||
they will be executed before all the other dependencies sequentially. | ||
In the given example `another_dependency` -> `get_session_dependency` -> `get_page` | ||
|
||
|
||
*It is also possible to specify "async" dependencies: | ||
|
||
```python | ||
from frontik.dependency_manager import dependency, async_dependencies | ||
|
||
|
||
async def get_session_dependency(handler: PageHandler) -> Session: | ||
async def get_session_dependency(handler: PageHandler = Depends(get_current_handler)): | ||
token = handler.get_cookie('token') | ||
return await session_client.get_session(token) | ||
await session_client.get_session(token) | ||
|
||
|
||
class Page(PageHandler): | ||
@async_dependencies([get_session_dependency]) | ||
@router.get(dependencies=[Depends(get_session_dependency)]) | ||
async def get_page(self): | ||
self.json.put({'result': 'done'}) | ||
... | ||
``` | ||
The passed list will not block the execution of the page_method, so they can be executed in parallel | ||
|
||
If you don’t need to use special dependencies at the router level, then you can use router from the frontik.handler | ||
```py | ||
from frontik.handler import router | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from typing import Any, Callable | ||
|
||
from fastapi import APIRouter as FastApiRouter | ||
from fastapi import Request | ||
from fastapi.dependencies.utils import solve_dependencies | ||
|
||
|
||
class APIRouter(FastApiRouter): | ||
def get(self, **kwargs) -> Callable: # type: ignore | ||
return super().get('', **kwargs) | ||
|
||
def post(self, **kwargs) -> Callable: # type: ignore | ||
return super().post('', **kwargs) | ||
|
||
def put(self, **kwargs) -> Callable: # type: ignore | ||
return super().put('', **kwargs) | ||
|
||
def delete(self, **kwargs) -> Callable: # type: ignore | ||
return super().delete('', **kwargs) | ||
|
||
def api_route(self, *args, **kwargs): | ||
decorator = super().api_route(*args, **kwargs) | ||
|
||
def frontik_decorator(func): | ||
decorator(func) | ||
func._route = self.routes[-1] | ||
route_method = func._route.methods # type: ignore | ||
|
||
if func.__name__ in ('get_page', 'post_page', 'put_page', 'delete_page') and route_method != { | ||
func.__name__.split('_')[0].upper(), | ||
}: | ||
raise RuntimeError(f'Wrong router type func={func.__name__} method={route_method}') | ||
return func | ||
|
||
return frontik_decorator | ||
|
||
|
||
async def execute_page_method_with_dependencies(handler: Any, get_page_method: Callable) -> Any: | ||
request = Request({ | ||
'type': 'http', | ||
'query_string': '', | ||
'headers': '', | ||
'handler': handler, | ||
}) | ||
|
||
route = get_page_method._route # type: ignore | ||
|
||
await solve_dependencies( | ||
request=request, | ||
dependant=route.dependant, | ||
body=None, | ||
dependency_overrides_provider=None, | ||
) | ||
|
||
return await get_page_method() |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.