Moved sections to individual directories
This commit is contained in:
parent
5ab159f149
commit
7a348a7d71
|
@ -0,0 +1,24 @@
|
||||||
|
.. _index-scripts:
|
||||||
|
|
||||||
|
=================
|
||||||
|
Creating a Script
|
||||||
|
=================
|
||||||
|
|
||||||
|
`Scripts` can be called from a script task in workflows. They are a great way to extend workflow capabilities.
|
||||||
|
|
||||||
|
Scripts extend the `Script` base class found in crc.scripts.script, and they must define the
|
||||||
|
`get_description`, `do_task`, and `do_task_validate_only` methods.
|
||||||
|
|
||||||
|
The get_description method should return a string that is displayed when configurators get a list of available scripts.
|
||||||
|
|
||||||
|
The do_task_validate_only method is run during the configurator `shield test`.
|
||||||
|
We only need to make sure that the script *can* run here. We don't want to make any externals calls.
|
||||||
|
|
||||||
|
The do_task method is where we put the code we need to achieve our task.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
01_script
|
||||||
|
02_workflow
|
||||||
|
03_running
|
|
@ -0,0 +1,57 @@
|
||||||
|
--------------
|
||||||
|
Example Script
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Create a script that accepts keyword arguments, calls an external api using the arguments, and returns a result.
|
||||||
|
|
||||||
|
My data source is a simple API found at https://deckofcardsapi.com that returns cards drawn from one or more decks of playing cards.
|
||||||
|
|
||||||
|
My script accepts two keyword arguments; cards and decks.
|
||||||
|
|
||||||
|
|
||||||
|
Example code: crc/scripts/tutorial.py
|
||||||
|
|
||||||
|
.. code-block:: Python
|
||||||
|
|
||||||
|
from crc.scripts.script import Script
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
class TutorialScript(Script):
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return """Simple script for teaching purposes"""
|
||||||
|
|
||||||
|
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||||
|
self.do_task(task, study_id, workflow_id, *args, **kwargs)
|
||||||
|
|
||||||
|
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
|
||||||
|
|
||||||
|
drawn_cards = []
|
||||||
|
|
||||||
|
cards = int(kwargs['cards']) if hasattr(kwargs, 'cards') else 1
|
||||||
|
decks = int(kwargs['decks']) if hasattr(kwargs, 'decks') else 1
|
||||||
|
|
||||||
|
deck_url = f'https://deckofcardsapi.com/api/deck/new/shuffle/?deck_count={decks}'
|
||||||
|
deck_response = requests.get(deck_url)
|
||||||
|
deck_id = deck_response.json()['deck_id']
|
||||||
|
|
||||||
|
card_url = f'https://deckofcardsapi.com/api/deck/{deck_id}/draw/?count={cards}'
|
||||||
|
card_response = requests.get(card_url)
|
||||||
|
|
||||||
|
for card in range(cards):
|
||||||
|
card_value = card_response.json()['cards'][card]['value']
|
||||||
|
card_suit = card_response.json()['cards'][card]['suit']
|
||||||
|
drawn_cards.append({'suit': card_suit, 'value': card_value})
|
||||||
|
|
||||||
|
return drawn_cards
|
||||||
|
|
||||||
|
|
||||||
|
First, I create an empty list of drawn_cards, and pull cards and decks from the keyword arguments.
|
||||||
|
Cards and decks both default to 1 if they don't exist as keyword arguments.
|
||||||
|
|
||||||
|
I make an API call to get a `deck_id`, and then make another call with the deck_id to get a `card_response`.
|
||||||
|
|
||||||
|
From the card_response, I build and return a list of drawn_cards.
|
||||||
|
|
||||||
|
We have a script we can call from a workflow. Now we have to build the workflow.
|
|
@ -0,0 +1,15 @@
|
||||||
|
----------------
|
||||||
|
Example Workflow
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Here is a simple workflow you can use to call this script.
|
||||||
|
|
||||||
|
.. image:: /_static/03/01/tutorial_workflow.png
|
||||||
|
|
||||||
|
.. image:: /_static/03/01/how_many.png
|
||||||
|
|
||||||
|
.. image:: /_static/03/01/get_cards.png
|
||||||
|
|
||||||
|
.. image:: /_static/03/01/display_cards.png
|
||||||
|
|
||||||
|
You can find the :download:`bpmn for the workflow <https://github.com/sartography/cr-connect-tutorial/blob/main/_static/tutorial.bpmn>` in the repository.
|
|
@ -0,0 +1,13 @@
|
||||||
|
=======
|
||||||
|
Running
|
||||||
|
=======
|
||||||
|
|
||||||
|
And here it is running.
|
||||||
|
|
||||||
|
.. image:: /_static/03/01/start_workflow.png
|
||||||
|
|
||||||
|
.. image:: /_static/03/01/enter_how_many.png
|
||||||
|
|
||||||
|
.. image:: /_static/03/01/display_returned_cards.png
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
.. _index-services:
|
||||||
|
|
||||||
|
==================
|
||||||
|
Building a Service
|
||||||
|
==================
|
||||||
|
|
||||||
|
Services are internal code related to a specific function. We have services that manage users and studies,
|
||||||
|
interact with Protocol Builder and the LDAP server, send emails, and make calls to SpiffWorkflow.
|
||||||
|
|
||||||
|
Services should not be called directly from outside the system. They should be called by scripts, the API, and other services.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
01_service
|
||||||
|
02_script
|
|
@ -0,0 +1,30 @@
|
||||||
|
----------------
|
||||||
|
Tutorial Service
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Let's build a service that replaces the do_task method in our script. We can then call the service from our script.
|
||||||
|
|
||||||
|
.. code-block:: Python
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
class TutorialService(object):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pick_a_card(cards, decks):
|
||||||
|
drawn_cards = []
|
||||||
|
|
||||||
|
deck_url = f'https://deckofcardsapi.com/api/deck/new/shuffle/?deck_count={decks}'
|
||||||
|
deck_response = requests.get(deck_url)
|
||||||
|
deck_id = deck_response.json()['deck_id']
|
||||||
|
|
||||||
|
card_url = f'https://deckofcardsapi.com/api/deck/{deck_id}/draw/?count={cards}'
|
||||||
|
card_response = requests.get(card_url)
|
||||||
|
|
||||||
|
for card in range(cards):
|
||||||
|
card_value = card_response.json()['cards'][card]['value']
|
||||||
|
card_suit = card_response.json()['cards'][card]['suit']
|
||||||
|
drawn_cards.append({'suit': card_suit, 'value': card_value})
|
||||||
|
|
||||||
|
return drawn_cards
|
|
@ -0,0 +1,26 @@
|
||||||
|
=============
|
||||||
|
Modify Script
|
||||||
|
=============
|
||||||
|
|
||||||
|
We can now modify our script to call this new service.
|
||||||
|
|
||||||
|
.. code-block:: Python
|
||||||
|
|
||||||
|
from crc.scripts.script import Script
|
||||||
|
from crc.services.tutorial_service import TutorialService
|
||||||
|
|
||||||
|
class TutorialScript(Script):
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return """Simple script for teaching purposes"""
|
||||||
|
|
||||||
|
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||||
|
self.do_task(task, study_id, workflow_id, *args, **kwargs)
|
||||||
|
|
||||||
|
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
|
||||||
|
|
||||||
|
cards = kwargs['cards']
|
||||||
|
decks = kwargs['decks']
|
||||||
|
|
||||||
|
drawn_cards = TutorialService.pick_a_card(cards=cards, decks=decks)
|
||||||
|
return drawn_cards
|
|
@ -0,0 +1,19 @@
|
||||||
|
.. _index-api:
|
||||||
|
|
||||||
|
======================
|
||||||
|
Adding an API Endpoint
|
||||||
|
======================
|
||||||
|
|
||||||
|
Currently, we have a service that returns playing cards.
|
||||||
|
We can call this service from a script, and return the result in a workflow.
|
||||||
|
|
||||||
|
Now, let's add an API endpoint that calls the service.
|
||||||
|
|
||||||
|
There are two steps in the process; defining the endpoint, and adding the Python code to support it.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
|
01_yml
|
||||||
|
02_get_cards
|
||||||
|
03_endpoints
|
|
@ -0,0 +1,86 @@
|
||||||
|
-------
|
||||||
|
api.yml
|
||||||
|
-------
|
||||||
|
|
||||||
|
The API endpoints are defined in `api.yml` in the `paths` section.
|
||||||
|
|
||||||
|
Endpoints all have a `path`. Our path will be `get_cards`.
|
||||||
|
Endpoints can have `parameters`. Our parameters are `cards` and `decks`.
|
||||||
|
Our parameters are included as part of the query.
|
||||||
|
They are integers, and are not required.
|
||||||
|
|
||||||
|
The request type for our endpoint is `GET`.
|
||||||
|
We tell the endpoint what Python code to execute in `operationId`.
|
||||||
|
We will turn off `security` for our endpoint, and add a `Configurator Tools` tag.
|
||||||
|
|
||||||
|
We must define a `200` response.
|
||||||
|
In the response, declare that we are returning an `array` of `PlayingCards`.
|
||||||
|
|
||||||
|
|
||||||
|
Here is what that code looks like:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
paths:
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
/get_cards:
|
||||||
|
parameters:
|
||||||
|
- name: cards
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: The number of cards to draw. Defaults to one.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
- name: decks
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: The number of decks to draw from. Defaults to one.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
get:
|
||||||
|
operationId: crc.api.tools.get_cards
|
||||||
|
security: [] # Disable security for this endpoint only.
|
||||||
|
summary: Draw cards from a deck of playing cards. For learning purposes only.
|
||||||
|
tags:
|
||||||
|
- Configurator Tools
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Returns the chosen card(s)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/PlayingCards"
|
||||||
|
|
||||||
|
We need to define the schema for PlayingCards.
|
||||||
|
|
||||||
|
Our playing cards have two properties; suit and value.
|
||||||
|
They are both strings.
|
||||||
|
|
||||||
|
You can add an example which shows up on the API webpage.
|
||||||
|
|
||||||
|
Schemas are defined in the `schemas` section.
|
||||||
|
|
||||||
|
The schema code looks like this:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
PlayingCards:
|
||||||
|
properties:
|
||||||
|
suit:
|
||||||
|
type: string
|
||||||
|
example: SPADES
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
example: 10
|
||||||
|
|
||||||
|
We use the `Connexion <https://connexion.readthedocs.io/en/latest/>`_ framework for our API.
|
||||||
|
|
||||||
|
You can check your YML code at https://editor.swagger.io/
|
|
@ -0,0 +1,38 @@
|
||||||
|
We now need to write the Python code that should exist in `crc.api.tools.get_cards`.
|
||||||
|
|
||||||
|
-----------------------
|
||||||
|
crc.api.tools.get_cards
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
We have defined an API endpoint. Now, we need to write the Python code to support it.
|
||||||
|
|
||||||
|
When we defined the get_cards endpoint, we set `operationId` to `crc.api.tools.get_cards`.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
get:
|
||||||
|
operationId: crc.api.tools.get_cards
|
||||||
|
|
||||||
|
This means that the API expects a method called `get_cards` in the `crc.api.tools` module.
|
||||||
|
|
||||||
|
We need to add this method.
|
||||||
|
|
||||||
|
get_cards
|
||||||
|
---------
|
||||||
|
|
||||||
|
We have already done the heavy lifting, so we don't have to write much Python code.
|
||||||
|
|
||||||
|
get_cards has to:
|
||||||
|
|
||||||
|
- take in two parameters; cards and decks,
|
||||||
|
- make a call to our service, and
|
||||||
|
- return a list of playing cards.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
def get_cards(cards=1, decks=1):
|
||||||
|
drawn_cards = TutorialService.pick_a_card(cards=cards, decks=decks)
|
||||||
|
return drawn_cards
|
||||||
|
|
||||||
|
Remember that our API definition says that our parameters are not required.
|
||||||
|
So, we had to give them default values in our method definition.
|
|
@ -0,0 +1,7 @@
|
||||||
|
---------
|
||||||
|
Endpoints
|
||||||
|
---------
|
||||||
|
|
||||||
|
At this point, we should have a functioning API endpoint that calls crc.api.tools.get_cards.
|
||||||
|
|
||||||
|
If you restart your instance, you can view the endpoint at http://localhost:5000/v1.0/ui/#/Configurator%20Tools/
|
Loading…
Reference in New Issue