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

New "*endpoint" decorators for custom endpoint creations #964

Merged
merged 13 commits into from
Nov 13, 2024

Conversation

sambarza
Copy link
Contributor

@sambarza sambarza commented Nov 1, 2024

Description

New decorators to permit the creation of custom endpoints:

from cat.mad_hatter.decorators import endpoint

@endpoint.get
@endpoint.post
@endpoint.endpoint (for all the other HTTP verbs)

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • Automatic tests implemented

Simple example of use inside a plugin

from cat.mad_hatter.decorators import endpoint

@endpoint.get(path="/hello")
def hello_world():
    return {"Hello":"world"}

Consume with:
curl http://localhost:1865/custom/hello

More complex example

from cat.mad_hatter.decorators import endpoint
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: str

@endpoint.post(path="/hello", prefix="/example", tags=["Custom Tag"])
def my_post_endpoint(item: Item, stray=Depends(HTTPAuth(AuthResource.PLUGINS, AuthPermission.LIST))):
    return {"Hello": item.name, "Description": item.description, "Stray user": stray.user_id}

Consume with:

curl --request POST \
  --url http://localhost:1865/example/hello \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "the Cat",
  "description": "is magic!"
}'

Internals

The decorators wrap the function as a FastAPI path operation, all the FastAPI path operation parameters can be used.
The default prefix is custom and default tag is Custom Endpoints

The new decorators are managed as other decorators (approach was: searched everywhere for hook and adapted for endpoint).

Activation and deactivation of plugins

  • During activation we have to do just a trick to flush the openapi schema cache for /docs
  • During deactivate we have to remove the FastAPI route manually, seems there is no official method to remove them, see Easy way to enable/disable endpoint fastapi/fastapi#8088, we have also to flush the openapi schema cache for /docs too

Change to startup process

Note, there is a change in the CheshireCat startup process, now the CheshireCat needs the FastAPI app instance, why?

The endpoint decorator creates a new FastAPI router for each new custom endpoint and then includes the router in the FastAPI app, CheshireCat activate the decorator injecting the FastAPI app.

Flows

Startup process

  • CheshireCat starts MadHatter initialization
  • MadHatter finds and loads all the plugins
  • MadHatter activate all the plugins however at this steps the endpoints decorators are not "activated", the custom routers are not included in FastAPI app yet
  • MadHatter.find_plugins() calls the CheshireCat callback on_finish_plugins_sync_callback
  • In the callback CheshireCat all the endpoints of active plugins get activated
  • The callback calls Endpoint.activate() for each custom endpoint
  • Endpoint.activate() add the route to the FastAPI routes and clear the /docs cache

Plugin installation process

  • User installs a plugin
  • MadHatter install the plugin
  • MadHatter calls toggle_plugin()
  • toggle_plugin() activates the plugin (however the endpoints are not really active, are not yet included in FastAPI app)
  • toggle_plugin() calls the CheshireCat callback on_finish_plugins_sync_callback
  • The callback calls Endpoint.activate() for each custom endpoint
  • Endpoint.activate() add the route to the FastAPI routes and clear the /docs cache

Plugin activation process

  • User activates a plugin
  • toggle_plugin() activates the plugin (however the endpoints are not really active, are not yet included in FastAPI app)
  • toggle_plugin() calls the CheshireCat callback on_finish_plugins_sync_callback
  • The callback calls Endpoint.activate() for each custom endpoint
  • Endpoint.activate() add the route to the FastAPI routes and clear the /docs cache

Plugin deactivation process

  • User activates a plugin
  • toggle_plugin() deactivate the plugin
  • Plugin.deactivate() calls Endpoint.deactivate() for each custom endpoint related to the plugin
  • Endpoint.deactivate() removes the route from the FastAPI routes and clear the /docs cache
  • toggle_plugin() calls the CheshireCat callback on_finish_plugins_sync_callback
  • The callback do nothing about endpoint decorator

Plugins API

The endpoints are already exposed by the /plugins/ API (can be shown in the admin portal).
This is an example of the returned payload with the new endpoints node:

{
   "installed": [
            "id": "cc-dot-commands",
            "name": "Dot Commands",
            "description": "Handy utility designed to simplify and speed up your plugin development process. Install it, activate it, and send a message with just a period '.' in the chat",
            "author_name": "[email protected]",
            "hooks": [...],
            "tools": [...],
            "endpoints": [
                {
                    "name": "/example/hello",
                    "tags": [
                        "Dot Commands"
                    ]
                }
            ]
        }
]

Automatic tests

test_endpoints.py tests the new decorators.

@valentimarco
Copy link
Collaborator

valentimarco commented Nov 1, 2024

DECORATORS
@lucapirrone

@sambarza
Copy link
Contributor Author

sambarza commented Nov 2, 2024

DECORATORS @lucapirrone

Suggested by @pieroit

@pieroit
Copy link
Member

pieroit commented Nov 2, 2024

This is cooking up gooooooood 😍

@sambarza sambarza marked this pull request as draft November 2, 2024 12:22
- one decorator but with dot syntax
  @endpoint.get
  @endpoint.post
  @endpoint.endpoint
- default prefix now "custom-endpoints" (kebab-case)
- add tests on tags
How to reproduce:
- Launch the Cat
- Goto /docs page
- Activate a plugin with a custom endpoint
- The custom endpoint can be used
- Refresh page /docs
- The new custom endpoint is not there

Why:
The openapi schema is cached by get_openapi_configuration_function

Solution:
Each time a function is decorated with `endpoint`, we flush the cache
- Routes manually removed from FastAPI routes (seems there is no official method to do this)
- Flush the docs cache
@sambarza sambarza marked this pull request as ready for review November 2, 2024 15:44
@sambarza sambarza marked this pull request as draft November 2, 2024 17:49
Why not using plugin.deactivate()?
the "endpoint" decorator needs the reference to the FastAPI app instance for endpoint removing.
Calling plugin.deactivate() the method CustomEndpoint.deactivate() raises the exception:
"self.cheshire_cat_api is None"

because the method on_finish_plugins_sync_callback is not called so the cheshire_cat_api is not injected
@sambarza sambarza marked this pull request as ready for review November 3, 2024 13:22
@pieroit
Copy link
Member

pieroit commented Nov 4, 2024

Run a poll at dev meeting for default endpoint path:

image

@sambarza
Copy link
Contributor Author

sambarza commented Nov 5, 2024

Default tags not changed, it's "Custom Endpoints" (more explicit), ok?
image

@pieroit
Copy link
Member

pieroit commented Nov 5, 2024

@sambarza yes awesome
Yesterday at dev meeting we had a read at the PR, this is going to be useful and fun
THANK YOUUU

@valentimarco valentimarco added enhancement New feature or request endpoints Related to http / ws endpoints labels Nov 7, 2024
@pieroit pieroit merged commit d435142 into cheshire-cat-ai:develop Nov 13, 2024
2 checks passed
@pieroit
Copy link
Member

pieroit commented Nov 13, 2024

Thanks @sambarza had a great experience reviewing
I improved tests a little:

  • network tests are here
  • mad_hatter tests are here where you created them, but only deal wth the mad hatter
  • updated other tests regarding decorators discovery and caches so now they include the endpoints

I'll spend some more time to see if the CustomEndpoint class can be unaware of fastAPI
No doubt CheshireCat class needs the reference

Awesome

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
endpoints Related to http / ws endpoints enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants