Skip to content

Commit

Permalink
release: 2.4.0 (#61)
Browse files Browse the repository at this point in the history
* Add `fast_depends.shema.get_schema` method to generate wrapped function payload (close #6)
* Improve ForwardRef resolution
* Update copyrights
* Update Readme and docs to close #59
  • Loading branch information
Lancetnik authored Jan 15, 2024
1 parent 3aa86e0 commit 9e44f6f
Show file tree
Hide file tree
Showing 22 changed files with 844 additions and 113 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2023 Pastukhov Nikita
Copyright (c) 2024 Pastukhov Nikita

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,60 @@ These are two main defferences from native FastAPI DI System.

---

### Dependencies Overriding

Also, **FastDepends** can be used as a lightweight DI container. Using it, you can easily override basic dependencies with application startup or in tests.

```python
from typing import Annotated

from fast_depends import Depends, dependency_provider, inject

def abc_func() -> int:
raise NotImplementedError()

def real_func() -> int:
return 1

@inject
def func(
dependency: Annotated[int, Depends(abc_func)]
) -> int:
return dependency

with dependency_provider.scope(abc_func, real_func):
assert func() == 1
```

`dependency_provider` in this case is just a default container already declared in the library. But you can use your own the same way:

```python
from typing import Annotated

from fast_depends import Depends, Provider, inject

provider = Provider()

def abc_func() -> int:
raise NotImplementedError()

def real_func() -> int:
return 1

@inject(dependency_overrides_provider=provider)
def func(
dependency: Annotated[int, Depends(abc_func)]
) -> int:
return dependency

with provider.scope(abc_func, real_func):
assert func() == 1
```

This way you can inherit the basic `Provider` class and define any extra logic you want!

---

### 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.
Expand Down
9 changes: 3 additions & 6 deletions docs/docs/alternatives.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
# Some more featured DI python libraries

`FastDepend` is a very small toolkit to achieve one point: provide you opportunity
to use **FastAPI** `Depends` and typecasting everywhere.
`FastDepend` is a very small toolkit to achieve one point: provide you with opportunity to use **FastAPI** `Depends` and typecasting everywhere.

Sometimes, more complex tools are required. In these cases I can reccomend you to take a look at
the following projects
Sometimes, more complex tools are required. In these cases I can reccomend you to take a look at the following projects

## [DI](https://adriangb.com/di/)

`di` is a modern dependency injection toolkit, modeled around the simplicity of FastAPI's
dependency injection.
`di` is a modern dependency injection toolkit, modeled around the simplicity of **FastAPI**'s dependency injection.

Key features:

Expand Down
54 changes: 54 additions & 0 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,60 @@ You can use this library without any frameworks in both **sync** and **async** c

These are two main defferences from native FastAPI DI System.

## Dependencies Overriding

Also, **FastDepends** can be used as a lightweight DI container. Using it, you can easily override basic dependencies with application startup or in tests.

```python
from typing import Annotated

from fast_depends import Depends, dependency_provider, inject

def abc_func() -> int:
raise NotImplementedError()

def real_func() -> int:
return 1

@inject
def func(
dependency: Annotated[int, Depends(abc_func)]
) -> int:
return dependency

with dependency_provider.scope(abc_func, real_func):
assert func() == 1
```

`dependency_provider` in this case is just a default container already declared in the library. But you can use your own the same way:

```python
from typing import Annotated

from fast_depends import Depends, Provider, inject

provider = Provider()

def abc_func() -> int:
raise NotImplementedError()

def real_func() -> int:
return 1

@inject(dependency_overrides_provider=provider)
def func(
dependency: Annotated[int, Depends(abc_func)]
) -> int:
return dependency

with provider.scope(abc_func, real_func):
assert func() == 1
```

This way you can inherit the basic `Provider` class and define any extra logic you want!

---

## 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.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/tutorial/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ just declare `Depends` requirement at original dependency function.

!!! Tip "Cache"
At the examples above `another_dependency` was called **AT ONCE!**.
`FastDepends` cashes all dependecies responses throw **ONE** `@inject` callstask.
`FastDepends` caches all dependecies responses throw **ONE** `@inject` callstask.
It means, that all nested dependencies give a one-time cached response. But,
with different injected function calls, cache will differ too.

Expand Down
6 changes: 3 additions & 3 deletions docs/docs/tutorial/overrides.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ To override a dependency for testing, you put as a key the original dependency (

And then **FastDepends** will call that override instead of the original dependency.

```python hl_lines="4 7 9 15 18" linenums="1"
```python hl_lines="4 7 10 13 18" linenums="1"
{!> docs_src/tutorial_5_overrides/example.py !}
```

Expand All @@ -39,8 +39,8 @@ And then **FastDepends** will call that override instead of the original depende

So, if you don't wish to override dependency everywhere, I extremely recommend to use the following fixture for your tests

```python linenums="1" hl_lines="7-10"
```python linenums="1" hl_lines="18-21"
{!> docs_src/tutorial_5_overrides/fixture.py !}
```

1. Drop all overridings
1. Drop all overridings
14 changes: 7 additions & 7 deletions docs/docs_src/tutorial_5_overrides/example.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
from fast_depends import Depends, inject, dependency_provider

def original_dependency():
return 1
raise NotImplementedError()

def override_dependency():
return 2
return 1

@inject
def func(d = Depends(original_dependency)):
return d

dependency_provider.override(original_dependency, override_dependency)
# or
dependency_provider.dependency_overrides[original_dependency] = override_dependency

def test():
@inject
def func(d = Depends(original_dependency)):
return d

assert func() == 2
assert func() == 1
35 changes: 14 additions & 21 deletions docs/docs_src/tutorial_5_overrides/fixture.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
from unittest.mock import Mock

import pytest
from fast_depends import dependency_provider, inject, Depends

# Base code

def base_dep():
return 1

def override_dep():
return 2

@inject
def func(d = Depends(base_dep)):
return d

# Tests

@pytest.fixture
def provider():
yield dependency_provider
dependency_provider.clear() # (1)!

def test_sync_overide(provider):
mock = Mock()

def base_dep():
mock.original()
return 1

def override_dep():
mock.override()
return 2

provider.override(base_dep, override_dep)

@inject
def func(d = Depends(base_dep)):
assert d == 2

func()

mock.override.assert_called_once()
assert not mock.original.called
assert func() == 2
11 changes: 1 addition & 10 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ repo_name: lancetnik/fastdepends
repo_url: https://github.com/lancetnik/FastDepends
edit_uri: https://github.com/lancetnik/FastDepends

copyright: Copyright © 2019 - 2023 Pastukhov Nikita
copyright: Copyright © 2023 - 2024 Pastukhov Nikita

docs_dir: docs

Expand Down Expand Up @@ -41,8 +41,6 @@ theme:
- content.tabs.link
- content.code.copy
- content.code.annotate
- navigation.top
- navigation.footer
i18n:
prev: 'Previous'
next: 'Next'
Expand All @@ -53,13 +51,6 @@ plugins:
- search
- markdownextradata:
data: data
- minify:
minify_html: true
minify_js: true
minify_css: true
htmlmin_opts:
remove_comments: true
cache_safe: true

markdown_extensions: # do not reorder
- toc:
Expand Down
2 changes: 1 addition & 1 deletion fast_depends/__about__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""FastDepends - extracted and cleared from HTTP domain FastAPI Dependency Injection System"""

__version__ = "2.3.1"
__version__ = "2.4.0b0"
3 changes: 2 additions & 1 deletion fast_depends/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from fast_depends.dependencies import dependency_provider
from fast_depends.dependencies import Provider, dependency_provider
from fast_depends.use import Depends, inject

__all__ = (
"Depends",
"dependency_provider",
"Provider",
"inject",
)
20 changes: 11 additions & 9 deletions fast_depends/_compat.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import sys
from importlib.metadata import version as get_version
from typing import Any, Dict, Optional, Type
from typing import Any, Dict, Optional, Tuple, Type

from pydantic import BaseModel, create_model
from pydantic.version import VERSION as PYDANTIC_VERSION

__all__ = (
"BaseModel",
"FieldInfo",
"create_model",
"evaluate_forwardref",
"PYDANTIC_V2",
"get_config_base",
"get_model_fields",
"ConfigDict",
"ExceptionGroup",
)
Expand All @@ -29,29 +27,33 @@
from pydantic._internal._typing_extra import ( # type: ignore[no-redef]
eval_type_lenient as evaluate_forwardref,
)
from pydantic.fields import FieldInfo

def model_schema(model: Type[BaseModel]) -> Dict[str, Any]:
return model.model_json_schema()

def get_config_base(config_data: Optional[ConfigDict] = None) -> ConfigDict:
return config_data or ConfigDict(**default_pydantic_config) # type: ignore[typeddict-item]

def get_model_fields(model: Type[BaseModel]) -> Dict[str, FieldInfo]:
return model.model_fields
def get_aliases(model: Type[BaseModel]) -> Tuple[str, ...]:
return tuple(f.alias or name for name, f in model.model_fields.items())

class CreateBaseModel(BaseModel):
"""Just to support FastStream < 0.3.7."""

model_config = ConfigDict(arbitrary_types_allowed=True)

else:
from pydantic.fields import ModelField as FieldInfo # type: ignore
from pydantic.typing import evaluate_forwardref as evaluate_forwardref # type: ignore[no-redef]
from pydantic.config import get_config, ConfigDict, BaseConfig

def get_config_base(config_data: Optional[ConfigDict] = None) -> Type[BaseConfig]: # type: ignore[misc]
return get_config(config_data or ConfigDict(**default_pydantic_config)) # type: ignore[typeddict-item]

def get_model_fields(model: Type[BaseModel]) -> Dict[str, FieldInfo]:
return model.__fields__ # type: ignore[return-value]
def model_schema(model: Type[BaseModel]) -> Dict[str, Any]:
return model.schema()

def get_aliases(model: Type[BaseModel]) -> Tuple[str, ...]:
return tuple(f.alias or name for name, f in model.__fields__.items())

class CreateBaseModel(BaseModel): # type: ignore[no-redef]
"""Just to support FastStream < 0.3.7."""
Expand Down
11 changes: 5 additions & 6 deletions fast_depends/core/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def build_call_model(
return_annotation = return_args[0]

class_fields: Dict[str, Tuple[Any, Any]] = {}
dependencies: Dict[str, "CallModel[..., Any]"] = {}
dependencies: Dict[str, CallModel[..., Any]] = {}
custom_fields: Dict[str, CustomField] = {}
positional_args: List[str] = []
keyword_args: List[str] = []
Expand Down Expand Up @@ -178,20 +178,19 @@ def build_call_model(
**class_fields,
)

response_model: Optional[Type[ResponseModel[T]]]
response_model: Optional[Type[ResponseModel[T]]] = None
if cast and return_annotation and return_annotation is not inspect.Parameter.empty:
response_model = create_model( # type: ignore[assignment]
response_model = create_model(
"ResponseModel",
__config__=get_config_base(pydantic_config),
__config__=get_config_base(pydantic_config), # type: ignore[assignment]
response=(return_annotation, ...),
)
else:
response_model = None

return CallModel(
call=call,
model=func_model,
response_model=response_model,
params=class_fields,
cast=cast,
use_cache=use_cache,
is_async=is_call_async,
Expand Down
Loading

0 comments on commit 9e44f6f

Please sign in to comment.