Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python: Add 'factory' support #1336

Merged

Conversation

gourav-kandoria
Copy link
Contributor

@gourav-kandoria gourav-kandoria commented Jun 19, 2024

This pr adds the factory support in python languange. By introducing new option as "factory" which can be set as true or false, if true, the callable will be interpreted as factory otherwise not

@gourav-kandoria
Copy link
Contributor Author

gourav-kandoria commented Jun 19, 2024

There are the config and module which I used to test
config

{
    "applications": {
        "python-app": {
            "type": "python",
            "processes": 2,
            "path": "/www/",
            "targets": {
                "front": {
                    "module": "app",
                    "callable": "app_factory",
                    "factory": true
                },
                "back": {
                    "module": "app",
                    "callable": "app"
                },
                "back_door": {
                    "module": "app",
                    "callable": "app",
                    "factory": false
                }
            }
        }
    },
    "listeners": {
        "127.0.0.1:8100": {
            "pass": "applications/python-app/front"
        },
        "127.0.0.1:8200": {
            "pass": "applications/python-app/back"
        },
        "127.0.0.1:8300": {
            "pass": "applications/python-app/back_door"
        }
    }
}

Python module:

def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])

    return [b'Hell WSGI', b'Hello world']

def application_f(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [b'from app created from factory']


def app_factory():
    return application_f

@ac000
Copy link
Member

ac000 commented Jun 19, 2024

Please don't open a new pull-request just to make changes, we loose all the previous context!

Instead, make changes locally then force push them up. It'll make everyone's life easier...

OK, I see this is now taking a completely different tact...

@gourav-kandoria gourav-kandoria changed the title change in python config validation and python set target Python: Add 'factory' support Jun 19, 2024
@gourav-kandoria
Copy link
Contributor Author

Please don't open a new pull-request just to make changes, we loose all the previous context!

Instead, make changes locally then force push them up. It'll make everyone's life easier...

OK, I see this is now taking a completely different tact...

sure will take care of this. actually, the changes were very much different from each other so thought of creating new pr itself

src/nxt_conf_validation.c Outdated Show resolved Hide resolved
src/nxt_conf_validation.c Outdated Show resolved Hide resolved
src/nxt_conf_validation.c Outdated Show resolved Hide resolved
src/python/nxt_python.c Outdated Show resolved Hide resolved
src/python/nxt_python.c Outdated Show resolved Hide resolved
src/python/nxt_python.c Outdated Show resolved Hide resolved
src/python/nxt_python.c Outdated Show resolved Hide resolved
src/python/nxt_python.c Outdated Show resolved Hide resolved
src/python/nxt_python.c Outdated Show resolved Hide resolved
@gourav-kandoria
Copy link
Contributor Author

@ac000 changes made as suggested

@ac000
Copy link
Member

ac000 commented Jun 20, 2024

@gourav-kandoria Thanks!

Let me squash those commits down and see what we end up with.. I'll push up the result for you to check...

@ac000
Copy link
Member

ac000 commented Jun 20, 2024

Hmm, looking at the nxt_conf_vldt_python_members[] array

static nxt_conf_vldt_object_t  nxt_conf_vldt_python_members[] = {               
    {                                                                           
        .name       = nxt_string("module"),                                     
        .type       = NXT_CONF_VLDT_STRING,                                     
        .validator  = nxt_conf_vldt_targets_exclusive,                          
        .u.string   = "module",                                                 
    }, {                                                                        
        .name       = nxt_string("callable"),                                   
        .type       = NXT_CONF_VLDT_STRING,                                     
        .validator  = nxt_conf_vldt_targets_exclusive,                          
        .u.string   = "callable",                                               
    }, {                                                                        
        .name       = nxt_string("factory"),                                    
        .type       = NXT_CONF_VLDT_BOOLEAN                                     
    }, {                                                                        
        .name       = nxt_string("prefix"),                                     
        .type       = NXT_CONF_VLDT_STRING,                                     
        .validator  = nxt_conf_vldt_targets_exclusive,                          
        .u.string   = "prefix",                                                 
    }, {                                                                        
        .name       = nxt_string("targets"),                                    
        .type       = NXT_CONF_VLDT_OBJECT,                                     
        .validator  = nxt_conf_vldt_targets,                                    
        .u.members  = nxt_conf_vldt_python_target_members                       
    },                                                                          
                                                                                
    NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members)                     
};

It seems none of these options can be used when "targets" is specified, so I will go ahead and add the .validator member to the new factory entry...

I also need to check what the u.string member is used for...

@ac000
Copy link
Member

ac000 commented Jun 20, 2024

OK, so I'm pretty sure the u.string is what's passed through to the nxt_conf_vldt_targets_exclusive() function as the data argument, so I''ll add that also.

@ac000 ac000 linked an issue Jun 20, 2024 that may be closed by this pull request
@ac000 ac000 force-pushed the factory_support_in_python_config branch 2 times, most recently from 1144039 to ca04789 Compare June 20, 2024 20:25
@ac000
Copy link
Member

ac000 commented Jun 20, 2024

@gourav-kandoria Please check over the updated patch.

Please also check over my attempt at explaining this in the commit message. I think I'm still missing a concrete reason why this is useful.

Commit message and diff of my changes

commit ca04789668ed52234c4c749a209d99c3b306c07f
Author: Gourav <[email protected]>
Date:   Thu Jun 20 01:10:53 2024 +0530

    python: Add 'factory' support
    
    This adds support for 'Application Factories' to the Python language
    module.
    
    This essentially allows you to run some code _before_ the main
    application is loaded. This is similar to the '--factory' setting in
    Uvicorn.
    
    It can be configured like
    
      "python-app": {
          "type": "python",
          "path": "/srv/www",
          "module": "create_app",
          "factory": true
      }
    
    The factory setting defaults to false.
    
    Closes: https://github.com/nginx/unit/issues/1106
    [ Commit subject, message and some minor code tweaks - Andrew ]
    Signed-off-by: Andrew Clayton <[email protected]>
diff --git ./src/nxt_conf_validation.c ./src/nxt_conf_validation.c
index ed3aa735..96592652 100644
--- ./src/nxt_conf_validation.c
+++ ./src/nxt_conf_validation.c
@@ -840,7 +840,9 @@ static nxt_conf_vldt_object_t  nxt_conf_vldt_python_members[] = {
         .u.string   = "callable",
     }, {
         .name       = nxt_string("factory"),
-        .type       = NXT_CONF_VLDT_BOOLEAN
+        .type       = NXT_CONF_VLDT_BOOLEAN,
+        .validator  = nxt_conf_vldt_targets_exclusive,
+        .u.string   = "factory",
     }, {
         .name       = nxt_string("prefix"),
         .type       = NXT_CONF_VLDT_STRING,
diff --git ./src/python/nxt_python.c ./src/python/nxt_python.c
index 9cb62878..4a08cc64 100644
--- ./src/python/nxt_python.c
+++ ./src/python/nxt_python.c
@@ -403,6 +403,7 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target,
     char              *callable, *module_name;
     PyObject          *module, *obj;
     nxt_str_t         str;
+    nxt_bool_t        is_factory = 0;
     nxt_conf_value_t  *value;
 
     static nxt_str_t  module_str = nxt_string("module");
@@ -451,25 +452,27 @@ nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target,
     }
 
     value = nxt_conf_get_object_member(conf, &factory_flag_str, NULL);
-    nxt_bool_t is_factory = 0;
     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);
+            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);
+            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",
                   callable, module_name);

@callahad
Copy link
Collaborator

@ac000 I'm happy to take a crack at explaining it this afternoon. The factory stuff will make sense to Pythonistas.

@callahad
Copy link
Collaborator

The Flask docs call out two main use cases:

if you move the creation of [the application object] into a function, you can then create multiple instances of this app later.

So why would you want to do this?

  1. Testing. You can have instances of the application with different settings to test every case.
  2. Multiple instances. Imagine you want to run different versions of the same application. Of course you could have multiple instances with different configs set up in your webserver, but if you use factories, you can have multiple instances of the same application running in the same application process which can be handy.

So if you've structured your application in that way, for those reasons, it'd be really handy to be able to run it in Unit as well :)

@callahad
Copy link
Collaborator

@ac000 here's my crack at a commit message:

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
[ Commit subject, message and some minor code tweaks - Andrew ]
Signed-off-by: Andrew Clayton <[email protected]>

@ac000 ac000 force-pushed the factory_support_in_python_config branch from ca04789 to cf8620e Compare June 21, 2024 14:56
@ac000
Copy link
Member

ac000 commented Jun 21, 2024

1:  ca047896 ! 1:  cf8620e5 python: Add 'factory' support
    @@ Metadata
     Author: Gourav <[email protected]>
     
      ## Commit message ##
    -    python: Add 'factory' support
    +    python: Support application factories
     
    -    This adds support for 'Application Factories' to the Python language
    -    module.
    +    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.
     
    -    This essentially allows you to run some code _before_ the main
    -    application is loaded. This is similar to the '--factory' setting in
    -    Uvicorn.
    +    Unit does not support passing arguments to factories.
     
    -    It can be configured like
    +    Setting the `factory` option to `true` instructs Unit to treat the
    +    configured `callable` as a factory.
     
    -      "python-app": {
    -          "type": "python",
    -          "path": "/srv/www",
    -          "module": "create_app",
    -          "factory": true
    -      }
    +    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
    -    [ Commit subject, message and some minor code tweaks - Andrew ]
    +    [ Commit message - Dan / Minor code tweaks - Andrew ]
         Signed-off-by: Andrew Clayton <[email protected]>
     
      ## src/nxt_conf_validation.c ##

@ac000
Copy link
Member

ac000 commented Jun 21, 2024

@andrey-zelenkov

Does this warrant adding a test case for the new Python language module "factory" boolean setting, it defaults to false with no change in behaviour. Setting it to true, well best read the commit message and see the example from above ;)

We'll also need to update the docs...

ac000 pushed a commit to gourav-kandoria/unit that referenced this pull request Jun 21, 2024
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: nginx#1106
Link: <nginx#1336 (comment)>
[ Commit message - Dan / Minor code tweaks - Andrew ]
Signed-off-by: Andrew Clayton <[email protected]>
@ac000 ac000 force-pushed the factory_support_in_python_config branch from cf8620e to fcf590c Compare June 21, 2024 15:16
@ac000
Copy link
Member

ac000 commented Jun 21, 2024

  • Add a link to the example config and app above
$ git range-diff cf8620e5...fcf590cd
1:  cf8620e5 ! 1:  fcf590cd python: Support application factories
    @@ Commit message
         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 <[email protected]>
     

@callahad
Copy link
Collaborator

Definitely needs docs, and I'd strongly prefer at least a simple WSGI test to be included in this patch before we merge.

@gourav-kandoria
Copy link
Contributor Author

The Flask docs call out two main use cases:

if you move the creation of [the application object] into a function, you can then create multiple instances of this app later.
So why would you want to do this?

  1. Testing. You can have instances of the application with different settings to test every case.
  2. Multiple instances. Imagine you want to run different versions of the same application. Of course you could have multiple instances with different configs set up in your webserver, but if you use factories, you can have multiple instances of the same application running in the same application process which can be handy.

So if you've structured your application in that way, for those reasons, it'd be really handy to be able to run it in Unit as well :)

@callahad, I guess, It should be better, if we are able to give support of arguments to pass to factory, Because, if someone is using factory, they might be using it for the purpose of being able to get any specific callable from the factory. Otherwise, factory without arguments is functionally equivalent to just callable option.
These are my opinion. I hope I am not missing anything important here

@ac000
Copy link
Member

ac000 commented Jun 24, 2024

I was wondering why you couldn't just have your callable call something else...

@gourav-kandoria
Copy link
Contributor Author

gourav-kandoria commented Jun 24, 2024

I was wondering why you couldn't just have your callable call something else...

well, the information regarding what to call has to come from somewhere. so factory will return the callable with this info preconfigured. So, I think that's a choice, one may prefer one over the other.

@callahad
Copy link
Collaborator

Gunicorn is the only server that supports passing arguments to factories, and even its docs suggest using environment variables instead of args.

I think there's value in merging this as-is, without argument support. That gives us parity with uvicorn, uWSGI, and gunicorn's preferred approach to factories.

@andrey-zelenkov
Copy link
Contributor

oh! I just pushed test cases inside test_python_factory.py file. I hope that won't be an issue or should we move these to test_python_target.py file

No worries, you can leave it as is (in test_python_factory.py). Just wanted to save you from extra work.

@ac000
Copy link
Member

ac000 commented Jun 26, 2024

@gourav-kandoria

Looks like there is some error in the new test somewhere, care to take a look?

gourav-kandoria added a commit to gourav-kandoria/unit that referenced this pull request Jun 26, 2024
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: nginx#1106
Link: <nginx#1336 (comment)>
[ Commit message - Dan / Minor code tweaks - Andrew ]
Signed-off-by: Andrew Clayton <[email protected]>
gourav-kandoria added a commit to gourav-kandoria/unit that referenced this pull request Jun 26, 2024
…fig"

Add the following tests cases:
1. When "factory" key is used inside the "targets" option.
2. When "factory" key is used at the root level of python application config.
3. When factory returns invalid callable or When factory is invalid callable

Closes: nginx#1106
Link: <nginx#1336>
Signed-off-by: Andrew Clayton <[email protected]>
@gourav-kandoria gourav-kandoria force-pushed the factory_support_in_python_config branch from 8185fe9 to 72e6d16 Compare June 26, 2024 19:15
gourav-kandoria added a commit to gourav-kandoria/unit that referenced this pull request Jun 26, 2024
…fig"

Add the following tests cases:
1. When "factory" key is used inside the "targets" option.
2. When "factory" key is used at the root level of python application config.
3. When factory returns invalid callable or When factory is invalid callable

Closes: nginx#1106
Link: <nginx#1336>
Signed-off-by: Andrew Clayton <[email protected]>
@gourav-kandoria gourav-kandoria force-pushed the factory_support_in_python_config branch from 72e6d16 to be3d582 Compare June 26, 2024 20:25
@gourav-kandoria
Copy link
Contributor Author

@gourav-kandoria

Looks like there is some error in the new test somewhere, care to take a look?

@ac000 made changes. There was some issue related to assertion failing while reading logs in teardown process. Although tests were passing. so made changes related to that taking reference from other tests

ac000 pushed a commit to gourav-kandoria/unit that referenced this pull request Jun 28, 2024
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: nginx#1106
Link: <nginx#1336 (comment)>
[ Commit message - Dan / Minor code tweaks - Andrew ]
Signed-off-by: Andrew Clayton <[email protected]>
ac000 pushed a commit to gourav-kandoria/unit that referenced this pull request Jun 28, 2024
Add the following tests cases:

1. When "factory" key is used inside the "targets" option.
2. When "factory" key is used at the root level of python application
   config.
3. When factory returns invalid callable or When factory is invalid
   callable

Link: <nginx#1336>
[ Commit subject & message formatting tweaks - Andrew ]
Signed-off-by: Andrew Clayton <[email protected]>
@ac000 ac000 force-pushed the factory_support_in_python_config branch from be3d582 to 5b06b11 Compare June 28, 2024 20:36
@ac000
Copy link
Member

ac000 commented Jun 28, 2024

  • Revert is_factory change
  • Commit message & subject tweaks
  • Fix a No newline at end of file issue
$ git range-diff be3d5823...5b06b11f
1:  79216972 ! 1:  047c5eeb python: Support application factories
    @@ src/python/nxt_python.c: nxt_python_set_target(nxt_task_t *task, nxt_python_targ
          char              *callable, *module_name;
          PyObject          *module, *obj;
          nxt_str_t         str;
    -+    nxt_bool_t        is_factory;
    ++    nxt_bool_t        is_factory = 0;
          nxt_conf_value_t  *value;
      
          static nxt_str_t  module_str = nxt_string("module");
    @@ src/python/nxt_python.c: nxt_python_set_target(nxt_task_t *task, nxt_python_targ
     +    value = nxt_conf_get_object_member(conf, &factory_flag_str, NULL);
     +    if (value != NULL) {
     +        is_factory = nxt_conf_get_boolean(value);
    -+    } else {
    -+        is_factory = 0;
     +    }
     +
     +    if (is_factory) {
2:  be3d5823 ! 2:  5b06b11f Add tests for feature - "Allowing factory supports for python app config"
    @@ Metadata
     Author: Gourav <[email protected]>
     
      ## Commit message ##
    -    Add tests for feature - "Allowing factory supports for python app config"
    +    tests: Add tests for python application factories
     
         Add the following tests cases:
    -    1. When "factory" key is used inside the "targets" option.
    -    2. When "factory" key is used at the root level of python application config.
    -    3. When factory returns invalid callable or When factory is invalid callable
     
    -    Closes: https://github.com/nginx/unit/issues/1106
    +    1. When "factory" key is used inside the "targets" option.
    +    2. When "factory" key is used at the root level of python application
    +       config.
    +    3. When factory returns invalid callable or When factory is invalid
    +       callable
    +
         Link: <https://github.com/nginx/unit/pull/1336>
    +    [ Commit subject & message formatting tweaks - Andrew ]
         Signed-off-by: Andrew Clayton <[email protected]>
     
      ## test/python/factory/wsgi.py (new) ##
    @@ test/test_python_factory.py (new)
     +                },
     +            }
     +        )
    - \ No newline at end of file

That is what I'm planning to merge.

This doesn't do

    if (nxt_fast_path(value == NULL)) {                                                                                        

or

    if (nxt_slow_path(is_factory)) {                                                           

As I simply don't see the point here. That is also passing judgement on peoples configuration and will "penalise" (but not really) people who do enable this feature...

@gourav-kandoria
Copy link
Contributor Author

@ac000 I guess task seems to be completed now. Are there any steps pending, before merge can be done.

wsgi_invalid_callable = None

def wsgi_factory_returning_invalid_callable():
return wsgi_invalid_callable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EOF please.

assert resp['body'] == '2'

def test_python_factory_invalid_callable_value(skip_alert):
skip_alert(r"[\s\S]*") # This regex will match to any string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will skip all alerts, including potentially dangerous ones (like segfaults), so if some alerts are expected, they should be listed explicitly:

-    skip_alert(r"[\s\S]*") # This regex will match to any string
+    skip_alert(
+        r"failed to create initial state",
+        r"failed to apply new conf",
+        r"did not return callable object",
+        r"can not be called to fetch callable",
+    )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @andrey-zelenkov just a question. on my local testing found only these alerts only
r"failed to apply new conf",
r"did not return callable object",
r"can not be called to fetch callable"
so will add skip_alert for them

Did you see it anywhere ("failed to create initial state",)
while running tests of this file, or you have added it only as an example here.

test/test_python_factory.py Show resolved Hide resolved
gourav-kandoria added a commit to gourav-kandoria/unit that referenced this pull request Jul 1, 2024
…fig"

Add the following tests cases:
1. When "factory" key is used inside the "targets" option.
2. When "factory" key is used at the root level of python application config.
3. When factory returns invalid callable or When factory is invalid callable

Closes: nginx#1106
Link: <nginx#1336>
Signed-off-by: Andrew Clayton <[email protected]>
@gourav-kandoria gourav-kandoria force-pushed the factory_support_in_python_config branch from 5b06b11 to cd063f7 Compare July 1, 2024 19:21
gourav-kandoria added a commit to gourav-kandoria/unit that referenced this pull request Jul 1, 2024
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: nginx#1106
Link: <nginx#1336 (comment)>
[ Commit message - Dan / Minor code tweaks - Andrew ]
Signed-off-by: Andrew Clayton <[email protected]>
gourav-kandoria added a commit to gourav-kandoria/unit that referenced this pull request Jul 1, 2024
…fig"

Add the following tests cases:
1. When "factory" key is used inside the "targets" option.
2. When "factory" key is used at the root level of python application config.
3. When factory returns invalid callable or When factory is invalid callable

Closes: nginx#1106
Link: <nginx#1336>
Signed-off-by: Andrew Clayton <[email protected]>
@gourav-kandoria gourav-kandoria force-pushed the factory_support_in_python_config branch from cd063f7 to 688f1c7 Compare July 1, 2024 19:44
@andrey-zelenkov
Copy link
Contributor

LGTM.

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: nginx#1106
Link: <nginx#1336 (comment)>
[ Commit message - Dan / Minor code tweaks - Andrew ]
Signed-off-by: Andrew Clayton <[email protected]>
Add the following tests cases:

1. When "factory" key is used inside the "targets" option.
2. When "factory" key is used at the root level of python application
   config.
3. When factory returns invalid callable or When factory is invalid
   callable

Link: <nginx#1336>
[ Commit subject & message formatting tweaks - Andrew ]
Signed-off-by: Andrew Clayton <[email protected]>
@ac000 ac000 force-pushed the factory_support_in_python_config branch from 688f1c7 to d67d350 Compare July 2, 2024 18:13
@ac000
Copy link
Member

ac000 commented Jul 2, 2024

  • Rebased with master
  • Re-tweaked tests commit message
$ git range-diff 688f1c78...d67d3501
 -:  -------- >  1:  57a0f94e tools/unitctl: unitctl export
 -:  -------- >  2:  999d2748 http: Move chunked buffer pos pointer while parsing
 -:  -------- >  3:  f80a36a6 http: Refactored nxt_h1p_request_body_read()
 -:  -------- >  4:  64f4c78b http: Support chunked request bodies
 -:  -------- >  5:  7a3b3fcf Packages: moved systemd service to forking on rpm-based distros
 -:  -------- >  6:  1a6d0bf5 Docker: bump node and perl versions
 -:  -------- >  7:  c88265a8 Docker: updated Rust and rustup versions
 -:  -------- >  8:  c7e921c7 Tests: chunked request body
 -:  -------- >  9:  5c4911a3 perl: Constify some local static variables
 -:  -------- > 10:  ab1b3f9d test/clone: Constify some local static variables
 -:  -------- > 11:  8e254a4d tstr, conf: Ensure error strings are nul-terminated
 -:  -------- > 12:  d62a5e2c contrib: updated njs to 0.8.5
 1:  c8343525 = 13:  a9aa9e76 python: Support application factories
 2:  688f1c78 ! 14:  d67d3501 Add tests for feature - "Allowing factory supports for python app config"
    @@ Metadata
     Author: Gourav <[email protected]>
     
      ## Commit message ##
    -    Add tests for feature - "Allowing factory supports for python app config"
    +    tests: Add tests for python application factories
     
         Add the following tests cases:
    -    1. When "factory" key is used inside the "targets" option.
    -    2. When "factory" key is used at the root level of python application config.
    -    3. When factory returns invalid callable or When factory is invalid callable
     
    -    Closes: https://github.com/nginx/unit/issues/1106
    +    1. When "factory" key is used inside the "targets" option.
    +    2. When "factory" key is used at the root level of python application
    +       config.
    +    3. When factory returns invalid callable or When factory is invalid
    +       callable
    +
         Link: <https://github.com/nginx/unit/pull/1336>
    +    [ Commit subject & message formatting tweaks - Andrew ]
         Signed-off-by: Andrew Clayton <[email protected]>
     
      ## test/python/factory/wsgi.py (new) ##

@ac000 ac000 merged commit d67d350 into nginx:master Jul 2, 2024
21 of 23 checks passed
@ac000
Copy link
Member

ac000 commented Jul 2, 2024

@gourav-kandoria Thanks! and thanks for your patience working through the review process...

@callahad
Copy link
Collaborator

callahad commented Jul 2, 2024

I'll second that; a sincere thank you for your work and engagement, @gourav-kandoria!

@gourav-kandoria
Copy link
Contributor Author

@ac000 @callahad It was also great working on this task. Thanks for your support and suggestions :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature request: Support application-factory pattern for Python apps
4 participants