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