REST API
Create a web application class
Define a python module that includes a web application class and your required API endpoints:
$ touch chart_extension/webapp.py
Open chart_extension/webapp.py
with your code editor and provide the following data:
from connect.eaas.core.decorators import router, web_app
from connect.eaas.core.extension import WebApplicationBase
@web_app(router)
class ChartWebApplication(WebApplicationBase):
pass
A web application class must import the structure from connect.eaas.core.extension.WebApplicationBase
and must be decorated with the web_app
decorator that accepts the router
instance as an argument.
The router
instance will be used later to declare your API endpoints.
Declare web application class in pyproject.toml
Your extension will be executed by the EaaS runtime called Connect Extension Runner.
This runner uses setuptools entrypoints to discover all features that are implemented by your extensions. Therefore, it is required to modify pyproject.toml and add an entrypoint for your web application as follows:
[tool.poetry]
name = "chart-extension"
version = "0.1.0"
description = "Chart extension"
authors = ["Globex corporation"]
readme = "README.md"
packages = [{include = "chart_extension"}]
[tool.poetry.dependencies]
python = ">=3.8,<3.11"
connect-eaas-core = ">=26.12,<27"
[tool.poetry.plugins."connect.eaas.ext"]
"webapp" = "chart_extension.webapp:ChartWebApplication"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Implement retrieve settings
API endpoint
The backend of this web application is based on the FastAPI web framework. The FastAPI framework uses the pydantic library to define input/output models for an API and perform JSON serialization/deserialization and validation of messages. Pydantic models are also used by FastAPI to generates schemas for your request/response payloads.
The following demonstrates how to implement a web application for working with multiple modules (models
) on the Connect platform. Add a new module by using the following command:
$ touch chart_extension/schemas.py
Next, add the Marketplace
and Settings
modules to the schemas.py
module:
from typing import List
from pydantic import BaseModel
class Marketplace(BaseModel):
id: str
name: str
description: str
icon: Optional[str]
@validator('icon')
def set_icon(cls, icon):
return icon or '/static/images/mkp.svg'
class Settings(BaseModel):
marketplaces: List[Marketplace] = []
Once your required models are defined, add an endpoint to your web application to retrieve settings of a specific account. Connect enables you to store the settings of an extension for a specific account within the installation objects.
Locate the ChartWebApplication
class and add the following endpoint:
from connect.eaas.core.decorators import router, web_app
from connect.eaas.core.extension import WebApplicationBase
from connect.eaas.core.inject.synchronous import get_installation
from fastapi import Depends
from chart_extension.schemas import Settings
@web_app(router)
class ChartWebApplication(WebApplicationBase):
@router.get(
'/settings',
summary='Retrive extension settings',
response_model=Settings,
)
def retrieve_settings(
self,
installation: dict = Depends(get_installation),
):
return Settings(marketplaces=installation['settings'].get('marketplaces', []))
By using the router
instance as a decorator, you can define an GET
operation, specify the path (/settings
), a summary for the OpenAPI specifications, and a response model for such operation.
To obtain the installation object owned by the account that is calling your settings endpoint, use the dependency injection technique of the FastAPI framework.
Specifically, FastAPI injects an installation
object as an argument for the method that handles this endpoint by using the get_installation
injection function provided by connect-eaas-core
.
The extension runner is used to retrieve an installation object that is owned by a certain caller.
Futhermore, this method returns a Settings model with selected marketplaces. These marketplaces are received from the settings
attribute of the installation object.
Note
Your endpoints will be prefixed with the /api
path fragment, so your retrieve settings endpoint will be available at
/api/settings
.
Warning
The settings attribute of an installation object can store arbitrary JSON and are supported by extensions that include a small number of values (1-2 Kb at most). If your extension requires to keep more settings for a given account, you will need to use an external storage system such as a database as a service.
In case more information on the FastAPI dependency injection system is required, refer to the FastAPI Dependencies docs.
Implements list marketplaces
API endpoint
The demo web application should include an endpoint to retrieve a list of available marketplaces. Thus, this application will query the Connect API to create a list of checkboxes and users will be able to select required matketplaces.
Implement this functionality by using the following code:
from connect.client import ConnectClient
from connect.eaas.core.decorators import router, web_app
from connect.eaas.core.extension import WebApplicationBase
from connect.eaas.core.inject.synchronous import get_installation, get_installation_client
from fastapi import Depends
from chart_extension.schemas import Settings
@web_app(router)
class ChartWebApplication(WebApplicationBase):
@router.get(
'/settings',
summary='Retrive extension settings',
response_model=Settings,
)
def retrieve_settings(
self,
installation: dict = Depends(get_installation),
):
return Settings(marketplaces=installation['settings'].get('marketplaces', []))
@router.get(
'/marketplaces',
summary='List all available marketplaces',
response_model=List[Marketplace],
)
def list_marketplaces(
self,
client: ConnectClient = Depends(get_installation_client),
):
return [
Marketplace(**marketplace)
for marketplace in client.marketplaces.all().values_list(
'id', 'name', 'description', 'icon',
)
]
ConnectClient
that works in the scope of a specific account. Such accounts will consume your specified endpoint by using the get_installation_client
injection function.
Implement save settings
API endpoint
It is also required to add the functionality to save provided settings. Use the following code to add the corresponding endpoint:
from connect.client import ConnectClient
from connect.eaas.core.decorators import router, web_app
from connect.eaas.core.extension import WebApplicationBase
from connect.eaas.core.inject.common import get_call_context
from connect.eaas.core.inject.models import Context
from connect.eaas.core.inject.synchronous import get_installation, get_installation_client
from fastapi import Depends
from chart_extension.schemas import Settings
@web_app(router)
class ChartWebApplication(WebApplicationBase):
@router.get(
'/settings',
summary='Retrive extension settings',
response_model=Settings,
)
def retrieve_settings(
self,
installation: dict = Depends(get_installation),
):
return Settings(marketplaces=installation['settings'].get('marketplaces', []))
@router.get(
'/marketplaces',
summary='List all available marketplaces',
response_model=List[Marketplace],
)
def list_marketplaces(
self,
client: ConnectClient = Depends(get_installation_client),
):
return [
Marketplace(**marketplace)
for marketplace in client.marketplaces.all().values_list(
'id', 'name', 'description', 'icon',
)
]
@router.post(
'/settings',
summary='Save charts settings',
response_model=Settings,
)
def save_settings(
self,
settings: Settings,
context: Context = Depends(get_call_context),
client: ConnectClient = Depends(get_installation_client),
):
client('devops').installations[context.installation_id].update(
payload={
'settings': settings.dict(),
},
)
return settings
In order to locate and update required installation identifiers, use FastAPI to inject the call context in your method.
In addition, note that this method requires a settings argument of the
Settings
type. Thus, the request payload will be deserialized to the Settings
pydantic model.
Info
The Context object is a pydantic model that contains the following attributes:
- installation_id: identifier of the installation object from which a call is originated
- user_id: current UI user id or service user id for API calls
- account_role: can be either
vendor
,distributor
orreseller
- call_source: use the
ui
value that calls will originated from the Connect user interface, useapi
to use direct API calls - call_type: in case your value is set to
admin
, all calls will be originated from the same account that own the extension.
Implement generate chart data
API endpoint
The demo web app renders charts by using quickchart.io that allows working with diagrams. Thus, the provided endpoint generates chart configuration json that will be send to quickchart.io as a query-string parameter.
Implement this functionality as follows:
from connect.client import ConnectClient
from connect.eaas.core.decorators import router, web_app
from connect.eaas.core.extension import WebApplicationBase
from connect.eaas.core.inject.common import get_call_context
from connect.eaas.core.inject.models import Context
from connect.eaas.core.inject.synchronous import get_installation, get_installation_client
from fastapi import Depends
from chart_extension.schemas import Settings
@web_app(router)
class ChartWebApplication(WebApplicationBase):
@router.get(
'/settings',
summary='Retrive extension settings',
response_model=Settings,
)
def retrieve_settings(
self,
installation: dict = Depends(get_installation),
):
return Settings(marketplaces=installation['settings'].get('marketplaces', []))
@router.get(
'/marketplaces',
summary='List all available marketplaces',
response_model=List[Marketplace],
)
def list_marketplaces(
self,
client: ConnectClient = Depends(get_installation_client),
):
return [
Marketplace(**marketplace)
for marketplace in client.marketplaces.all().values_list(
'id', 'name', 'description', 'icon',
)
]
@router.post(
'/settings',
summary='Save charts settings',
response_model=Settings,
)
def save_settings(
self,
settings: Settings,
context: Context = Depends(get_call_context),
client: ConnectClient = Depends(get_installation_client),
):
client('devops').installations[context.installation_id].update(
payload={
'settings': settings.dict(),
},
)
return settings
@router.get(
'/chart',
summary='Generate chart data',
)
def generate_chart_data(
self,
installation: dict = Depends(get_installation),
client: ConnectClient = Depends(get_installation_client),
):
data = {}
for mp in installation['settings'].get('marketplaces', []):
active_assets = client('subscriptions').assets.filter(
R().marketplace.id.eq(mp['id']) & R().status.eq('active'),
).count()
data[mp['id']] = active_assets
return {
'type': 'bar',
'data': {
'labels': list(data.keys()),
'datasets': [
{
'label': 'Subscriptions',
'data': list(data.values()),
},
],
},
}