2

My problem is as follows. There is some function in my library:

def some_func(arg1=1):
    pass

which I want to use in three ways:

Using import statements in other python scripts:

import my_library
my_library.some_func()

Expose using a CLI interface with click.

@click.group()
@click.option('--arg1', default=1)
def some_func(arg1):
    pass

Expose using a web interface with Flask.

@app.route('/endpoint', defaults={'arg1': 1})
def some_func(arg1):
    pass

But how can I structure this efficiently without duplicating too much code?

Is it possible to merge all three? I tried (variations of) the following, which fails:

@click.group()
@click.option('--arg1', default=1)
@app.route('/endpoint', defaults={'arg1': 1})
def some_func(arg1=1):
    pass

Or do I really need the 3 different functions as defined above?

And if so, how should I go about the default values?

Is accessing a global variable at all places the best way to do this?

2
  • You should use three entry points, and there is nothing wrong with shared constants being used as defaults. Commented May 6, 2019 at 15:33
  • Thanks. But to my eye it seems so ugly to use constants for default values, but perhaps that is because I personally have not seen that "in-the-wild" yet. Therefore I try to find out if there is some better approach to creating these kind of apps.. But I have a hard time finding anything about it. Commented May 6, 2019 at 15:46

2 Answers 2

2

You can use the hug library, which was made precisely for this use case.

Example:


"""An example of writing an API to scrape hacker news once, and then enabling usage everywhere"""
import hug
import requests


@hug.local()
@hug.cli()
@hug.get()
def top_post(section: hug.types.one_of(('news', 'newest', 'show'))='news'):
    """Returns the top post from the provided section"""
    content = requests.get('https://news.ycombinator.com/{0}'.format(section)).content
    text = content.decode('utf-8')
    return text.split('<tr class=\'athing\'>')[1].split("<a href")[1].split(">")[1].split("<")[0]
Sign up to request clarification or add additional context in comments.

1 Comment

Seems interesting, thanks for pointing me towards this resource!
1

Since decorator syntax is just a shortcut for function application, you start with defining your function in your library. Then your click example becomes:

import my_library

click.group()(click.option('--arg1', default=1)(my_library.some_func))

and your Flask example becomes

import library

app.route('/endpoint', defaults={'arg1': 1})(my_library.some_func)

(I know for Flask, the return value of the decorator isn't important; I assume the same is true for click.)

This assumes you aren't trying to use the same script as both a command-line tool and a Flask app; that doesn't make much sense IMO.


As far as simplifying the default value, nothing good comes to mind. Click, Flask, and your function have three different ways of indicting default value; the only thing in common is the actual value of that default. You might do something like this. First, in my_library.py:

some_func_default = 1

def some_func(arg=None):  # Or some other sentinel
    if arg is None:
        arg = some_func_default

Then in your two other scripts:

click.group()(click.option('--arg1', default=my_library.some_func_default)(my_library.some_func))

and

app.route('/endpoint', defaults={'arg1': my_library.some_func_default})(my_library.some_func)

Granted, you could use the inspect module to extract the default value from your original definition of some_func, but that doesn't help with the difference in how Click and Flask set default values for their entry points.

4 Comments

Thanks for your answer. Your assumption is correct in that these are in fact separate scripts. But what would then be a good option for the default values? Using global variables from my_library?
@MuadDev That seems as good as any. The problem is with how Click and Flask specify defaults, not with how to discover the default value.
Thanks for your additional advice. But could you explain why you'd use a sentinel value in the "my_library" definition and not just directly using the global variable like: some_func(arg=some_func_default)?
I'm generalizing early, in case the default value is something mutable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.