anandology: The argument values are not constants, so they should be in lowercase.

On 3:33PM, Sat, Jun 17, 2017 Nabarun Pal <notifications@github.com> wrote:

> *@palnabarun* commented on this pull request.
> ------------------------------
>
> In firefly/main.py
> <https://github.com/rorodata/firefly/pull/15#discussion_r122564373>:
>
> >
>  def main():
>      # ensure current directory is added to sys.path
>      if "" not in sys.path:
>          sys.path.insert(0, "")
>
>      args = parse_args()
> -    functions = load_functions(args.functions)
> +    print(len(args.functions), args.CONFIG_FILE)
> +    if (len(args.functions) > 0 and args.CONFIG_FILE) or (len(args.functions) == 0 and not args.CONFIG_FILE):
> +        raise FireflyError("Invalid arguments provided. Please specify either a config file or a list of functions.")
>
> I thought about that. But the current one seemed more verbose to me. Both
> are okay with me.
>
> —
> You are receiving this because you commented.
>
>
> Reply to this email directly, view it on GitHub
> <https://github.com/rorodata/firefly/pull/15#discussion_r122564373>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AAAdkUVfpndTH0tzinN7_Zo2Z9XKCmLfks5sE5H0gaJpZM4N8vrJ>
> .
>

diff --git a/firefly/main.py b/firefly/main.py
index dd4a4fb..8db6caf 100644
--- a/firefly/main.py
+++ b/firefly/main.py
@@ -1,30 +1,47 @@
+import os
 import sys
 import argparse
 import importlib
+import yaml
 from .app import Firefly
 from .server import FireflyServer
+from .validator import ValidationError, FireflyError
 
 def parse_args():
     p = argparse.ArgumentParser()
     p.add_argument("-b", "--bind", dest="ADDRESS", default="127.0.0.1:8000")
-    p.add_argument("functions", nargs='+', help="functions to serve")
+    p.add_argument("-c", "--config", dest="config_file", default=None)
+    p.add_argument("functions", nargs='*', help="functions to serve")
     return p.parse_args()
 
-def load_function(function_spec):
+def load_function(function_spec, path=None, name=None):
     if "." not in function_spec:
         raise Exception("Invalid function, please specify it as module.function")
 
     mod_name, func_name = function_spec.rsplit(".", 1)
     mod = importlib.import_module(mod_name)
     func = getattr(mod, func_name)
-    return (func_name, func)
+    path = path or "/"+func_name
+    name = name or func_name
+    return (path, name, func)
 
 def load_functions(function_specs):
     return [load_function(function_spec) for function_spec in function_specs]
 
+def parse_config_file(config_file):
+    if not os.path.exists(config_file):
+        raise FireflyError("Specified config file does not exist.")
+    with open(config_file) as f:
+        config_dict = yaml.safe_load(f)
+    return config_dict
+
+def parse_config_data(config_dict):
+    return [(load_function(f["function"], path=f["path"], name=name, ))
+            for name, f in config_dict["functions"].items()]
+
 def add_routes(app, functions):
-    for name, function in functions:
-        app.add_route('/'+name, function, name)
+    for path, name, function in functions:
+        app.add_route(path, function, name)
 
 def main():
     # ensure current directory is added to sys.path
@@ -32,7 +49,14 @@ def main():
         sys.path.insert(0, "")
 
     args = parse_args()
-    functions = load_functions(args.functions)
+
+    if (args.functions and args.CONFIG_FILE) or (not args.functions and not args.CONFIG_FILE):
+        raise FireflyError("Invalid arguments provided. Please specify either a config file or a list of functions.")
+
+    if len(args.functions):
+        functions = load_functions(args.functions)
+    elif args.CONFIG_FILE:
+        functions = parse_config_data(parse_config_file(args.CONFIG_FILE))
 
     app = Firefly()
     add_routes(app, functions)
diff --git a/firefly/validator.py b/firefly/validator.py
index b1a2eaf..bb3a477 100644
--- a/firefly/validator.py
+++ b/firefly/validator.py
@@ -8,6 +8,9 @@ else:
 class ValidationError(Exception):
     pass
 
+class FireflyError(Exception):
+    pass
+
 def validate_args(function, kwargs):
     function_signature = signature(function)
     try:
diff --git a/requirements.txt b/requirements.txt
index 8ec0719..28de9ef 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,5 @@
 gunicorn==19.7.1
 WebOb==1.7.2
 requests==2.18.1
+PyYAML==3.12
 funcsigs==1.0.2 ; python_version < '3'

Make firefly support a config file
It should be possible to specify the specs in an yaml config file instead of passing all command-line arguments. Plan for more options for each function, like output-content-type, input-content-type, endpoint etc.

It would be better to have a version field to the config file so that we can handle old versions if we change the format in future.
diff --git a/tests/test_main.py b/tests/test_main.py
index 7ecfbf2..723def1 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -3,6 +3,7 @@ from firefly.main import load_function
 
 def test_load_functions():
     os.path.exists2 = os.path.exists
-    name, func = load_function("os.path.exists2")
+    path, name, func = load_function("os.path.exists2")
+    assert path == "/exists2"
     assert name == "exists2"
     assert func == os.path.exists

pip install -e .[dev]
parse_log_pytest
requirements.txt
pyyaml pytest>=3.1.1
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi==2025.1.31
chardet==3.0.4
exceptiongroup==1.2.2
-e git+https://github.com/rorodata/firefly.git@6fb71b3970af79aad79f4793e0d9e3304420dd14#egg=Firefly
gunicorn==19.7.1
idna==2.5
iniconfig==2.1.0
packaging==24.2
pluggy==1.5.0
pytest==8.3.5
PyYAML==6.0.2
requests==2.18.1
tomli==2.2.1
urllib3==1.21.1
WebOb==1.7.2

name: firefly
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - certifi==2025.1.31
      - chardet==3.0.4
      - exceptiongroup==1.2.2
      - gunicorn==19.7.1
      - idna==2.5
      - iniconfig==2.1.0
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - pyyaml==6.0.2
      - requests==2.18.1
      - tomli==2.2.1
      - urllib3==1.21.1
      - webob==1.7.2
prefix: /opt/conda/envs/firefly

tests/test_main.py::test_load_functions
firefly/validator.py requirements.txt firefly/main.py
firefly/validator.py requirements.txt firefly/main.py