python: Support application factories

Adds support for the app factory pattern to the Python language module.
A factory is a callable that returns a WSGI or ASGI application object.

Unit does not support passing arguments to factories.

Setting the `factory` option to `true` instructs Unit to treat the
configured `callable` as a factory.

For example:

    "my-app": {
        "type": "python",
        "path": "/srv/www/",
        "module": "hello",
        "callable": "create_app",
        "factory": true
    }

This is similar to other WSGI / ASGI servers. E.g.,

    $ uvicorn --factory hello:create_app
    $ gunicorn 'hello:create_app()'

The factory setting defaults to false.

Closes: https://github.com/nginx/unit/issues/1106
Link: <https://github.com/nginx/unit/pull/1336#issuecomment-2179381605>
[ Commit message - Dan / Minor code tweaks - Andrew ]
Signed-off-by: Andrew Clayton <a.clayton@nginx.com>
This commit is contained in:
Gourav 2024-06-26 11:14:50 +05:30 committed by Andrew Clayton
parent d62a5e2c37
commit a9aa9e76db
2 changed files with 37 additions and 1 deletions

View file

@ -841,6 +841,11 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = {
.type = NXT_CONF_VLDT_STRING, .type = NXT_CONF_VLDT_STRING,
.validator = nxt_conf_vldt_targets_exclusive, .validator = nxt_conf_vldt_targets_exclusive,
.u.string = "callable", .u.string = "callable",
}, {
.name = nxt_string("factory"),
.type = NXT_CONF_VLDT_BOOLEAN,
.validator = nxt_conf_vldt_targets_exclusive,
.u.string = "factory",
}, { }, {
.name = nxt_string("prefix"), .name = nxt_string("prefix"),
.type = NXT_CONF_VLDT_STRING, .type = NXT_CONF_VLDT_STRING,
@ -865,6 +870,9 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[] = {
}, { }, {
.name = nxt_string("callable"), .name = nxt_string("callable"),
.type = NXT_CONF_VLDT_STRING, .type = NXT_CONF_VLDT_STRING,
}, {
.name = nxt_string("factory"),
.type = NXT_CONF_VLDT_BOOLEAN,
}, { }, {
.name = nxt_string("prefix"), .name = nxt_string("prefix"),
.type = NXT_CONF_VLDT_STRING, .type = NXT_CONF_VLDT_STRING,
@ -883,6 +891,9 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_notargets_members[] = {
}, { }, {
.name = nxt_string("callable"), .name = nxt_string("callable"),
.type = NXT_CONF_VLDT_STRING, .type = NXT_CONF_VLDT_STRING,
}, {
.name = nxt_string("factory"),
.type = NXT_CONF_VLDT_BOOLEAN,
}, { }, {
.name = nxt_string("prefix"), .name = nxt_string("prefix"),
.type = NXT_CONF_VLDT_STRING, .type = NXT_CONF_VLDT_STRING,

View file

@ -403,11 +403,13 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target,
char *callable, *module_name; char *callable, *module_name;
PyObject *module, *obj; PyObject *module, *obj;
nxt_str_t str; nxt_str_t str;
nxt_bool_t is_factory = 0;
nxt_conf_value_t *value; nxt_conf_value_t *value;
static nxt_str_t module_str = nxt_string("module"); static nxt_str_t module_str = nxt_string("module");
static nxt_str_t callable_str = nxt_string("callable"); static nxt_str_t callable_str = nxt_string("callable");
static nxt_str_t prefix_str = nxt_string("prefix"); static nxt_str_t prefix_str = nxt_string("prefix");
static nxt_str_t factory_flag_str = nxt_string("factory");
module = obj = NULL; module = obj = NULL;
@ -449,7 +451,30 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target,
goto fail; goto fail;
} }
if (nxt_slow_path(PyCallable_Check(obj) == 0)) { value = nxt_conf_get_object_member(conf, &factory_flag_str, NULL);
if (value != NULL) {
is_factory = nxt_conf_get_boolean(value);
}
if (is_factory) {
if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
nxt_alert(task,
"factory \"%s\" in module \"%s\" "
"can not be called to fetch callable",
callable, module_name);
goto fail;
}
obj = PyObject_CallObject(obj, NULL);
if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
nxt_alert(task,
"factory \"%s\" in module \"%s\" "
"did not return callable object",
callable, module_name);
goto fail;
}
} else if (nxt_slow_path(PyCallable_Check(obj) == 0)) {
nxt_alert(task, "\"%s\" in module \"%s\" is not a callable object", nxt_alert(task, "\"%s\" in module \"%s\" is not a callable object",
callable, module_name); callable, module_name);
goto fail; goto fail;