Skip to content

Commit

Permalink
add custom fields and advanced
Browse files Browse the repository at this point in the history
  • Loading branch information
Lancetnik committed Apr 18, 2023
1 parent d77e375 commit d46302b
Show file tree
Hide file tree
Showing 33 changed files with 606 additions and 30 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ wtf
.mypy_cache
coverage.json
site
wtf.py
wtf.py
CODE_OF_CONDUCT.md
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ assert main("1", 2) == 4.0
---

### Features

Synchronous code is fully supported in this package: without any `async_to_sync`, `run_sync`, `syncify` or any other tricks.

Also, *FastDepends* casts functions' return values the same way, it can be very helpful in building your own tools.
Expand All @@ -104,6 +105,35 @@ These are two main defferences from native Fastapi DI System.

---

### Custom Fields

If you wish to write your own FastAPI or another closely by architecture tool, you should define your own custom fields to specify application behavior.

Custom fields can be used to adding something specific to a function arguments (like a BackgroundTask) or parsing incoming objects special way. You able decide by own, why and how you will use these tools.

FastDepends grants you this opportunity a very intuitive and comfortable way.

```python
from fast_depends import inject
from fast_depends.library import CustomField

class Header(CustomField):
def use(self, **kwargs: AnyDict) -> AnyDict:
kwargs = super().use(**kwargs)
kwargs[self.param_name] = kwargs["headers"][self.param_name]
return kwargs

@inject
def my_func(header_field: int = Header()):
return header_field

assert my_func(
headers={ "header_field": "1" }
) == 1
```

---

### Note
Library was based on **0.95.0 FastAPI** version.

Expand Down
102 changes: 102 additions & 0 deletions docs/docs/advanced/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# CustomField

!!! warning "Packages developers"
This is the part of documentation will talks you about some features, that can be helpfull to develop your own packages with `FastDepends`

## Custom Arguments Field

If you wish to write your own **FastAPI** or another closely by architecture tool, you
should define your own custom fields to specify application behavior. At **FastAPI** these fields are:

* Body
* Path
* Query
* Header
* Cookie
* Form
* File
* Security

Custom fields can be used to adding something specific to a function arguments (like a *BackgroundTask*) or
parsing incoming objects special way. You able decide by own, why and how you will use these tools.

`FastDepends` grants you this opportunity a very intuitive and comfortable way.

### Let's write *Header*

As an example, will try to implement **FastAPI** *Header* field

```python linenums="1" hl_lines="1 3-4"
{!> docs_src/advanced/custom/class_declaration.py !}
```

Just import `fast_depends.library.CustomField` and implements `use` (async or sync) method.
That's all. We already have own *Header* field to parse **kwargs** the special way.

### *Header* usage

Now we already can use the *Header* field

```python linenums="1" hl_lines="4 8"
{!> docs_src/advanced/custom/usage.py !}
```

As we defined, *Header* parse incoming **headers kwargs field**, get a parameter by name and put it to
original function as an argument.

### More details

`CustomField` has some fields you should know about

```python
class CustomField:
param_name: str
cast: bool
```

* `param_name` - an original function argument name to replace by your field instance. It was `header_field` at the example above.
* `cast` - specify the typecasting behavior. Use *False* to disable pydantic typecasting for fields using with your *CustomField*

```python linenums="1" hl_lines="3 8 12-13"
{!> docs_src/advanced/custom/cast_arg.py !}
```

!!! note
Pydantic understands only python-native annotation or Pydantic classes. If users will annotate your fields by other classes,
you should set `cast=False` to avoid pydantic exeptions.

```python
def use(self, **kwargs: AnyDict) -> AnyDict: ...
```

Your *CustimField* objects receive casted to *kwargs* an original function incoming arguments at `use` method.
Returning from the `use` method dict replace an original one. Original function will be executed **with a returned from your fields kwargs**.
Be accurate with.

And one more time:

```python linenums="1" hl_lines="6 9"
original_kwargs = { "headers": { "field": 1 }}

new_kwargs = Header().set_param_name("field").use(**kwargs)
# new_kwargs = {
# "headers": { "field": 1 },
# "field": 1 <-- new field from Header
# }

original_function(**new_kwargs)
```

I hope it was clearly enough right now.

Also, custom fields using according their definition: from left to right.
Next Custom Fields **kwargs** is a return of previous.

An example:

```python linenums="1"
@inject
def func(field1 = Header(), field2 = Header()): ...
```

**field2** incoming kwargs is an output of **field1.use()**
60 changes: 60 additions & 0 deletions docs/docs/advanced/starlette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Let's write some code

Now we take the *starlette example* from [usages](/FastDepends/usages/) and specify it to use *Path* now.

## Handle *request* specific fields

First of all, **Starlette** pass to a handler the only one argument - `request`
To use them with `FastDepends` we need unwrap `request` to kwargs.

```python hl_lines="6-8" linenums="1"
{!> docs_src/advanced/custom/starlette.py [ln:1,16-21] !}
```

!!! note ""
Also, we wraps an original handler to `fast_depends.inject` too at *3* line

## Declare *Custom Field*

Next step, define *Path* custom field

```python linenums="1" hl_lines="8"
{!> docs_src/advanced/custom/starlette.py [ln:2,8-13] !}
```

## Usage with the *Starlette*

And use it at our *Starlette* application:
```python linenums="1" hl_lines="6 7 10"
{!> docs_src/advanced/custom/starlette.py [ln:4-6,23-30] !}
```

Depends is working as expected too

```python linenums="1" hl_lines="1 4-5 9"
def get_user(user_id: int = Path()):
return f"user {user_id}"

@wrap_starlette
async def hello(user: str = Depends(get_user)):
return PlainTextResponse(f"Hello, {user}!")

app = Starlette(debug=True, routes=[
Route("/{user_id}", hello)
])
```

As an *Annotated* does
```python linenums="1" hl_lines="2"
@wrap_starlette
async def get_user(user: Annotated[int, Path()]):
return PlainTextResponse(f"Hello, {user}!")
```

## Full example

```python linenums="1"
{!> docs_src/advanced/custom/starlette.py !}
```

The code above works "as it". You can copy it and declare other *Header*, *Cookie*, *Query* fields by yourself. Just try, it's fun!
29 changes: 29 additions & 0 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,35 @@ You can use this library without any frameworks in both **sync** and **async** c

These are two main defferences from native Fastapi DI System.

## Custom Fields

If you wish to write your own FastAPI or another closely by architecture tool, you should define your own custom fields to specify application behavior.

Custom fields can be used to adding something specific to a function arguments (like a BackgroundTask) or parsing incoming objects special way. You able decide by own, why and how you will use these tools.

FastDepends grants you this opportunity a very intuitive and comfortable way.

```python
from fast_depends import inject
from fast_depends.library import CustomField

class Header(CustomField):
def use(self, **kwargs: AnyDict) -> AnyDict:
kwargs = super().use(**kwargs)
kwargs[self.param_name] = kwargs["headers"][self.param_name]
return kwargs

@inject
def my_func(header_field: int = Header()):
return header_field

assert my_func(
headers={ "header_field": "1" }
) == 1
```

More details you can find at [advanced](/FastDepends/advanced) tutorial

!!! warning ""
Library was based on **0.95.0 FastAPI** version.

Expand Down
10 changes: 5 additions & 5 deletions docs/docs/tutorial/classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
### "Callable", remember?

```python linenums="1"
{!> docs_src/tutorial_2/tutorial_1.py !}
{!> docs_src/tutorial_2_classes/tutorial_1.py !}
```

Yep, all of these examples can be used as a dependency!
Expand All @@ -16,7 +16,7 @@ You can use class initializer as a dependency. This way, object of this class
will be the type of your dependency:

```python linenums="1" hl_lines="5-6 9"
{!> docs_src/tutorial_2/tutorial_2.py !}
{!> docs_src/tutorial_2_classes/tutorial_2.py !}
```

!!! warning
Expand All @@ -32,7 +32,7 @@ If you wish to specify your dependency behavior earlier, you can use `__call__`
already inititalized class object.

```python linenums="1" hl_lines="7-8 11"
{!> docs_src/tutorial_2/tutorial_3.py !}
{!> docs_src/tutorial_2_classes/tutorial_3.py !}
```

---
Expand All @@ -43,15 +43,15 @@ Also, you can use classmethods or staticmethod as dependencies.
It can be helpful with some OOP patterns (Strategy as an example).

```python linenums="1" hl_lines="4-6 9"
{!> docs_src/tutorial_2/tutorial_4.py !}
{!> docs_src/tutorial_2_classes/tutorial_4.py !}
```

---

### ANY METHOD (4-th call)

```python linenums="1" hl_lines="7-8 11"
{!> docs_src/tutorial_2/tutorial_5.py !}
{!> docs_src/tutorial_2_classes/tutorial_5.py !}
```


Expand Down
16 changes: 8 additions & 8 deletions docs/docs/tutorial/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ But, I can remember you, if it's nessesary.

=== "Sync"
```python hl_lines="3-4" linenums="1"
{!> docs_src/tutorial_1/1_sync.py !}
{!> docs_src/tutorial_1_quickstart/1_sync.py !}
```

=== "Async"
```python hl_lines="4-5 7-8" linenums="1"
{!> docs_src/tutorial_1/1_async.py !}
{!> docs_src/tutorial_1_quickstart/1_async.py !}
```

!!! tip "Be accurate"
Expand All @@ -31,24 +31,24 @@ But, I can remember you, if it's nessesary.

=== "Sync"
```python hl_lines="2" linenums="6"
{!> docs_src/tutorial_1/1_sync.py [ln:5-8]!}
{!> docs_src/tutorial_1_quickstart/1_sync.py [ln:5-8]!}
```

=== "Async"
```python hl_lines="1 4 5" linenums="10"
{!> docs_src/tutorial_1/1_async.py [ln:10-16]!}
{!> docs_src/tutorial_1_quickstart/1_async.py [ln:10-16]!}
```

**Second step**: declare dependency required with `Depends`

=== "Sync"
```python hl_lines="3 5" linenums="6"
{!> docs_src/tutorial_1/1_sync.py [ln:5-10]!}
{!> docs_src/tutorial_1_quickstart/1_sync.py [ln:5-10]!}
```

=== "Async"
```python hl_lines="7 9" linenums="10"
{!> docs_src/tutorial_1/1_async.py [ln:10-18]!}
{!> docs_src/tutorial_1_quickstart/1_async.py [ln:10-18]!}
```

**Last step**: just use the dependencies calling result!
Expand All @@ -67,14 +67,14 @@ just declare `Depends` requirement at original dependency function.

=== "Sync"
```python linenums="1" hl_lines="3-4 6-7 12-13"
{!> docs_src/tutorial_1/2_sync.py !}
{!> docs_src/tutorial_1_quickstart/2_sync.py !}
```

1. Call another_dependency here

=== "Async"
```python linenums="1" hl_lines="4-5 7-8 13-14"
{!> docs_src/tutorial_1/2_async.py !}
{!> docs_src/tutorial_1_quickstart/2_async.py !}
```

1. Call another_dependency here
Expand Down
10 changes: 5 additions & 5 deletions docs/docs/tutorial/yield.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ For example, you could use this to create a database session and close it after

Only the code prior `yield` statement is executed before sending a response
```python linenums="1" hl_lines="1-2"
{!> docs_src/tutorial_3/tutorial_1.py !}
{!> docs_src/tutorial_3_yield/tutorial_1.py !}
```

The *yielded* value is what is injected into original function
```python linenums="1" hl_lines="3"
{!> docs_src/tutorial_3/tutorial_1.py !}
{!> docs_src/tutorial_3_yield/tutorial_1.py !}
```

The code following the `yield` statement is executed after the original function has been called
```python linenums="1" hl_lines="4"
{!> docs_src/tutorial_3/tutorial_1.py !}
{!> docs_src/tutorial_3_yield/tutorial_1.py !}
```

!!! tip
Expand All @@ -30,9 +30,9 @@ The code following the `yield` statement is executed after the original function
!!! warning
All errors occures at original function or another dependencies will be raised this place
```python linenums="1" hl_lines="3"
{!> docs_src/tutorial_3/tutorial_1.py !}
{!> docs_src/tutorial_3_yield/tutorial_1.py !}
```
To guarantee `db.close()` execution use the following code:
```python linenums="1" hl_lines="3 5"
{!> docs_src/tutorial_3/tutorial_2.py !}
{!> docs_src/tutorial_3_yield/tutorial_2.py !}
```
Loading

0 comments on commit d46302b

Please sign in to comment.