Repository: rorodata/firefly

Issue:
Add the ability to find all the available functions served by a firefly service
The user should be able to find the functions available in a firefly service. Right now a server supports only one function, but that is going to change soon. 

So, we need to identify the right way to expose the list of functions. It could either be on `/` or some other endpoint like `/_list`. Look at the other RPC implementations and see how they work. We don't have to follow them, but that would give a good idea. It would be better to provide the docstring and argument names along with the function listing.

Please discuss the plan here before implementing.

Discussion:

Patch:
diff --git a/firefly/app.py b/firefly/app.py
index c317648..d3c0da8 100644
--- a/firefly/app.py
+++ b/firefly/app.py
@@ -3,13 +3,28 @@ from webob.exc import HTTPNotFound
 import json
 from .validator import validate_args, ValidationError
 from .utils import json_encode
+from .version import __version__
 
 class Firefly(object):
     def __init__(self):
         self.mapping = {}
+        self.add_route('/', self.generate_index,internal=True)
 
-    def add_route(self, path, function, **kwargs):
-        self.mapping[path] = FireflyFunction(function, **kwargs)
+    def add_route(self, path, function, function_name=None, **kwargs):
+        self.mapping[path] = FireflyFunction(function, function_name, **kwargs)
+
+    def generate_function_list(self):
+        return {f.name: {"path": path, "doc": f.doc}
+                for path, f in self.mapping.items()
+                if f.options.get("internal") != True}
+
+    def generate_index(self):
+        help_dict = {
+            "app": "firefly",
+            "version": __version__,
+            "functions": self.generate_function_list()
+            }
+        return help_dict
 
     def __call__(self, environ, start_response):
         request = Request(environ)
@@ -25,10 +40,16 @@ class Firefly(object):
 
 
 class FireflyFunction(object):
-    def __init__(self, function, **kwargs):
+    def __init__(self, function, function_name=None, **options):
         self.function = function
+        self.options = options
+        self.name = function_name or function.__name__
+        self.doc = function.__doc__ or ""
 
     def __call__(self, request):
+        if self.options.get("internal", False):
+            return self.make_response(self.function())
+
         kwargs = self.get_inputs(request)
         try:
             validate_args(self.function, kwargs)
diff --git a/firefly/main.py b/firefly/main.py
index 37eb583..dd4a4fb 100644
--- a/firefly/main.py
+++ b/firefly/main.py
@@ -24,7 +24,7 @@ def load_functions(function_specs):
 
 def add_routes(app, functions):
     for name, function in functions:
-        app.add_route('/'+name, function)
+        app.add_route('/'+name, function, name)
 
 def main():
     # ensure current directory is added to sys.path


Test patch:
diff --git a/tests/test_app.py b/tests/test_app.py
index edad1b9..183c914 100644
--- a/tests/test_app.py
+++ b/tests/test_app.py
@@ -1,13 +1,41 @@
 from webob import Request, Response
-from firefly.app import FireflyFunction
+from firefly.app import Firefly, FireflyFunction
 
 def square(a):
+    '''Computes square'''
     return a**2
 
+class TestFirefly:
+    def test_generate_function_list(self):
+        firefly = Firefly()
+        assert firefly.generate_function_list() == {}
+
+        firefly.add_route("/square", square, "square")
+        returned_dict = {
+                "square": {
+                    "path": "/square",
+                    "doc": "Computes square"
+                }
+            }
+        assert firefly.generate_function_list() == returned_dict
+
+    def test_generate_function_list_for_func_name(self):
+        firefly = Firefly()
+        firefly.add_route("/sq2", square, "sq")
+        returned_dict = {
+                "sq": {
+                    "path": "/sq2",
+                    "doc": "Computes square"
+                }
+            }
+        assert firefly.generate_function_list() == returned_dict
+
+
+
 class TestFireflyFunction:
     def test_call(self):
         func = FireflyFunction(square)
-        request = Request.blank("/", POST='{"a": 3}')
+        request = Request.blank("/square", POST='{"a": 3}')
         response = func(request)
         assert response.status == '200 OK'
         assert response.text == '9'