Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rename and improve pluggables #428

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ use it.
* **Exception Handlers**: Apply exception handlers on any desired level.
* **Permissions**: Apply specific rules and permissions on each API.
* **Interceptors**: Intercept requests and add logic before reaching the endpoint.
* **Pluggables**: Create plugins for Esmerald and hook them into any application and/or
* **Extensions**: Create plugins for Esmerald and hook them into any application and/or
distribute them.
* **DAO and AsyncDAO**: Avoid database calls directly from the APIs. Use business objects instead.
* **ORM Support**: Native support for [Saffier][saffier_orm] and [Edgy][edgy_orm].
Expand Down
55 changes: 32 additions & 23 deletions docs/en/docs/pluggables.md → docs/en/docs/extensions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Pluggables
# Extensions

What are pluggables in an Esmerald context? A separate and individual piece of software that
What are extensions in an Esmerald context? A separate and individual piece of software that
can be hooked into **any** Esmerald application and perform specific actions individually without
breaking the ecosystem.

Expand All @@ -20,17 +20,15 @@ wouldn't make too much sense right?
Also, how could we create this pattern, like Flask, to have an `init_app` and allow the application
to do the rest for you? Well, Esmerald now does that via its internal protocols and interfaces.

In Esmerald world, this is called [**pluggable**](#pluggable).

!!! Note
Pluggables only exist on an [application level](./application/levels.md#application-levels).
Extensions only exist on an [application level](./application/levels.md#application-levels).

## Pluggable

This object is one of a kind and does **a lot of magic** for you when creating a pluggble for
This object is one of a kind and does **a lot of magic** for you when creating an extension for
your application or even for distribution.

A **pluggable** is an object that receives an [Extension](#extension) class with parameters
A **Pluggable** is an object that receives an [Extension](#extension) class with parameters
and hooks them into your Esmerald application and executes the [extend](#extend) method when
starting the system.

Expand All @@ -42,10 +40,6 @@ It is this simple but is it the only way to add a pluggable into the system? **S

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

!!! Danger
If another object but the [Extension](#extension) is provided to the Pluggable, it will
raise an `ImproperlyConfigured`. Pluggables are **always expecting an Extension to be provided**.

## Extension

This is the main class that should be extended when creating a pluggable for Esmerald.
Expand All @@ -72,14 +66,14 @@ It is the entry-point for your extension.
The extend by default expects `kwargs` to be provided but you can pass your own default parameters
as well as there are many ways of creating and [hooking a pluggable]

## Hooking pluggables
## Hooking pluggables and extensions

As mentioned before, there are different ways of hooking a pluggable into your Esmerald application.

### The automated and default way

When using the default and automated way, Esmerald expects the pluggable to be passed into a dict
`pluggables` upon instantiation of an Esmerald application with `key-pair` value entries and where
`extensions` upon instantiation of an Esmerald application with `key-pair` value entries and where
the `key` is the name for your pluggable and the `value` is an instance [Pluggable](#pluggable)
holding your [Extension](#extension) object.

Expand All @@ -93,7 +87,15 @@ parameter if needed
{!> ../../../docs_src/pluggables/pluggable.py !}
```

You can access all the pluggables of your application via `app.pluggables` at any given time.
You can access all the extensions of your application via `app.extensions` at any given time.

#### Reordering

Sometimes there are dependencies between extensions. One requires another.
You can reorder the extending order by using the method `ensure_extension(name)` of `app.extensions`.
It will fail if the extension doesn't exist, so only call it in extend.

{!> ../../../docs_src/pluggables/reorder.py !}

### The manual and independent way

Expand All @@ -105,33 +107,40 @@ This way you don't need to use the [Pluggable](#pluggable) object in any way and
simply just use the [Extension](#extension) class or even your own since you **are in control**
of the extension.

```python hl_lines="25 42-43"
There are two variants how to do it:

```python title="With extension class or Pluggable"
{!> ../../../docs_src/pluggables/manual.py !}
```

```python hl_lines="25 42-43" title="Self registering"
{!> ../../../docs_src/pluggables/manual_self_registering.py !}
```

You can use for the late registration the methods `add_extension`.
It will automatically initialize and call extend for you when passing a class or **Pluggable**,
**but not when passing an instance**.

### Standalone object

But, what if I don't want to use the [Extension](#extension) object for my pluggable? Is this
possible?
´
yes, it must only implement the ExtensionProtocol.
devkral marked this conversation as resolved.
Show resolved Hide resolved

Short answer, yes, but this comes with limitations:

* You **cannot** hook the class within a [Pluggable](#pluggable) and use the automated way.
* You **will always need** to start it manually.

```python hl_lines="9 25 42-43"
```python hl_lines="9 25"
{!> ../../../docs_src/pluggables/standalone.py !}
```

## Important notes

As you can see, **pluggables** in Esmerald can be a powerful tool that isolates common
As you can see, **extensions** in Esmerald can be a powerful tool that isolates common
functionality from the main Esmerald application and can be used to leverage the creation of plugins
to be used across your applications and/or to create opensource packages for any need.

## ChildEsmerald and pluggables

A [Pluggable](#pluggable) **is not the same** as a [ChildEsmerald](./routing/router.md#child-esmerald-application).
An [Extension](#extension) **is not the same** as a [ChildEsmerald](./routing/router.md#child-esmerald-application).

These are two completely independent pieces of functionality with completely different purposes, be
careful when considering one and the other.
Expand Down
2 changes: 1 addition & 1 deletion docs/en/docs/extras/path-params.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,4 @@ And since `something` is not declared in the Enum type, you will get an error si
```


[lilya]: https//lilya.dev
[lilya]: https://lilya.dev
2 changes: 1 addition & 1 deletion docs/en/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ example.
* **Pluggables**: Create plugins for Esmerald and hook them into any application and/or
distribute them.
* **DAO and AsyncDAO**: Avoid database calls directly from the APIs. Use business objects instead.
* **ORM Support**: Native support for [Saffier][saffier_orm] and [Edgy][_orm].
* **ORM Support**: Native support for [Saffier][saffier_orm] and [Edgy][edgy_orm].
* **ODM Support**: Native support for [Mongoz][mongoz_odm].
* **APIView**: Class Based endpoints for your beloved OOP design.
* **JSON serialization/deserialization**: Both UJSON and ORJSON support.
Expand Down
2 changes: 1 addition & 1 deletion docs/en/docs/references/esmerald.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ from esmerald import Esmerald
- add_include
- add_child_esmerald
- add_router
- add_pluggable
- add_extension
- register_encoder

::: esmerald.ChildEsmerald
Expand Down
6 changes: 6 additions & 0 deletions docs/en/docs/references/extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# **`Extension`** class

This is the reference for the main object `Extension`. It is optionally wrapped by
an [Pluggable](./pluggables.md).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a Pluggable


::: esmerald.Extension
6 changes: 2 additions & 4 deletions docs/en/docs/references/pluggables.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# **`Pluggable`** class

This is the reference for the main object `Pluggable` that contains all the parameters,
attributes and functions.
This is the reference for the wrapper object `Pluggable` that contains all the parameters,
attributes and functions. It wraps an [Extension](./extensions.md).

::: esmerald.Pluggable

::: esmerald.Extension
13 changes: 12 additions & 1 deletion docs/en/docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ hide:

# Release Notes

## Unreleased

### Added

- Allow passing HTTP/WebSocket handlers directly to routes. They are automatically wrapped in Gateways-

### Changed

- Pluggables can now receive plain Extensions and Extension classes.
- Rename to extensions.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Breaking: Pluggables are now renamed to Extensions, check the docs for more information.


## 3.4.4

### Added
Expand Down Expand Up @@ -1023,7 +1034,7 @@ across main application and children.

### Added

- Esmerald [Pluggables](./pluggables.md) [#60](https://github.com/dymmond/esmerald/pull/60).
- Esmerald Pluggables [#60](https://github.com/dymmond/esmerald/pull/60).

This is the feature for the esmerald ecosystem that allows you to create plugins and extensions for any application
as well as distribute them as installable packages.
Expand Down
2 changes: 2 additions & 0 deletions docs/en/docs/routing/routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ different APIs and systems, so Esmerald created its own.
A Gateway is an extension of the Route, really, but adds its own logic and handling capabilities, as well as its own
validations, without compromising the core.

It is automatically added when just passing an HTTP/Websocket handler to routes.

### Gateway and application

In simple terms, a Gateway is not a direct route but instead is a "wrapper" of a [handler](./handlers.md)
Expand Down
5 changes: 4 additions & 1 deletion docs/en/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ nav:
- dependencies.md
- exceptions.md
- exception-handlers.md
- pluggables.md
- extensions.md
- password-hashers.md
- requests.md
- context.md
Expand Down Expand Up @@ -199,6 +199,7 @@ nav:
- references/permissions.md
- references/middleware/baseauth.md
- references/middleware/middlewares.md
- references/extensions.md
- references/pluggables.md
- references/exceptions.md
- references/request.md
Expand Down Expand Up @@ -238,5 +239,7 @@ extra:
alternate:
- link: /
name: en - English
- link: /ru/
name: ru - русский язык
hooks:
- ../../scripts/hooks.py
2 changes: 1 addition & 1 deletion docs_src/pluggables/child_esmerald.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ def extend(self, **kwargs: "DictAny") -> None:
logger.success("Added the ChildEsmerald via pluggable.")


app = Esmerald(routes=[], pluggables={"child-esmerald": Pluggable(ChildEsmeraldPluggable)})
app = Esmerald(routes=[], extensions={"child-esmerald": Pluggable(ChildEsmeraldPluggable)})
16 changes: 5 additions & 11 deletions docs_src/pluggables/manual.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,18 @@ def extend(self, **kwargs: "DictAny") -> None:
# Do something here like print a log or whatever you need
logger.success("Started the extension manually")

# Add the extension to the pluggables of Esmerald
# And make it accessible
self.app.add_pluggable("my-extension", self)


@get("/home")
async def home(request: Request) -> JSONResponse:
"""
Returns a list of pluggables of the system.
Returns a list of extensions of the system.

"pluggables": ["my-extension"]
"extensions": ["my-extension"]
"""
pluggables = list(request.app.pluggables)
extensions = list(request.app.extensions)

return JSONResponse({"pluggables": pluggables})
return JSONResponse({"extensions": extensions})


app = Esmerald(routes=[Gateway(handler=home)])

extension = MyExtension(app=app)
extension.extend()
app.add_extension("my-extension", MyExtension)
43 changes: 43 additions & 0 deletions docs_src/pluggables/manual_self_registering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Optional

from loguru import logger

from esmerald import Esmerald, Extension, Gateway, JSONResponse, Request, get
from esmerald.types import DictAny


class MyExtension(Extension):
def __init__(self, app: Optional["Esmerald"] = None, **kwargs: "DictAny"):
super().__init__(app, **kwargs)
self.app = app
self.kwargs = kwargs

def extend(self, **kwargs: "DictAny") -> None:
"""
Function that should always be implemented when extending
the Extension class or a `NotImplementedError` is raised.
"""
# Do something here like print a log or whatever you need
logger.success("Started the extension manually")

# Add the extension to the extensions of Esmerald
# And make it accessible
self.app.add_extension("my-extension", self)


@get("/home")
async def home(request: Request) -> JSONResponse:
"""
Returns a list of extensions of the system.

"extensions": ["my-extension"]
"""
extensions = list(request.app.extensions)

return JSONResponse({"extensions": extensions})


app = Esmerald(routes=[Gateway(handler=home)])

extension = MyExtension(app=app)
extension.extend()
2 changes: 1 addition & 1 deletion docs_src/pluggables/pluggable.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ def extend(self, config: PluggableConfig) -> None:

pluggable = Pluggable(MyExtension, config=my_config)

app = Esmerald(routes=[], pluggables={"my-extension": pluggable})
app = Esmerald(routes=[], extensions={"my-extension": pluggable})
21 changes: 21 additions & 0 deletions docs_src/pluggables/reorder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import Optional

from loguru import logger
from pydantic import BaseModel

from esmerald import Esmerald, Extension
from esmerald.types import DictAny


class MyExtension1(Extension):
def extend(self) -> None:
self.app.extensions.ensure_extension("extension2")
logger.success(f"Extension 1")


class MyExtension2(Extension):
def extend(self) -> None:
logger.success(f"Extension 2")


app = Esmerald(routes=[], extensions={"extension1": MyExtension1, "extension2": MyExtension2})
8 changes: 1 addition & 7 deletions docs_src/pluggables/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,13 @@ class PluggableConfig(BaseModel):


class MyExtension(Extension):
def __init__(
self, app: Optional["Esmerald"] = None, config: PluggableConfig = None, **kwargs: "DictAny"
):
super().__init__(app, **kwargs)
self.app = app

def extend(self, config: PluggableConfig) -> None:
logger.success(f"Successfully passed a config {config.name}")


class AppSettings(EsmeraldAPISettings):
@property
def pluggables(self) -> Dict[str, "Pluggable"]:
def extensions(self) -> Dict[str, Union["Extension", "Pluggable", type["Extension"]]]:
return {"my-extension": Pluggable(MyExtension, config=my_config)}


Expand Down
2 changes: 1 addition & 1 deletion docs_src/pluggables/settings_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def extend(self, config: PluggableConfig) -> None:

class AppSettings(EsmeraldAPISettings):
@property
def pluggables(self) -> Dict[str, "Pluggable"]:
def extensions(self) -> Dict[str, Union["Extension", "Pluggable", type["Extension"]]]:
return {"my-extension": Pluggable(MyExtension, config=my_config)}


Expand Down
Loading