Requests API
Table of contents
Add Request API to dapr.yaml
As we will be having many dapr applications, let’s leverage the multi-run feature of Dapr to run all the applications at once. For now, let’s add the request api only to the dapr.yaml
file.
- Open the
dapr.yaml
file in the/
folder and add a new node for the application including the environment variables.
Toggle solution
- appID: summarizer-requests-api
appDirPath: ./src/requests-api/
appPort: 13000
command: ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "13000"]
env:
STATE_STORE_NAME: "summarizer-statestore"
STATE_STORE_QUERY_INDEX_NAME: "orgIndx"
BINDING_SMTP: "summarizer-smtp"
APP_PORT: 13000
Note: The
appID
is used to identify the application in the Dapr runtime. TheappDirPath
is the path to the application folder. TheappPort
is the port used by the application. Thecommand
is the command used to start the application. Theenv
is the environment variables used by the application.
Create State Store yaml
From the previous definition of the environment variables of our application, we can see that we will be using a state store. Let’s create the yaml file for the state store.
- Create a new file named
summarizer-statestore.yaml
in the/dapr/local/components
folder, add a state store component called ‘summarizer-statestore’ and configure it to use Redis. Query indexes should also be configured to allow for fast retrieval of data (url, url_hashed and id).
Toggle solution
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: summarizer-statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
- name: queryIndexes
value: |
[
{
"name": "orgIndx",
"indexes": [
{
"key": "id",
"type": "TEXT"
},
{
"key": "url",
"type": "TEXT"
},
{
"key": "url_hashed",
"type": "TEXT"
}
]
}
]
Note: The
name
is the name of the component. Thetype
is the type of the component. Theversion
is the version of the component. Themetadata
is the configuration of the component. We also added a query index to the state store to be able to query the state store by url. url_hashed is the hashed version of the url to ease the search query and avoid any special characters issues.
Request API Overview
- Open the
app.py
file in the/src/requests-api
folder, and notice the DaprClient object that is used to interact with the Dapr runtime.
dapr_client = DaprClient()
- We also have several endpoints defined to manage the requests lifecycle :
@app.get('/requests')
: Get all the requests@app.post('/search-requests-by-url')
: Search a request with specific link@app.post('/requests', status_code = status.HTTP_201_CREATED)
: Create a request
Implementing state and SMTP binding methods
Let’s implement the state methods and the SMTP output binding.
- Open the
request_state.py
file in the/src/requests-api
folder, managing the state store.
def __query_state(self, query : str)
is using the DaprClient object to execute the query to the state store.
- Fill the
find_all
method to get all the requests from the state store. The method should return a list ofRequest
objects.
Toggle solution
def find_all(self, token: str = None):
try:
# Paging are supported using token
# see https://docs.dapr.io/developing-applications/building-blocks/state-management/howto-state-query-api/
token = f", \"token\": \"{token}\"" if token is not None else ""
query = '''
{{
"page": {{
"limit": 100{0}
}}
}}
'''.format(token)
return self.__query_state(query)
except Exception as e:
logging.error(
f"Error while trying to find all requests within the dapr state store")
logging.error(e)
return []
- Fill the
__try_filter
method to get a request from the state store using a specific property and value. The method should return aRequest
object or None.
Toggle solution
def __try_filter(self, property : str, value : str):
try:
query = '''
{{
"filter": {{
"EQ": {{ "{0}": "{1}" }}
}}
}}
'''.format(property, value)
results = self.__query_state(query)
return results[0] if len(results) > 0 else None
except Exception as e:
logging.error(
f"Error while trying to find request using {property} and {value} within the dapr state store")
logging.error(e)
return None
- Fill the
__alert_owner
method to invoke the smtp binding, providing an email contents as a data.None.
Toggle solution
def __alert_owner(self, request: SummarizeRequest):
# Send email to requestor
email_contents = """
<html>
<body>
<p>Hi,</p>
<p>Here is the summary for the article you requested:</p>
<p><a href="{url}">{url}</a></p>
<p>{summary}</p>
<p>Thanks for using our service!</p>
</body>
</html>
""".format(url=request.get_url(), summary=request.get_summary())
self.dapr_client.invoke_binding(
binding_name=self.settings.binding_smtp,
operation='create',
data=json.dumps(email_contents),
binding_metadata={
"emailTo": request.get_email(),
"subject": f"🎉 New Summary for {request.get_url()}!"
}
)
Validate that the request is stored in the state store and email received
-
Execute dapr run multi run command to start the application
dapr run -f .
-
Open a new terminal and use curl to validate new request creation
-
Check that the request is stored in the state store and email received