-
-
Notifications
You must be signed in to change notification settings - Fork 71
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
Ability to serve an ASGI app object directly, rather than passing a module string #35
Comments
Hi @simonw, You should be able to serve your application just writing a from datasette.app import Datasette
ds = Datasette(memory=True)
app = ds.app() and run Granian from cli with: |
Thanks - that recipe worked for me right now, and didn't return any errors. However... I really want the ability to start Granian running from my own Python scripts, in the manner shown above. The reason is that Datasette is configured by the command line. The usual way of starting it looks like this:
There are a whole ton of options like those: https://docs.datasette.io/en/stable/cli-reference.html#datasette-serve
I ran into the "can't pickle local object" error while trying to build a new plugin, The implementation of So I guess this is a feature request: I would like a documented way to programatically start a Granian server without having to use the |
@simonw using Benchmarks apps in Granian repo can show you an example: granian/benchmarks/app/asgi.py Lines 76 to 82 in e5139e2
|
The challenge with that is that my application object needs to be instantiated with additional arguments that have been provided by the user. One thing that might work is that I could code-generate a Python script file that instantiates an |
@simonw considering how granian is designed, is not possible to directly pass an application instance to the server, as it would require that every loaded object should be pickable due to Probably we can solve this with a |
That could work. I'm not sure how I'd pass in the additional arguments though. The thing I want to build is effectively a CLI script that looks something like this:
(Plus a whole bunch more options). Running this script would instantiate my existing |
@simonw wait, I got an idea to get this working with the current implementation. The Line 303 in 9265a03
You can use this to override the default behaviour to loading the application, and thus you can write something like this in a run.py file: from datasette.app import Datasette
from granian import Granian
ds_kwargs = {}
def load_app(target):
if target != "dyn":
raise RuntimeError("Should never get there")
ds = Datasette(**ds_kwargs)
return ds.app()
def main():
global ds_kwargs
ds_kwargs.update(some_util_to_convert_cli_param_to_dict())
srv = Granian("dyn", address="127.0.0.1", port=8002, interface="asgi")
srv.serve(target_loader=load_app)
if __name__ == "__main__":
main() then running |
That didn't quite work - the subprocess couldn't see the I passed the arguments as serialized JSON: srv = Granian(
# Pass kwars as serialized JSON to the subprocess
json.dumps(kwargs),
address=host, Then in def load_app(target):
from datasette import cli
ds_kwargs = json.loads(target)
ds = cli.serve.callback(**ds_kwargs)
return ds.app() |
You can try the above out like this:
This will start a server on port 8000 serving the Datasette interface. |
@simonw probably wrapping works and is a cleaner solution. Eg: from datasette.app import Datasette
from granian import Granian
def app_loader(kwargs):
def load_app(target):
if target != "dyn":
raise RuntimeError("Should never get there")
ds = Datasette(**kwargs)
return ds.app()
return load_app
def main():
ds_kwargs = some_util_to_convert_cli_param_to_dict()
srv = Granian("dyn", address="127.0.0.1", port=8002, interface="asgi")
srv.serve(target_loader=app_loader(ds_kwargs))
if __name__ == "__main__":
main() |
Tried that just now but it didn't work - I got this error:
|
How about if Granian had some kind of mechanism where you could specify a pickle-able object which should be passed to each of the workers, specifically designed for this kind of use-case? |
Gonna think about it. Probably the theme here is making the |
@gi0baro Is there a solution to the problem? I want to run application without global variables.
not work |
@novichikhin what's the error? Do you have a stack trace? |
|
@novichikhin can you try with def app_loader():
from whatever import get_app_settings, register_app
settings = ...
return register_app(...) |
|
@novichikhin I don't really know how to solve the pickling issues. Maybe adding support for factories should solve your need? |
Subscribing, this issue is unfortunately a blocker for me. I currently use |
@novichikhin pickle cannot serialise closures for obvious reasons, they are not compiled into byte-code until called at least once. If you want to use settings as an argument you can either:
Both variants are applicable only for global module scope, i. e. no closures :) # runner.py
def app_loader(_: str, *, settings: AppSettings):
return register_app(settings=settings)
# __main__.py
import functools
from .runner import app_loader
settings = ...
loader = functools.partial(app_loader, settings=settings)
Granian(...).serve(target_loader=loader) |
This is a very exciting project - thanks for releasing this!
I tried to get it working with my https://datasette.io/ ASGI app and ran into this error:
Here's the script I wrote to replicate the problem, saved as
serve_datasette_with_granian.py
:Run it like this to see the error (run
pip install datasette
first):Are there changes I can make to Datasette to get this to work, or is this something that illustrates a bug in Granian?
Relevant Datasette code is here: https://github.com/simonw/datasette/blob/6a352e99ab988dbf8fd22a100049caa6ad33f1ec/datasette/app.py#L1429-L1454
It's applying my
asgi-csrf
ASGI middleware from https://github.com/simonw/asgi-csrfFunding
The text was updated successfully, but these errors were encountered: