Skip to content

Commit

Permalink
allow import strings for Pluggables
Browse files Browse the repository at this point in the history
Changes:

- allow import strings for Pluggable
- allow passing just import strings for extensions
  • Loading branch information
devkral committed Dec 18, 2024
1 parent 9d3f976 commit 722a913
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 12 deletions.
6 changes: 3 additions & 3 deletions docs/en/docs/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ starting the system.

It is this simple but is it the only way to add a pluggable into the system? **Short answser is no**.

More details about this in [hooking a pluggable into the application](#hooking-pluggables).
More details about this in [hooking a pluggable into the application](#hooking-pluggables-and-extensions).

## Extension

Expand Down Expand Up @@ -174,7 +174,7 @@ And simply start the application.

```shell
ESMERALD_SETTINGS_MODULE=AppSettings uvicorn src:app --reload

INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
Expand All @@ -186,7 +186,7 @@ And simply start the application.

```shell
$env:ESMERALD_SETTINGS_MODULE="AppSettings"; uvicorn src:app --reload

INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
Expand Down
6 changes: 5 additions & 1 deletion docs_src/pluggables/pluggable.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ def extend(self, config: PluggableConfig) -> None:

pluggable = Pluggable(MyExtension, config=my_config)

app = Esmerald(routes=[], extensions={"my-extension": pluggable})
# it is also possible to just pass strings instead of pluggables but this way you lose the ability to pass arguments
app = Esmerald(
routes=[],
extensions={"my-extension": pluggable, "my-other-extension": Pluggable("path.to.extension")},
)
4 changes: 2 additions & 2 deletions esmerald/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -1348,7 +1348,7 @@ async def home() -> Dict[str, str]:
),
] = None,
extensions: Annotated[
Optional[Dict[str, Union[Extension, Pluggable, type[Extension]]]],
Optional[Dict[str, Union[Extension, Pluggable, type[Extension], str]]],
Doc(
"""
A `list` of global extensions from objects inheriting from
Expand Down Expand Up @@ -1395,7 +1395,7 @@ def extend(self, config: PluggableConfig) -> None:
),
] = None,
pluggables: Annotated[
Optional[Dict[str, Union[Extension, Pluggable, type[Extension]]]],
Optional[Dict[str, Union[Extension, Pluggable, type[Extension], str]]],
Doc(
"""
THIS PARAMETER IS DEPRECATED USE extensions INSTEAD
Expand Down
18 changes: 15 additions & 3 deletions esmerald/pluggables/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from abc import ABC, abstractmethod
from inspect import isclass
from typing import TYPE_CHECKING, Any, Iterator, Optional
from typing import TYPE_CHECKING, Any, Iterator, Optional, cast

from lilya._internal._module_loading import import_string
from typing_extensions import Annotated, Doc

from esmerald.exceptions import ImproperlyConfigured
Expand Down Expand Up @@ -49,15 +50,24 @@ def extend(self, config: PluggableConfig) -> None:
my_config = PluggableConfig(name="my extension")
pluggable = Pluggable(MyExtension, config=my_config)
# or
# pluggable = Pluggable("path.to.MyExtension", config=my_config)
app = Esmerald(routes=[], extensions={"my-extension": pluggable})
```
"""

def __init__(self, cls: type["ExtensionProtocol"], **options: Any):
self.cls = cls
def __init__(self, cls: type["ExtensionProtocol"] | str, **options: Any):
self.cls_or_string = cls
self.options = options

@property
def cls(self) -> type["ExtensionProtocol"]:
cls_or_string = self.cls_or_string
if isinstance(cls_or_string, str):
self.cls_or_string = cls_or_string = import_string(cls_or_string)
return cast(type["ExtensionProtocol"], cls_or_string)

def __iter__(self) -> Iterator:
iterator = (self.cls, self.options)
return iter(iterator)
Expand Down Expand Up @@ -167,6 +177,8 @@ def extend(self, **kwargs: "DictAny") -> None:
self[name].extend(**val)

def __setitem__(self, name: Any, value: Any) -> None:
if isinstance(value, str):
value = Pluggable(value)
if not isinstance(name, str):
raise ImproperlyConfigured("Extension names should be in string format.")
elif isinstance(value, Pluggable):
Expand Down
8 changes: 6 additions & 2 deletions esmerald/testclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,12 @@ def create_client(
backend: "Literal['asyncio', 'trio']" = "asyncio",
backend_options: Optional[Dict[str, Any]] = None,
interceptors: Optional[List["Interceptor"]] = None,
pluggables: Optional[Dict[str, Union["Extension", "Pluggable", type["Extension"]]]] = None,
extensions: Optional[Dict[str, Union["Extension", "Pluggable", type["Extension"]]]] = None,
pluggables: Optional[
Dict[str, Union["Extension", "Pluggable", type["Extension"], str]]
] = None,
extensions: Optional[
Dict[str, Union["Extension", "Pluggable", type["Extension"], str]]
] = None,
permissions: Optional[List["Permission"]] = None,
dependencies: Optional["Dependencies"] = None,
middleware: Optional[List["Middleware"]] = None,
Expand Down
8 changes: 8 additions & 0 deletions tests/pluggables/import_target.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from loguru import logger

from esmerald import Extension


class MyExtension2(Extension):
def extend(self) -> None:
logger.info("Started extension2")
12 changes: 11 additions & 1 deletion tests/pluggables/test_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,20 @@ def extend(self, config: Config) -> None:

def test_generates_pluggable():
app = Esmerald(
routes=[], extensions={"test": Pluggable(MyExtension, config=Config(name="my pluggable"))}
routes=[],
extensions={
"test": Pluggable(MyExtension, config=Config(name="my pluggable")),
"test2": Pluggable("tests.pluggables.import_target.MyExtension2"),
"test3": "tests.pluggables.import_target.MyExtension2",
},
)

assert "test" in app.extensions
assert isinstance(app.extensions["test"], Extension)
assert "test2" in app.extensions
assert isinstance(app.extensions["test2"], Extension)
assert "test3" in app.extensions
assert isinstance(app.extensions["test3"], Extension)


def test_generates_many_pluggables():
Expand Down

0 comments on commit 722a913

Please sign in to comment.