Browse Source

Merge pull request #26 from kalliope-project/update_api

Update api
Monf 8 years ago
parent
commit
4726e270ca

+ 62 - 9
Docs/rest_api.md

@@ -4,17 +4,18 @@ Kalliope provides the REST API to manage the synapses. For configuring the API r
 
 ## Synapse API
 
-| Method | URL                      | Action               |
-|--------|--------------------------|----------------------|
-| GET    | /synapses                | List synapses        |
-| GET    | /synapses/<synapse_name> | Show synapse details |
-| POST   | /synapses/<synapse_name> | Run a synapse        |
+| Method | URL                      | Action                      |
+|--------|--------------------------|-----------------------------|
+| GET    | /synapses                | List synapses               |
+| GET    | /synapses/<synapse_name> | Show synapse details        |
+| POST   | /synapses/<synapse_name> | Run a synapse by its name   |
+| POST   | /order                   | Run a synapse from an order |
 
 ## Curl examples
 
 >**Note:** --user is only needed if `password_protected` is True
 
-### Get all synapse
+### List synapses
 
 Normal response codes: 200
 Error response codes: unauthorized(401), itemNotFound(404)
@@ -68,7 +69,7 @@ Output example:
 }
 ```
 
-### Get one synapse's detail by its name. 
+### Show synapse details 
 
 Normal response codes: 200
 Error response codes: unauthorized(401), itemNotFound(404)
@@ -100,7 +101,7 @@ Output example:
 }
 ```
 
-### Run a synapse by its name. 
+### Run a synapse by its name
 
 Normal response codes: 201
 Error response codes: unauthorized(401), itemNotFound(404)
@@ -130,4 +131,56 @@ Output example:
     ]
   }
 }
-```
+```
+
+
+Run a synapse from an order
+
+Normal response codes: 201
+Error response codes: unauthorized(401), itemNotFound(404)
+
+Curl command:
+```
+curl -i --user admin:secret -H "Content-Type: application/json" -X POST -d '{"order":"my order"}' http://localhost:5000/order
+```
+
+If the order contains accent or quotes, use a file for testing with curl
+```
+cat post.json 
+{"order":"j'aime"}
+```
+Then
+```
+curl -i --user admin:secret -H "Content-Type: application/json" -X POST --data @post.json http://localhost:5000/order/
+```
+
+Output example if the order have matched and so launched synapses:
+```
+{
+  "synapses": [
+    {
+      "name": "Say-hello", 
+      "neurons": [
+        {
+          "name": "say", 
+          "parameters": "{'message': ['Hello sir']}"
+        }
+      ], 
+      "signals": [
+        {
+          "order": "hello"
+        }
+      ]
+    }
+  ]
+}
+```
+
+If the order haven't match ny synapses:
+```
+{
+  "error": {
+    "error": "The given order doesn't match any synapses"
+  }
+}
+```

+ 1 - 1
README.md

@@ -43,7 +43,7 @@ Kalliope is easy-peasy to use, see the hello world
 | [STT](Docs/stt.md)                 | Speech to text configuration                                                                |
 | [TTS](Docs/tts.md)                 | Text to speech configuration                                                                |
 | [Triggers](Docs/trigger.md)        | Magic hotword engine used to make Kalliope listtening for an order                          |
-
+| [REST API](Docs/rest_api.md)       | Integrated REST API. Can be used to send an order                                           |
 
 ## Contributing
 

+ 1 - 0
core/ConfigurationManager/BrainLoader.py

@@ -38,6 +38,7 @@ class BrainLoader(object):
         dict_brain = cls.get_yaml_config(file_path)
         # create a new brain
         brain = Brain()
+        brain.brain_yaml = dict_brain
         # create list of Synapse
         synapses = list()
         for synapses_dict in dict_brain:

+ 1 - 1
core/MainController.py

@@ -33,7 +33,7 @@ class MainController:
         if self.settings.rest_api.active:
             Utils.print_info("Starting REST API Listening port: %s" % self.settings.rest_api.port)
             app = Flask(__name__)
-            flask_api = FlaskAPI(app, port=self.settings.rest_api.port, brain_file=brain_file)
+            flask_api = FlaskAPI(app, port=self.settings.rest_api.port, brain=self.brain)
             flask_api.start()
 
         # create an order listener object. This last will the trigger callback before starting

+ 2 - 1
core/Models/Brain.py

@@ -1,5 +1,6 @@
 
 class Brain(object):
-    def __init__(self, synapses=None, brain_file=None):
+    def __init__(self, synapses=None, brain_file=None, brain_yaml=None):
         self.synapses = synapses
         self.brain_file = brain_file
+        self.brain_yaml = brain_yaml

+ 5 - 0
core/Models/Event.py

@@ -5,3 +5,8 @@ class Event(object):
     def __str__(self):
         return "%s: period: %s" % (self.__class__.__name__,
                                    self.period)
+
+    def serialize(self):
+        return {
+            'event': self.period
+        }

+ 6 - 0
core/Models/Neuron.py

@@ -4,3 +4,9 @@ class Neuron(object):
     def __init__(self, name=None, parameters=None):
         self.name = name
         self.parameters = parameters
+
+    def serialize(self):
+        return {
+            'name': self.name,
+            'parameters': str(self.parameters)
+        }

+ 5 - 0
core/Models/Order.py

@@ -4,3 +4,8 @@ class Order(object):
 
     def __str__(self):
         return "%s: Sentence: %s" % (self.__class__.__name__, self.sentence)
+
+    def serialize(self):
+        return {
+            'order': self.sentence
+        }

+ 7 - 0
core/Models/Synapse.py

@@ -3,3 +3,10 @@ class Synapse(object):
         self.name = name
         self.neurons = neurons
         self.signals = signals
+
+    def serialize(self):
+        return {
+            'name': self.name,
+            'neurons': [e.serialize() for e in self.neurons],
+            'signals': [e.serialize() for e in self.signals]
+        }

+ 6 - 0
core/OrderAnalyser.py

@@ -30,10 +30,13 @@ class OrderAnalyser:
     def start(self):
         synapses_found = False
         problem_in_neuron_found = False
+        # create a dict of synapses that have benn launched
+        launched_synapses = list()
         for synapse in self.brain.synapses:
             for signal in synapse.signals:
                 if type(signal) == Order:
                     if self._spelt_order_match_brain_order_via_table(signal.sentence, self.order):
+                        launched_synapses.append(synapse)
                         synapses_found = True
                         logger.debug("Order found! Run neurons: %s" % synapse.neurons)
                         Utils.print_success("Order matched in the brain. Running synapse \"%s\"" % synapse.name)
@@ -77,6 +80,9 @@ class OrderAnalyser:
         if not synapses_found:
             Utils.print_info("No synapse match the captured order: %s" % self.order)
 
+        # return the list of launched synapse
+        return launched_synapses
+
     def _associate_order_params_to_values(self, order_to_check):
         """
         Associate the variables from the order to the incoming user order

+ 54 - 7
core/RestAPI/FlaskAPI.py

@@ -1,23 +1,32 @@
 import threading
 
 from flask import jsonify
+from flask import request
+from flask_restful import abort
 
-from core.ConfigurationManager.BrainLoader import BrainLoader
+from core import OrderAnalyser
 from core.RestAPI.utils import requires_auth
 from core.SynapseLauncher import SynapseLauncher
 
 
 class FlaskAPI(threading.Thread):
-    def __init__(self, app, port=5000, brain_file=None):
+    def __init__(self, app, port=5000, brain=None):
+        """
+
+        :param app: Flask API
+        :param port: Port to listen
+        :param brain: Brain object
+        :type brain: Brain
+        """
         super(FlaskAPI, self).__init__()
         self.app = app
         self.port = port
-        self.brain_file = brain_file
-        self.brain_yaml = BrainLoader.get_yaml_config(file_path=self.brain_file)
+        self.brain = brain
 
         self.app.add_url_rule('/synapses/', view_func=self.get_synapses, methods=['GET'])
         self.app.add_url_rule('/synapses/<synapse_name>', view_func=self.get_synapse, methods=['GET'])
         self.app.add_url_rule('/synapses/<synapse_name>', view_func=self.run_synapse, methods=['POST'])
+        self.app.add_url_rule('/order/', view_func=self.run_order, methods=['POST'])
 
     def run(self):
         self.app.run(host='0.0.0.0', port="%s" % int(self.port), debug=True, threaded=True, use_reloader=False)
@@ -28,7 +37,7 @@ class FlaskAPI(threading.Thread):
         :param synapse_name:
         :return:
         """
-        all_synapse = self.brain_yaml
+        all_synapse = self.brain.brain_yaml
         for el in all_synapse:
             print el
             if el[0]["name"] in synapse_name:
@@ -40,7 +49,7 @@ class FlaskAPI(threading.Thread):
         """
         get all synapse
         """
-        data = jsonify(synapses=self.brain_yaml)
+        data = jsonify(synapses=self.brain.brain_yaml)
         return data, 200
 
     @requires_auth
@@ -76,6 +85,44 @@ class FlaskAPI(threading.Thread):
             return jsonify(error=data), 404
 
         # run the synapse
-        SynapseLauncher.start_synapse(synapse_name, brain_file=self.brain_file)
+        SynapseLauncher.start_synapse(synapse_name, brain_file=self.brain.brain_file)
         data = jsonify(synapses=synapse_target)
         return data, 201
+
+    @requires_auth
+    def run_order(self):
+        """
+        Give an order to Kalliope via API like it was from a spoken one
+        Test with curl
+        curl -i --user admin:secret -H "Content-Type: application/json" -X POST -d '{"order":"my order"}' http://localhost:5000/order
+        In case of quotes in the order or accents, use a file
+        cat post.json:
+        {"order":"j'aime"}
+        curl -i --user admin:secret -H "Content-Type: application/json" -X POST --data @post.json http://localhost:5000/order/
+        :return:
+        """
+        if not request.get_json() or 'order' not in request.get_json():
+            abort(400)
+
+        order = request.get_json('order')
+        if order is not None:
+            # get the order
+            order_to_run = order["order"]
+            oa = OrderAnalyser(order=order_to_run, brain=self.brain)
+            launched_synapses = oa.start()
+
+            if launched_synapses:
+                # if the list is not empty, we have launched one or more synapses
+                data = jsonify(synapses=[e.serialize() for e in launched_synapses])
+                return data, 201
+            else:
+                data = {
+                    "error": "The given order doesn't match any synapses"
+                }
+                return jsonify(error=data), 400
+
+        else:
+            data = {
+                "error": "order cannot be null"
+            }
+            return jsonify(error=data), 400

+ 10 - 34
test.py

@@ -1,55 +1,31 @@
 # coding: utf8
 import logging
 
+from flask import Flask
+
 from core import OrderAnalyser
 from core import Utils
 from core.ConfigurationManager import SettingLoader
 from core.ConfigurationManager.BrainLoader import BrainLoader
 from core.Players import Mplayer
+from core.RestAPI.FlaskAPI import FlaskAPI
 
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger.setLevel(logging.DEBUG)
 
-# order = "quelle heure est-il"
-# oa = OrderAnalyser(order=order)
-# oa.start()
-
-
 
 brain = BrainLoader.get_brain()
+#
+# order = "bonjour"
+# oa = OrderAnalyser(order=order, brain=brain)
+# oa.start()
 
-order = "bonjour"
-
-oa = OrderAnalyser(order=order, brain=brain)
-
-oa.start()
 
 
-# settings = SettingLoader.get_settings()
-#
-# tts_name_to_use = "pico2wave"
-# sentence_to_say = "bonjour monsieur, je m'appelle Kalliopé"
-#
-#
-# def _get_tts_object_from_name(tts_name_to_use):
-#     """
-#     Return a Tts object from the nae of the Tss. Get parameters in settings
-#     :param tts_name_to_use:
-#     :return:
-#     """
-#     return next((x for x in settings.ttss if x.name == tts_name_to_use), None)
-#
-#
-# # create a tts object from the tts the user want to user
-# tts_object = _get_tts_object_from_name(tts_name_to_use)
-#
-# if tts_object is None:
-#     print "TTS module name %s not found in settings" % tts_name_to_use
-#
-# else:
-#     tts_module_instance = Utils.get_dynamic_class_instantiation("tts", tts_object.name.capitalize(), tts_object.parameters)
-#     tts_module_instance.say(sentence_to_say)
+app = Flask(__name__)
+flask_api = FlaskAPI(app, port=5000, brain=brain)
+flask_api.start()