Moved sections to individual directories

This commit is contained in:
mike cullerton 2021-05-27 10:48:50 -04:00
parent 5ab159f149
commit 7a348a7d71
11 changed files with 331 additions and 0 deletions

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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/

View File

@ -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.

View File

@ -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/