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
 ## 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
 ## Curl examples
 
 
 >**Note:** --user is only needed if `password_protected` is True
 >**Note:** --user is only needed if `password_protected` is True
 
 
-### Get all synapse
+### List synapses
 
 
 Normal response codes: 200
 Normal response codes: 200
 Error response codes: unauthorized(401), itemNotFound(404)
 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
 Normal response codes: 200
 Error response codes: unauthorized(401), itemNotFound(404)
 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
 Normal response codes: 201
 Error response codes: unauthorized(401), itemNotFound(404)
 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                                                                |
 | [STT](Docs/stt.md)                 | Speech to text configuration                                                                |
 | [TTS](Docs/tts.md)                 | Text to speech configuration                                                                |
 | [TTS](Docs/tts.md)                 | Text to speech configuration                                                                |
 | [Triggers](Docs/trigger.md)        | Magic hotword engine used to make Kalliope listtening for an order                          |
 | [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
 ## Contributing
 
 

+ 1 - 0
core/ConfigurationManager/BrainLoader.py

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

+ 1 - 1
core/MainController.py

@@ -33,7 +33,7 @@ class MainController:
         if self.settings.rest_api.active:
         if self.settings.rest_api.active:
             Utils.print_info("Starting REST API Listening port: %s" % self.settings.rest_api.port)
             Utils.print_info("Starting REST API Listening port: %s" % self.settings.rest_api.port)
             app = Flask(__name__)
             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()
             flask_api.start()
 
 
         # create an order listener object. This last will the trigger callback before starting
         # 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):
 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.synapses = synapses
         self.brain_file = brain_file
         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):
     def __str__(self):
         return "%s: period: %s" % (self.__class__.__name__,
         return "%s: period: %s" % (self.__class__.__name__,
                                    self.period)
                                    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):
     def __init__(self, name=None, parameters=None):
         self.name = name
         self.name = name
         self.parameters = parameters
         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):
     def __str__(self):
         return "%s: Sentence: %s" % (self.__class__.__name__, self.sentence)
         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.name = name
         self.neurons = neurons
         self.neurons = neurons
         self.signals = signals
         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):
     def start(self):
         synapses_found = False
         synapses_found = False
         problem_in_neuron_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 synapse in self.brain.synapses:
             for signal in synapse.signals:
             for signal in synapse.signals:
                 if type(signal) == Order:
                 if type(signal) == Order:
                     if self._spelt_order_match_brain_order_via_table(signal.sentence, self.order):
                     if self._spelt_order_match_brain_order_via_table(signal.sentence, self.order):
+                        launched_synapses.append(synapse)
                         synapses_found = True
                         synapses_found = True
                         logger.debug("Order found! Run neurons: %s" % synapse.neurons)
                         logger.debug("Order found! Run neurons: %s" % synapse.neurons)
                         Utils.print_success("Order matched in the brain. Running synapse \"%s\"" % synapse.name)
                         Utils.print_success("Order matched in the brain. Running synapse \"%s\"" % synapse.name)
@@ -77,6 +80,9 @@ class OrderAnalyser:
         if not synapses_found:
         if not synapses_found:
             Utils.print_info("No synapse match the captured order: %s" % self.order)
             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):
     def _associate_order_params_to_values(self, order_to_check):
         """
         """
         Associate the variables from the order to the incoming user order
         Associate the variables from the order to the incoming user order

+ 54 - 7
core/RestAPI/FlaskAPI.py

@@ -1,23 +1,32 @@
 import threading
 import threading
 
 
 from flask import jsonify
 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.RestAPI.utils import requires_auth
 from core.SynapseLauncher import SynapseLauncher
 from core.SynapseLauncher import SynapseLauncher
 
 
 
 
 class FlaskAPI(threading.Thread):
 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__()
         super(FlaskAPI, self).__init__()
         self.app = app
         self.app = app
         self.port = port
         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/', 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.get_synapse, methods=['GET'])
         self.app.add_url_rule('/synapses/<synapse_name>', view_func=self.run_synapse, methods=['POST'])
         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):
     def run(self):
         self.app.run(host='0.0.0.0', port="%s" % int(self.port), debug=True, threaded=True, use_reloader=False)
         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:
         :param synapse_name:
         :return:
         :return:
         """
         """
-        all_synapse = self.brain_yaml
+        all_synapse = self.brain.brain_yaml
         for el in all_synapse:
         for el in all_synapse:
             print el
             print el
             if el[0]["name"] in synapse_name:
             if el[0]["name"] in synapse_name:
@@ -40,7 +49,7 @@ class FlaskAPI(threading.Thread):
         """
         """
         get all synapse
         get all synapse
         """
         """
-        data = jsonify(synapses=self.brain_yaml)
+        data = jsonify(synapses=self.brain.brain_yaml)
         return data, 200
         return data, 200
 
 
     @requires_auth
     @requires_auth
@@ -76,6 +85,44 @@ class FlaskAPI(threading.Thread):
             return jsonify(error=data), 404
             return jsonify(error=data), 404
 
 
         # run the synapse
         # 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)
         data = jsonify(synapses=synapse_target)
         return data, 201
         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
 # coding: utf8
 import logging
 import logging
 
 
+from flask import Flask
+
 from core import OrderAnalyser
 from core import OrderAnalyser
 from core import Utils
 from core import Utils
 from core.ConfigurationManager import SettingLoader
 from core.ConfigurationManager import SettingLoader
 from core.ConfigurationManager.BrainLoader import BrainLoader
 from core.ConfigurationManager.BrainLoader import BrainLoader
 from core.Players import Mplayer
 from core.Players import Mplayer
+from core.RestAPI.FlaskAPI import FlaskAPI
 
 
 logging.basicConfig()
 logging.basicConfig()
 logger = logging.getLogger("kalliope")
 logger = logging.getLogger("kalliope")
 logger.setLevel(logging.DEBUG)
 logger.setLevel(logging.DEBUG)
 
 
-# order = "quelle heure est-il"
-# oa = OrderAnalyser(order=order)
-# oa.start()
-
-
 
 
 brain = BrainLoader.get_brain()
 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()